3단계: 모던 자바 적용 (고급 기술)
3단계: 모던 자바로 다듬기
2단계의 뼈대를 유지한 채 중복을 제네릭으로, null을 Optional로, 루프를 스트림으로, 정책 클래스를 람다로 바꾸고, 스레드 풀로 동시 주문을 안전하게 처리한다. 이 단계에서 새로 쓰는 기술: 제네릭
Repository<T, ID>,Optional체인, 스트림 집계(groupingBy/teeing/flatMap), 함수형 인터페이스와 default 조합자,ExecutorService/Future/AtomicInteger,ConcurrentHashMap.compute,record,sealed interface+ 패턴 매칭switch.
1. 이번 단계의 목표
1.1 새 요구사항
| # | 요구사항 | 세부 |
|---|---|---|
| R13 | 할인 정책 | VIP 10%, 소계 5만 원 이상 3,000원 추가. 정책 조합 가능, 추가 시 서비스 코드 무변경 |
| R14 | 통계 | 회원별 매출 상위 N, 카테고리별 매출·수량, 월별 매출, 베스트셀러, 일정 금액 이상 주문 목록 |
| R15 | 동시 주문 | 재고 60개 한정판에 100건의 주문이 동시에 들어와도 정확히 60건만 성공하고 재고는 0에서 멈춘다 |
| R16 | 결제 결과 | 승인/거절을 예외가 아닌 값으로 돌려주고, 두 경우를 빠짐없이 처리했는지 컴파일러가 확인 |
| R17 | 주문 날짜 | 통계를 위해 주문에 날짜가 있어야 한다 |
1.2 2단계의 한계가 왜 문제였는가
- 저장소 중복: R14의 통계는 주문 저장소를 읽는다. 통계용 조회 메서드를 추가하려면 세 저장소 인터페이스와 세 구현체 중 어디를 고칠지부터 고민해야 한다. 제네릭으로 하나만 있으면 고민이 없다.
- null 반환: 통계는 "주문 → 회원 → 등급"처럼 조회를 연쇄한다. 매 단계
if (x == null)을 쓰면 통계 코드가 검사문으로 뒤덮인다.Optional.map/flatMap은 체인 중 어디가 비어도 한 번에 기본값으로 떨어진다. - 명령형 집계: R14는 다섯 가지 통계다. 2단계 방식(
for + if + merge)으로 다섯 개를 쓰면 루프가 다섯 개, 각각TreeMap인지LinkedHashMap인지, 정렬은 어디서 하는지가 제각각이 된다. 스트림은 "무엇을"만 쓰고 "어떻게"는 라이브러리에 맡긴다. - 할인 정책 부재: R13을 2단계 방식으로 하면
DiscountPolicy인터페이스 +VipDiscount,ThresholdDiscount,CompositeDiscount클래스 셋. 정책 하나가 한 줄인데 클래스 하나씩이다. 람다면 정책이 한 줄이고 조합이 메서드 체인이다. - 동시성 취약: R15가 결정적이다. 2단계
Product.decreaseStock은ensureStock(qty); stock -= qty;두 문장이다. 스레드 A가ensureStock을 통과한 직후 스레드 B도 통과하면, 둘 다 차감해서 재고가 음수가 된다(check-then-act 경쟁). 재고를 "확인하고 차감"하는 것을 하나의 원자적 동작으로 만들어야 한다. - 결제 결과가 예외: 잔액 부족은 결제에서 흔한 결과다. 예외로 표현하면 호출자가
catch를 빼먹어도 컴파일러가 모른다(unchecked).sealed로 결과 타입을 닫으면switch가 모든 경우를 다뤘는지 컴파일러가 검사한다.