가존에 애플리케이션의 유스케이스들을 전부 이벤트 핸들러로 변경하면서 새로운 배치 만드는 함수 같은 것들도 이벤트를 만들어서 처리함. 이게 직관적으로 보면 약간 어색해 보임. 배치가 생성도 안 됐는데 api를 호출하는 것도 이상함. 이벤트와 같은 메시지 버스를 다루지만 약간 다른 규칙으로 처리하는 커맨드를 도입함
대략 보니까 기존에 이벤트로 하던걸 커맨드라고 따로 빼서 원래 필요한 서비스작업들을 커맨드로 실제 이벤트로 처리하려 했던 것들을 이벤트로 남김.
커맨드도 메시지의 일종임.
다만 중요한점은 커맨드는 한 행위자로부터 다른 구체적인 행위자에게 전달됨. 명령형 동사구를 사용함. 커맨드는 의도를 잡아냄. 그래서 커맨드를 보내는 행위자는 커맨드 수신자가 커맨드 처리에 실패했을 때 오류 정보를 돌려받길 원함. 이벤트는 행위자가 관심 있는 모든 리스너에게 보내는 메시지임. 그니까 행위자는 누가 이벤트를 받는지 모르고 받는 쪽의 성공이나 실패에 관심이 없음
10.2 예외 처리 방식의 차이점
일단 코드를 보면서 차이를 알아야함 위에서 언급한 거처럼 이벤트는 그냥 에러 나도 기존 로직 그대로 진행 커맨드는 raise 함
def handle(
message: Message,
uow: unit_of_work.AbstractUnitOfWork,
):
results = []
queue = [message]
while queue:
message = queue.pop(0)
if isinstance(message, events.Event):
handle_event(message, queue, uow)
elif isinstance(message, commands.Command):
cmd_result = handle_command(message, queue, uow)
results.append(cmd_result)
else:
raise Exception(f"{message} was not an Event or Command")
return results
def handle_event(
event: events.Event,
queue: List[Message],
uow: unit_of_work.AbstractUnitOfWork,
):
for handler in EVENT_HANDLERS[type(event)]:
try:
logger.debug("handling event %s with handler %s", event, handler)
handler(event, uow=uow)
queue.extend(uow.collect_new_events())
except Exception:
logger.exception("Exception handling event %s", event)
continue
def handle_command(
command: commands.Command,
queue: List[Message],
uow: unit_of_work.AbstractUnitOfWork,
):
logger.debug("handling command %s", command)
try:
handler = COMMAND_HANDLERS[type(command)]
result = handler(command, uow=uow)
queue.extend(uow.collect_new_events())
return result
except Exception:
logger.exception("Exception handling command %s", command)
raise
10.3 논의: 이벤트, 커맨드, 오류 처리
그럼 여기서 궁금한 게 '이벤트 처리에 실패하면 어떻게 되는가?'임 위처럼 처리하면 시스템의 일관성을 유지한다고 확신할 수 있을까?
근데 기존에 애그리게이트를 생각해 보면, 애그리게이트는 업데이트 성공이나 실패를 원자적으로 처리하기 위해 UoW를 설계했음. 정의에 따르면 여러 애그리게이트는 즉각적으로 일관성 있을 필요가 없음. 어떤 게 실패하고. 성공해도 일관성 있는 상태를 보장한다고 한다. 책에 예시를 보여주는데 이걸 보면 애그리게이트는 상태를 데이터베이스에 영속화한 다음에 이벤트를 발생시킴, 근데 상태를 영속화하기 전에 이벤트를 발생시키고 동시에 모든 변화를 커밋하면 어떤가?? -> 어떻게 보면 작업이 완료됐다고 확신할 수 있어 보임.
하지만 예를 들어서 A, B, C, D 순서로 동작할 때 D라는 로직에서 문제가 생기면 앞의 로직들을 얘 때문에 진행할 수 없다. 만약에 이게 책의 예시처럼 물건을 사고 그 뒤에 그 결과에 따라서 이벤트가 발생한다고 할 때. 이벤트 발생 로직 문제 때문에 앞의 주문 로직이 안 되는 건 말이 안 된다.
이런 관심사를 분리하면 실패할 수 있는 요소들이 서로 격리되어 실패하게 할 수 있다 함. 이렇게 하는 게 시스템 전체의 신뢰성을 높이는 거라고 함. 코드에서 성공해야 하는 부분은 주문을 만드는 커맨드 핸들러뿐이다.
10.4 동기적으로 오류 복구하기
10.3 절에서 이벤트가 커맨드와 독립적으로 실패해도 괜찮은 이야길 했다면, 여기서는 불가피하게 오류가 발생한 경우 오류를 복구시킬 수 있다고 확신하려면 어떻게 해야 하는지가 나와있음.
위의 코드에도 있지만 핸들러에 로그를 찍고 있음. 또한 실제로 서비스는 네트워크 일시적인 문제, 데이터베이스 테이블의 교착상태, 배치로 인해 발생하는 서비스 중단 등이 생길 수 있음. 이런 경우에는 재시도하는 로직을 추가해야 함. 여기서는 tenacity라는 라이브러리를 사용함
참고 및 출처: <파이썬으로 살펴보는 아키텍처 패턴: TDD, DDD, EDM 적용하기)> (해리 퍼시벌, 밥 그레고리 지음, 오현석 옮김, 한빛미디어 , 2021)
'Book' 카테고리의 다른 글
의존성 주입(그리고 부트스트래핑) (0) | 2023.11.16 |
---|---|
명령-질의 책임 분리(CQRS) (0) | 2023.11.03 |
메시지 버스로 통합하기 (2) | 2023.10.22 |
오브젝트. Chapter 01 객체,설계 (0) | 2023.10.18 |
애그리게이트와 일관성 경계 (0) | 2023.10.08 |