kee-log

인터페이스는 언제 만들어야 하는가

인터페이스는 언제 만들어야 하는가

Application Service는 어디에 있는가에서 레이어를 정리하고 나면 다음 함정이 기다립니다. "그럼 이 흐름의 각 단계를 전부 인터페이스로 뽑아야 하나?"

큰 UseCase 하나를 여러 단계로 쪼갤 때 — 예를 들어 "주문 생성" 유스케이스 안에 "장바구니 검증", "재고 확인", "쿠폰 적용", "결제 요청" 같은 단계가 있다고 하면 — 이 단계들을 전부 ICartValidator, IInventoryChecker, ICouponApplier, IPaymentRequester처럼 인터페이스화하고 싶은 유혹이 생깁니다.

하지만 이건 대체로 과합니다. 특히 아직 업무 규칙이 자주 바뀌는 탐색 단계의 코드라면 더더욱 그렇습니다.

인터페이스가 필요한 경우

  • 여러 UseCase에서 실제로 재사용된다
  • 테스트에서 대체 구현이 명확히 필요하다 (예: 외부 API를 Mock으로 교체)
  • 외부 포트 성격이다 (DB, 메시징, 외부 시스템 연동)
  • 구현이 여러 개로 바뀔 가능성이 현실적으로 있다
  • Application 경계에서 역할 이름이 안정적이다

인터페이스가 과한 경우

  • 호출자가 하나뿐이다
  • 구현도 하나뿐이다
  • 단순히 Handler가 길어서 나눈 것뿐이다
  • 아직 업무 규칙이 자주 바뀌는 탐색 단계다
  • 메서드 하나짜리 pass-through 서비스가 된다

현실적인 단계

1. Command Handler는 유지한다
2. 너무 긴 부분만 private method로 분리한다
3. 재사용이 "확인된" 것만 Application Service로 추출한다
4. 이미 재사용 중인 서비스만 interface로 유지한다

예를 들어 우선은 이 정도면 충분합니다.

CreateOrder.Handler
 → validateCartAsync(...)
 → checkInventoryAsync(...)
 → applyCouponAsync(...)
 → requestPaymentAsync(...)

그리고 나중에 validateCartAsync가 다른 UseCase(예: "재주문하기")에서도 필요해지면, 그때 가서

CartValidator (내부 sealed class)

로 추출하고, 더 나중에 구현을 교체할 필요가 실제로 생기면 그때 ICartValidator로 올립니다.

핵심은 "Command chain을 줄이기 위해 모든 단계를 인터페이스 서비스로 바꾸는 것"보다, 상위 Handler를 유지하고 내부 메서드·소수의 내부 서비스만 쓰는 편이 Simplicity와 실용주의 원칙에 더 맞는다는 것입니다. 인터페이스는 나중에 필요할 때 올려도 늦지 않습니다. 미리 만드는 인터페이스는 대부분 구현체가 하나뿐인 채로 남습니다.

상위 UseCase 단위에도 같은 기준을

  • 외부 계약에서 들어오는 최초 진입점(예: CreateOrder.Command) → 상위/Orchestrating UseCase. Command Handler로 유지하고, 내부적으로는 Application Service를 호출해 흐름만 조정한다.
  • 독립적으로 호출될 수 있는 하위 흐름(예: CreateShipment.Command) → Command Handler를 유지할 수 있되, 핵심 로직은 Application Service로 추출 가능하다.
  • 외부 공개가 필요 없고 여러 UseCase에서 재사용되는 흐름(예: ValidateOrderPlan) → 내부 Application Service가 더 적합하다. Command Handler까지 만들 필요는 없다.
  • 조회처럼 보이지만 실제로는 내부 정책/결정 로직(예: FindShippingSlot) → 외부 Query라기보다는 내부 Application Service 혹은 Policy Service 후보다. MediatR Query로 둘 필요성은 낮다.
  • 판단이 애매한 경우(예: 하위 도메인이 독립 Bounded Context 성격을 띠기 시작하는 흐름) → 내부에서만 쓰인다면 Application Service, 다른 BC에서도 호출된다면 Contracts 경계(공개 UseCase)가 필요하다는 신호로 본다.

다음 글에서는 "재사용이 확인되면 승격한다"는 이 기준을 유스케이스 자체의 구조로 확장합니다. → Leaf UseCase — 유스케이스도 트리로 생각하기