마이크로서비스패턴[5] — 5장
5장 비즈니스 로직 설계
1장 모놀리식 지옥에서 벗어나라
2장 분해 전략
3장 프로세스 간 통신
4장 트랜잭션 관리: 사가
5장 비즈니스 로직 설계
6장 비즈니스 로직 개발: 이벤트 소싱
7장 마이크로서비스 쿼리 구현
8장 외부 API 패턴
9장 마이크로서비스 테스트 1부
10장 마이크로서비스 테스트 2부
11장 프로덕션 레디 서비스 개발
12장 마이크로서비스 배포
13장 마이크로서비스로 리팩터링
비즈니스 로직이 여러 서비스에 흩어져 있는 마이크로서비스 아키텍처는 복잡한 비즈니스 로직을 개발하기가 까다롭다. 각 클래스들간에 연관관계가 복잡하고, 서로 다른 서비스를 호출하며 트랜잭션을 넘나드는 상황이 발생한다. 이러한 문제는 서비스 비즈니스 로직을 여러 애그리거트로 구성하는 DDD 애그리거트 패턴으로 해결할 수 있다.
- 애그리거트를 사용하면 객체가 서비스의 경계를 넘나들 일이 없다. 객체 대산 기본키를 이용해 참조하는 것으로 해결할 수 있기 때문이다.
- 한 트랜잭션으로 하나의 애그리거트를 생성/수정할 수 있기 때문에 트랜잭션을 일관성있게 관리할 수 있다.
이 장은 마이크로서비스 아키텍처에서 비즈니스로직을 어떤 관점으로 구성할지에 대해 얘기한다.
5.비즈니스 로직 구성 패턴
책에 있는 ftgo 어플리케이션은 1장에서 봤던 그림과 같이 hexagonal 아키텍처로 구성되어 있는것을 볼 수 있다.
- 인바운드 어댑터 : 클라이언트 요청을 받아 비즈니스 로직을 호출
- 아웃바운드 어댑터 : 비즈니스 로직의 요청을 받아 다른 서비스 및 어플리케이션을 실행
위 그림에서는 비즈니스 로직을 호출하는 REST API, Web UI가 인바운드 어댑터에 해당되고, 아웃바운드 어댑터는 비즈니스 로직이 호출하는 MySQL adapter, Stripe adapter 등이 존재하는 것을 알 수 있다.
5.1. 비즈니스 로직 설계
- 트랜잭션 스크립트 패턴
절차지향적인 패턴으로 비즈니스 로직을 설계하는 방법이 있다.
위 그림에서는 OrderService에서 모든 행위를 관장하고 있으며, OrderRepository에게 DB를 호출하는 역할을 하고 있다. 반면 Order객체는 Order의 상태만을 관리할 뿐 객체 자체의 역할로써는 아무것도 하지 않고 있다.
이것을 트랜잭션 스크립트 패턴, 또는 빈약한 도메인 모델이라고 부른다.
빈약한 도메인 모델은 단순한 비즈니스 로직에는 아주 잘 통하기 때문에 사용되기도 한다.
https://martinfowler.com/eaaCatalog/transactionScript.html
https://martinfowler.com/bliki/AnemicDomainModel.html
- 도메인 모델 패턴
위의 절차적 접근 방식은 비즈니스 로직이 복잡해지면 관리가 어려워지는 단점이 있는데, 프로그램의 복잡성은 점점 커지기 때문에 도메인 모델 패턴을 응용한 객체지향 설계를 하는 것이 좋다.
도메인모델을 적용한 모델을 보면 서비스의 기능이 단순해졌다는 것을 알 수 있다. 행위를 호출하는건 그대로 OrderService의 역할이지만, 실제 행위를 처리하는것은 상태를 가진 Order객체가 처리하게 된다.
객체 지향 설계를 통해 설계를 이해/관리하기 쉬워지며, 기능에 대한 테스트, 확장성이 용이해지기 때문에 도메인 모델 패턴은 부각되고 있다.
하지만 도메인 모델 패턴도 마이크로서비스패턴에는 해결해야 할 문제가 많기 때문에 이를 개선한 DDD 모델을 필요로 하게 된다.
- 도메인 주도 설계(Domain -Driven Design)
앞서 본 도메인 모델 패턴에서 복잡한 비즈니스 로직을 개발하기 위해 개선한 접근 방식이 에릭 에반스의 Domain-Driven Design이다.
DDD 방식으로 설계를 하면 각 서비스는 자체 도메인을 가지며, 어플리케이션 전체를 도메인으로 다뤘을 때의 문제점을 방지할 수 있게 된다.
DDD방식으로 개발할 경우 전략적 설계와 전술적 설계라는 방식이 있는데, 비즈니스 로직은 전술적 설계를 기반으로 모델에서 수행하는 역할과 클래스를 분리해야 하는데, 분리되는 클래스들은 아래와 같다.
- 엔티티 : 영속화가 되어 데이터가 관리될 객체를 의미한다. 자바에서는 JPA @Entity 로 DDD의 엔티티를 나타냄
- 밸류 객체 : 속성값이 모아진 객체
- 팩토리 : 일반 생성자로 만들기에 복잡한 객체 생성 로직이 구현된 객체 또는 메서드
- 리포지토리 : 엔티티를 저장하는 DB 로직을 캡슐화 한 객체
- 서비스 : 엔티티, 밸류객체에 속하지 않는 비즈니스 로직 구현 객체
5.2. 도메인 모델 설계 : DDD 애그리거트 패턴
전통적인 도메인 모델은 비즈니스 객체들의 경계가 불분명하다. 경계가 불분명하면 마이크로서비스 아키텍처에서 문제가 생길 가능성이 높다.
- 애그리거트
애그리거트는 한 단위로 취급 가능한 경계 내부의 도메인 객체들이다. 하나의 루트 엔티티와 하나 이상의 기타 엔티티 + 밸류 객체로 구성된다.
작업은 애그리거트 단위로 이루어지며, 애그리거트 루트를 통해 전체 엔티티들이 역할을 수행하게 된다. DDD 도메인 모델 설계의 핵심은 애그리거트와 그 경계, 그리고 애그리거트 루트를 식별하는 것이다.
- 애그리거트 규칙1
외부 클래스는 각 애그리거트 루트를 통해서만 참조를 할 수 있게 해야한다. 무분별한 값 변경을 방지하기 위해서다.
- 애그리거트 규칙2
애그리거트는 객체 레퍼런스 대신에 기본키를 가지고 서로 참조를 해야 한다.
애그리거트간의 경계는 분명하되, 느슨하게 결합되어야 하기 때문에 기본키와 FK를 통해 연결하는것이 좋다.
- 애그리거트 규칙3
하나의 트랜잭션으로 하나의 애그리거트를 생성/수정해야 한다. 데이터의 정합성을 맞추기 위해 하나의 트랜잭션으로 프로세스가 이루어 져야 하고, 이 프로세스는 앞장에서 배운 사가를 사용해 관리해야 일관성을 유지할 수 있다.
- 애그리거트 분리
애그리거트의 단위는 작을수록 좋다. 잘게 나누어져있는만큼 동시 처리 가능한 요청 개수가 늘고 확장성이 좋아진다.
5.3. 도메인 이벤트 발행
DDD의 입장에서 도메인 이벤트는 애그리거트에 발생한 이벤트이다.
애그리거트는 상태가 변경될 때마다 컨슈머를 위해 이벤트를 발행한다.
- 도메인 이벤트란?
과거 분사형 동사로 명명한 클래스이다. 이벤트에 의미를 부여하는 프로퍼티는 원시값 또는 밸류 객체이다. 대부분 이벤트ID, 타임스탬프와 같은 메타데이터도 존재하는데, 변경을 일으킨 사용자의 신원정보를 넣기도 하는데 감사 용도로 좋다고 한다.
intetface DomainEvent {}interface OrderDomainEvent extends DomainEvent{}class OrderCreatedEvent implements OrderDomainEvent {}interace DomainEventEnvelope<T extends DomainEvent> {
String getAggregateId();
Message getMessage();
String getAggregateType();
String getEventId(); T getEvent();
}
- 이벤트 강화
컨슈머를 작성할 때 이벤트 컨슈머가 항상 서비스를 쿼리해서 애그리거트를 조회하는 것은 오버헤드를 발생시킨다. 그러므로 이벤트를 발행할 때 컨슈머에 필요한 이벤트를 함께 가지고 다니도록 이벤트 강화 기법을 적용할 수 있다
class OrderCreatedEvent implements OrderEvent {
private List<OrderLineItem> lineItems;
private DeliveryInformation deliveryInformation; // 강화된 데이터
}
- 도메인 식별
도메인 이벤트를 식별하기 위해 최근에는 이벤트 스토밍이라는 기법을 많이 사용하는 추세이다. 이벤트 스토밍에 대해서는 여기서 다루지 않고, 이전에 다뤘던 박재성(jason)님의 이벤트 스토밍 유튜브로 대신한다. - 도메인 이벤트 생성 및 발행
비즈니스 로직이 메인 이벤트를 메시지 브로커에 발행하려면 먼저 도메인 이벤트를 생성해야 한다.생성한 이벤트는 애그리커트 메서드 반환 값이 이벤트 목록을 넣어 반환하며, 메시지를 발행하기 위해서는 트랜잭셔널 메시지를 사용하여 반환을 한다. - 도메인 이벤트 소비
도메인 이벤트는 결국 메시지로 바뀌어 메시지 브로커에 발행된다.
이번 장에서는 절차지향적인 패턴에서 도메인 주도 설계까지 흘러가는 흐름이 중요했다고 생각한다. 이벤트 발행과 구독도 물론 중요하지만 DDD의 개념은 혼자서 학습하기에는 어려웠는데, 나의 경우 책을 완독하고 스터디를 통해서 계속적으로 학습했기 때문에 그나마 이해가 용이했다.
이벤트 생성과 발행/구독은 앞으로도 계속 나오겠지만 이후에 실습코드로 구현해야 할 것 같다.