개요
이 문서는 서비스 함수가 도메인 모델에 종속되어 발생하는 문제를 python 내장 자료형으로 변경하고 이어 이벤트 발행 구조로 개선하는 방법을 설명합니다.
도메인 종속성이 높은 서비스 함수의 한계
아래 코드에서 allocate
함수는 OrderLine
도메인 모델에 직접 의존하고 있습니다.
def allocate( line: OrderLine, repo: AbstractRepository, session) -> str:
파라미터를 기본형으로 바꿔서 도메인 모델 의존성 낮추기
도메인에서 분리된 서비스 계층을 갖기 위해서 아래와 같이 도메인 모델에서 python 기본형으로 수정했습니다.
def allocate( line: OrderLine, orderid: str, sku: str, qty: int, repo: AbstractRepository, session) -> str: line = OrderLine(orderid, sku, qty) ...
내부적으로 OrderLine을 사용하기 때문에 여전히 같은 것 아니냐는 질문이 생길 수 있습니다.
위와 같이 변경을 한 이유는 테스트 코드를 보면 쉽게 알 수 있습니다.
def test_returns_allocation(): ... line = Orderline("o1", "LAMP", 10) result = services.allocate(line, ...) result = services.allocate("o1", "LAMP", 10)
도메인 객체의 인터페이스가 변경되어도, 테스트 코드는 그대로 유지될 가능성이 커집니다.
테스트 코드는 시스템을 변경하는 데 장애물이 아닌 도움이 되어야 하기 때문에, 기능이 아닌 코드 디자인을 바꾸는 것만으로 깨지는 테스트 코드는 줄이는 것이 좋습니다.
단일 책임 원칙을 지키는 이벤트 기반 설계
단일 책임 원칙(SSR)을 달성하기 위해 allocate의 파라미터를 기본형에서, 이벤트 모델로 다시 변경해보겠습니다. 예를 들어, 할당 시 재고가 부족한 경우 이메일 발송을 하는 로직을 서비스 함수에서 핸들러로 책임을 옮깁니다.
class Product: ...
def allocate(self, line: OrderLine) -> str: try: ... batch.allocate(line) ... except StopIteration: email.send_mail(...) raise OutOfStock(f"Out of stock for sku {line.sku}") self.events.append(event.OutOfStock(line.sku)) return None
이벤트 핸들러를 통해 이메일 발송 등의 부수 효과를 별도 핸들러로 분리할 수 있습니다.
def handle(event: events.Event): for handler in HANDLERS[type(event)]: handler(event)
def send_out_of_stock_notification(event: events.OutOfStock): email.send_mail(...)
HANDLERS = { events.OutOfStock: [send_out_of_stock_notification],}
마치며
- 파라미터를 기본형으로 바꾸면, 서비스 함수가 도메인 모델에 직접 의존하지 않아 요구사항과 코드 디자인의 결합도가 낮아질 수 있습니다.
- 이벤트 기반 구조를 적용하면, 부수 효과(예: 이메일 발송)를 별도의 핸들러로 관리할 수 있어 책임이 명확하게 분리됩니다.