orders
OrderApplicationServiceExposed + Spring Modulith + DDD
하나의 애플리케이션과 DB를 사용해도 주문과 배송의 상태 변경 책임을 분리할 수 있다. 공개 이벤트는 두 모듈의 연결 규칙이 되고, Modulith 검증은 이를 어긴 컴파일 시점 참조를 테스트에서 차단한다.
OrderApplicationServiceShippingReservationHandler이 예제는 DDD 용어만 소개하지 않는다. 주문 수락과 배송 예약이라는 서로 다른 상태 변경을 독립 트랜잭션과 테이블로 구현하고, 허용된 이벤트 참조와 금지된 내부 참조를 자동 검증한다.
AcceptOrderCommand는 주문 모듈로 들어가고 OrderApplicationService만 주문 상태를 변경한다.
orders.events는 Named Interface이고 배송 모듈은 orders :: events만 참조할 수 있다.
shipping → orders.internal 참조를 넣은 픽스처에서 ApplicationModules.verify()가 Violations를 반환한다.
주문과 배송은 각자 상태와 Exposed 테이블을 관리한다. 두 모듈 사이에서는 orders :: events만 공개되며, Modulith 검증은 배송 모듈이 주문 모듈의 내부 구현을 직접 참조하지 못하게 한다.
orders :: events만 Named Interface로 공개하고, ApplicationModules.verify()가 shipping → orders.internal 참조를 차단하는 전체 구조를 보여준다.
다이어그램 크게 보기
orders는 주문 상태를, shipping은 배송 예약 상태를 각각 소유한다. 배송 모듈은 주문 모듈의 내부 구현을 참조하지 않고 공개된 OrderAcceptedEvent만 수신한다.
WorkshopOrdersddd_modulith_orders · orderKey 고유 인덱스OrderAcceptedEventorders :: eventsshipping → orders.internalShippingReservationsddd_modulith_shipping_reservations · orderKey 고유 인덱스이 예제에는 별도의 Order Aggregate Root 클래스는 없다. 대신 애플리케이션 서비스의 트랜잭션, 저장소의 저장 절차, Exposed 테이블의 제약 조건을 조합해 주문 접수에 필요한 일관성을 보장한다.
AcceptOrderCommand를 OrderApplicationService에서 처리해 주문 상태 변경 진입점을 한곳으로 제한한다.
TransactionTemplate 안에서 주문을 저장하고 조회해 하나의 주문 접수 작업으로 처리한다.
WorkshopOrders.orderKey.uniqueIndex()가 동일한 주문 키의 중복 저장을 차단한다.
저장된 행을 OrderSummary로 매핑하고, 트랜잭션이 끝난 뒤 OrderAcceptedEvent를 발행한다.
Order Aggregate Root에 행위를 정의하고, 저장소가 Aggregate와 Exposed 행을 변환하도록 확장할 수 있다. 다음 클래스 다이어그램은 현재 소스에 실제로 존재하는 타입만 표시한다.여러 기능이 같은 테이블과 저장소를 직접 변경하기 시작하면 상태 변경 규칙이 분산되고 변경 영향 범위를 예측하기 어려워진다. 주문과 배송처럼 업무 용어, 상태 전이, 변경 주기가 다른 영역은 책임 경계와 공개 계약을 분명히 해야 한다.
AcceptOrderCommand 해석orderKey 중복을 고유 인덱스로 차단ACCEPTED 상태로 신규 저장OrderAcceptedEvent 발행orders :: eventsShippingReservationHandler가 이벤트 수신ExposedShippingReservationRepository로 저장경계를 만들면 변경 영향과 테스트 범위를 줄일 수 있지만, 이벤트 계약·매핑·실패 처리 설계가 추가된다. Modulith가 검증하는 범위와 운영 메시징에서 보장해야 할 범위를 구분한다.
주문 규칙은 orders, 배송 예약 규칙은 shipping이 관리한다. 다른 모듈은 내부 저장소를 직접 호출하지 않는다.
ApplicationModules.verify()는 패키지와 Named Interface를 기준으로 컴파일 시점 참조 위반을 찾는다.
모듈 사이에 전달할 최소 정보를 이벤트로 정의하고, 내부 모델과 이벤트 모델의 매핑 및 변경 호환성을 관리해야 한다.
이 예제의 @EventListener는 같은 프로세스에서 동작한다. Outbox, 브로커 저장, 중복 전달, 멱등성, 재처리를 구현하거나 보장하지 않는다.
테스트는 주문 수락 후 배송 예약이 저장되는 정상 경로와, orders.internal.LeakyOrderRepository를 직접 참조하는 경계 위반 픽스처를 함께 검증한다. 검증기는 업무 규칙이나 런타임 이벤트 전달 성공을 검사하지 않는다.
./gradlew :08-ddd-modulith-boundaries:test
AcceptOrderCommand
→ orders transaction
→ ExposedOrderRepository
→ ddd_modulith_orders
→ OrderAcceptedEvent
→ ShippingReservationHandler
→ shipping transaction
→ ddd_modulith_shipping_reservations