도메인 간 행위는 책임의 주인에게 맡겨라
호출자 쪽 인터페이스를 모듈 사이에 두기 전에 행위의 주인을 정하고, 호출자가 그 도메인의 기능을 의존하게 만드는 기준을 설명합니다.
출처 및 AI 안내: 이 글은 제미니의 개발실무 유튜브를 기반으로 작성되었습니다.
gpt-5.6-sol모델을 사용해 생성·편집했습니다.
주문이 완료되면 상품 재고를 확인하고 차감해야 한다. 주문 패키지에 인터페이스를 정의하고 상품 패키지가 구현하게 만들 수도 있다. 어떤 상황에서는 가능한 설계다. 하지만 인터페이스부터 추출하기 전에 더 근본적인 질문을 해야 한다. 재고 행위의 주인은 누구인가?
재고 확인과 차감이 상품의 핵심 기능이라면 상품이 그 기능을 제공해야 한다. 주문은 상품의 기능을 호출해 구매 흐름을 조율한다. 그러면 상품이 주문이 붙인 개념을 구현하는 대신, 비즈니스 책임을 따라 의존 방향이 만들어진다.
두 번째 호출자로 소유권을 확인한다
주문 하나만 보면 그 사례에 지나치게 맞추기 쉽다. 예약 흐름에서도 상품 재고를 먼저 확보하거나 차감해야 한다고 해보자. 재고 행위를 주문 전용 인터페이스로 만들었다면 다른 호출자가 같은 행위를 위한 포장을 또 만들게 된다. 이 반복은 행위가 어느 호출자에도 속하지 않을 가능성을 보여준다.
상품에 재고 핸들러와 같은 기능을 두면 주문과 예약 모두 같은 곳에 재고 작업을 요청할 수 있다. 정확한 이름과 오퍼레이션은 실제 정책에 따라 달라진다. 핵심은 상품에서 가장 중요한 재고 개념을 상품 가까이에 두는 것이다.
그렇다고 협업 인터페이스를 언제나 호출받는 쪽에 두라는 뜻은 아니다. 호출자가 자신의 요구를 표현하는 인터페이스를 소유하고 다른 모듈이 구현하는 편이 맞는 경우도 분명히 있다. 가상의 예제만으로 모든 경우를 결정할 수는 없다. 도메인과 행위를 먼저 정의한 뒤 인터페이스 방향을 선택해야 한다.
모듈 의존성이 같은 이야기를 하게 한다
하나의 프로젝트 안에 있는 패키지는 다소 애매해도 컴파일될 수 있다. 모듈 경계에서는 문제가 선명해진다. 주문에 인터페이스가 있고 상품이 구현하면 상품이 주문 모듈을 알아야 한다. 이 문장이 비즈니스적으로 자연스러운지 물어보자. 상품이 주문 행위를 수행하는가, 아니면 주문이 상품에 재고 변경을 요청하는가?
제시된 사례에서는 두 번째가 더 자연스럽다. 주문 모듈이 상품 모듈의 기능을 의존한다. 상품은 자기 재고를 관리하기 위해 주문을 임포트할 필요가 없다.
규모가 커지면 같은 소유권이 조직 경계에도 나타난다. 상품팀이 API를 제공하고 주문팀이 이를 호출할 수 있다. 전송 방식은 바뀌었지만 책임은 그대로다. 상품팀은 재고를 소유하고 주문팀은 구매 흐름을 소유한다.
조율도 분명한 책임이다
재고를 상품에 맡긴다고 주문의 역할이 없어지는 것은 아니다. 주문은 주문 완료 과정에서 언제 재고를 확인하고 차감할지 결정한다. 전체 유스케이스와 주문 상태를 책임진다. 상품은 자기가 소유한 재고 행위를 수행하고 그 규칙을 지킨다.
이렇게 나누면 변경 이유도 읽기 쉬워진다. 재고 규칙이 바뀌면 상품이 바뀐다. 주문 순서가 바뀌면 주문이 바뀐다. 예약이 같은 기능을 필요로 하면 주문 모양의 추상화를 복제하지 않고 상품을 의존한다.
도메인 간 코드는 누가 먼저 호출하는지만 보고 배치하지 않는다. 행위의 의미와 앞으로 생길 수 있는 다른 호출자, 그리고 한 호출자가 사라져도 그 행위를 계속 소유할 도메인을 본다. 의존 그래프는 그 답을 그대로 표현해야 한다.