홈 › 미니 프로젝트 › 03 / 4

3단계: 모던 자바 적용 (고급 기술)

섹션 6진행 0 / 4

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가 모든 경우를 다뤘는지 컴파일러가 검사한다.