도입
백엔드 개발에서 버그의 상당수는 “계산이 틀려서”가 아니라, 상태가 예상과 다르게 바뀌어서 발생합니다.
동시성 환경에서는 이 문제가 더 심해지는데 이러한 위험을 줄이기 위해 불변성(immutability), 순수 함수(pure function), 부수 효과(side effect) 제어에 집중합니다.
“함수형은 언어가 아니라 사고방식이다.
상태를 줄이면 테스트와 운영이 쉬워진다.”
정의
불변 데이터를 중심으로 프로그램을 구성하는 패러다임입니다.
“함수형 = 재귀/람다/스트림” 같은 기술로만 이해하면 실무 적용이 어려워집니다.
핵심은 결과가 예측 가능한 코드(Deterministic)를 만드는 것입니다.
핵심 개념
순수 함수
예: “주문 총액 계산”은 DB나 시간에 의존하지 않고 계산만 하면 됩니다. 이런 로직은 순수 함수로 분리하면 테스트가 매우 간단해집니다.
// 순수 함수 예시(개념)
// - 외부 상태 접근 없음
// - 동일 입력 → 동일 출력
int calcTotalPrice(List<OrderLine> lines) {
int sum = 0;
for (OrderLine l : lines) sum += l.price() * l.qty();
return sum;
}
불변성
가변 상태가 공유되면(예: static Map, 전역 캐시) 어디서 값이 바뀌었는지 추적하기 어렵고, 멀티스레드에서 레이스 컨디션이 발생합니다. 불변 객체는 생성 후 상태가 바뀌지 않으니 공유해도 안전하고, 로그/디버깅도 쉬워집니다.
// 불변 객체 개념 예시
final class Money {
private final int amount;
Money(int amount) { this.amount = amount; }
int amount() { return amount; }
Money plus(Money other) { return new Money(this.amount + other.amount); }
}
부수효과(Side Effect) 분리
백엔드에서 부수효과는 보통 DB 저장, 외부 API 호출, 메시지 발행, 로그 기록입니다. 이들을 로직 중간에 섞어버리면 테스트가 어렵고, 장애 시 영향 범위가 커집니다. 반대로 “순수 로직은 계산만” 하고, “부수효과는 마지막에 한 곳에서” 처리하면 설계가 단단해집니다.
💡 TIP / “순수 계산 → 마지막에 저장”
가장 쉬운 FP 적용법은 (1) 계산 로직을 순수 함수로 분리하고, (2) 저장/발행 같은 IO는 애플리케이션 서비스 끝에서 수행하는 구조입니다. 이 방식은 DDD의 “도메인 모델(순수 규칙) vs 인프라(IO)” 분리와도 잘 맞습니다.
함수 합성 & 컬렉션 처리
Java에서는 Stream API가 대표적입니다. 필터/매핑/집계가 “선언적”으로 표현되어 읽기 쉬워지고, 중간 변수를 줄여 부수효과를 줄일 수 있습니다.
// 컬렉션 처리 예시(개념)
int total = lines.stream()
.filter(l -> l.qty() > 0)
.mapToInt(l -> l.price() * l.qty())
.sum();
백엔드에서 언제 쓰면 좋은가
자주 하는 오해
BAD
- Stream 남발로 디버깅/가독성 악화
- IO(DB/HTTP)를 람다 체인 중간에 섞음
- 불변을 지킨다고 복사 비용이 과도해짐
GOOD
- 순수 계산 로직만 함수형 스타일로 분리
- IO는 경계(서비스 끝/인프라 레이어)에 모아 통제
- 불변 객체는 “핵심 값”부터 적용
정리
✅ 핵심 요약
- ✔️ FP의 핵심은 순수 함수 + 불변성 + 부수효과 분리입니다.
- ✔️ 백엔드에서는 “규칙 계산”을 순수 함수로 분리하는 것만으로도 효과가 큽니다.
- ✔️ IO(DB/네트워크)는 경계로 모아 통제하면 테스트/장애 대응이 쉬워집니다.
- ✔️ FP는 전면 도입보다 “문제 많은 지점부터 점진 적용”이 현실적입니다.