🧭 시작은 단순했습니다
Loopers 과제 Round 3에서는
상품, 브랜드, 좋아요, 주문, 결제 도메인을 구현해야 했습니다.
기능만 보면 단순한 CRUD처럼 보였지만,
실제로는 도메인 간 협력, 구조 확장, 책임 분리 등
설계 측면에서 많은 고민이 필요했습니다.
📦 1. 상품 조회 – 정렬 기준이 늘어날수록 구조는 어떻게 바뀔까?
처음엔 이렇게 생각했습니다:
“상품 리스트를 가져오고, 브랜드/좋아요 정보를 붙이면 되지!”
이 방식의 장점은 도메인 경계가 명확하다는 점이었습니다.
- Product는 ProductService에서
- Brand와 Like는 각각의 서비스에서 분리되어 처리
하지만 정렬 기준이 다양해지면서 문제가 생겼습니다.
예를 들어:
- 좋아요 순 정렬
- 브랜드 인기순 정렬
- 주문/리뷰 기반 정렬 가능성
기존 구조에서는 정렬을 Product 기준으로만 처리할 수 있었기 때문에,
다른 기준으로 정렬하려면 조합 흐름을 분기해야 했고
→ 코드 중복과 복잡도가 증가했습니다.
🔁 그래서 선택한 해결책은 "조인 + DTO"
정렬 조건마다 흐름을 나누는 대신,
필요한 정보를 조인하여 한 번의 쿼리로 조회하고
Projection된 DTO로 결과를 반환하는 방식으로 바꿨습니다.
⚠️ 그런데 이건 도메인 침해 아닐까?
기술적으로는
“조회니까 조인하면 되지”
라고 할 수 있지만, 설계 관점에선 고민이 생겼습니다:
- RepositoryImpl에서 도메인 간 조인이 이뤄지면 경계가 모호
- 정렬 조건이 늘어날수록 특정 도메인에 의존한 쿼리가 증가
- 유지보수 시 다른 도메인 변경에 따라 조회 쿼리까지 영향 받을 가능성
그래서 다음과 같은 절충안을 정했습니다:
- 도메인 로직이 아닌 단순 데이터 조회만 수행
- Projection 결과는 DTO로만 사용
- 해당 DTO에서 다른 도메인 기능을 호출하지 않음
완벽한 정답은 아닐 수 있지만,
현재로선 현실적인 선택이었습니다.
🧩 2. 결제 흐름 – 전략은 썼다, 그런데 책임은 누구인가?
결제 흐름은 다음과 같았습니다:
- 상품 재고 차감
- 주문 생성
- 결제
- 주문 상태 전이
처음엔 포인트 결제만 있었지만,
**카드 또는 복합 결제(포인트 + 카드)**로 확장될 가능성이 있었습니다.
그래서 결제 방식은
👉 전략 패턴(Strategy Pattern) 으로 구현했습니다.
💡 전략 패턴의 장점
- 새로운 결제 방식이 생겨도 기존 코드 수정 없이 전략만 추가
- OCP(Open-Closed Principle) 만족, 확장성 우수
🤔 그런데 전략 선택은 누가 책임져야 할까?
전략을 선택하는 책임이 PaymentService에 있는 게 맞는가?
이건 결제 방식이라는 ‘흐름’에 가까운 컨텍스트이므로
UseCase나 Facade에서 선택하는 게 맞지 않을까 생각했습니다.
✅ 그래서 책임을 Facade로 이동
- 결제 전략은 OrderFacade에서 선택
- PaymentService는 실행만 담당
또는
👉 전략 선택과 실행을 묶는 PaymentProcessor 컴포넌트 도입도 고려할 수 있습니다.
💡 Composite 전략까지 확장 고려
“포인트 부족하면 나머지 카드로 결제해줘요”
같은 요구사항을 대비해
CompositePaymentStrategy 구조도 함께 설계했습니다.
public class CompositePaymentStrategy implements PaymentStrategy {
private final List<PaymentStrategy> delegates;
@Override
public void pay(PaymentCommand command) {
for (PaymentStrategy strategy : delegates) {
strategy.pay(command);
}
}
@Override
public boolean supports(PaymentMethod method) {
return delegates.stream().anyMatch(s -> s.supports(method));
}
}
🤯 그리고 현실은... JPA가 기억에 남지 않음
설계는 좋았지만, 실제 구현에선 많은 오류를 마주쳤습니다.
- Q클래스 미생성
- queryDsl 작성 오류로 인한 에러들
- 잘못된 연관관계 설정
좋아요 정렬 쿼리 하나에 반나절이 날아갔습니다 😇
✅ 마무리
이번 과제를 통해 느낀 건
"코드는 지워지지만, 설계 고민은 남는다"
좋아요 정렬 하나로 도메인 경계까지 고민하게 됐고,
포인트 결제 하나로 전략 패턴과 책임 분리까지 생각하게 됐습니다.
정답은 아닐 수 있지만,
이 고민들이 코드에 녹아 있다는 것만으로도 의미가 있다고 생각합니다.
✍️ 다음 글 예고:
“Q클래스가 생성이 안 됩니다”… 왜 매번 그놈의 Gradle은 나를 배신하는가
실전에서 겪은 QueryDSL 삽질 일지와 해결법 총정리 🔍
'Loopers' 카테고리의 다른 글
락은 왜 느리고, MVCC는 무엇을 바꾸었나 (8) | 2025.08.10 |
---|---|
동시성 제어, 도메인 분리를 삼킨 괴물 (4) | 2025.08.08 |
Loopers WIL – 2주차 (0) | 2025.07.24 |
Loopers요구사항 정의서 없이 만드는 개발은 결국 돌고 돌아 제자리로 돌아온다 (0) | 2025.07.24 |
WIL – 1주차 (TDD & 테스트 가능한 구조) (2) | 2025.07.17 |