[마이크로서비스패턴] 5장 비즈니스 로직 설계

5장. 비즈니스 로직 설계

5장 핵심내용

  • 비즈니스 로직 패턴 적용: 트랜잭션 스크립트 패턴, 도메인 모델 패턴
  • DDD 애그리거트 패턴
  • 도메인 이벤트 패턴 MSA에 적용

비즈니스 로직 구성 패턴

2가지 패턴 : 트랜잭션 스크립트 & 도메인 모델

1. 트랜잭션 스크립트 패턴

비즈니스 로직을 요청 타입별로 하나씩 매핑된 절차적 트랜잭션 스크립트 뭉치로 구성

객체 지향 설계가 과도하다고 판단되는 단순한 비즈니스 로직에 적합.

2. 도메인 모델 패턴

비즈니스 로직을 상태와 동작을 가진 클래스로 구성된 객체 모델로 구성

객체 지향 설계 장점

  1. 설계 이해 및 관리 쉬움
  2. 테스트 쉬움
  3. 다양한 객체 지향 디자인 패턴 응용 가능

도메인 주도 설계

  • entity : 영속적 신원 소유 객체. JPA @Entity. 두 엔티티가 속성 값이 동일해도 다른 객체로 구분.
  • value object : 여러 값을 모아 놓은 객체. 속성 값이 동일한 두 객체는 동일 취급.
  • factory : 객체 생성 로직이 구현된 객체 또는 메서드.
  • repository : 엔티티를 저장하는 DB 접근 로직 캡슐화한 객체.
  • service : 비즈니스 로직 구현 객체

도메인 모델 설계: DDD 애그리거트 패턴

도메인 모델 설계 시 각 객체 간의 관계의 경계가 불분명. => 애그리거트 활용

애그리거트

도메인 모델을 여러 애그리거트로 구성. 각 애그리거트는 한 단위로 취급 가능한 객체망.
루트 엔터티 1개 & 기타 엔터티 + 밸류 객체

DDD 모델 설계 핵심 : 애그리거트와 그 경계, 루트 식별

애그리거트 규칙

  1. 애그리거트 루트만 참조하라
    • 클라이언트는 애그리거트 루트 메서드만 호출해서 애그리거트를 업데이트 => 자신의 불변 값을 강제할 수 있음
  2. 애그리거트 간 참조는 반드시 기본키를 사용
    • 객체 레퍼런스 대신 기본키로 서로 참조.
    • 애그리거트 간 느슨한 결합 : 뚜렷한 경계. 다른 애그리거트 업데이트 절대 불가. 다른 서비스 애그리거트여도 문제 없음.(id 값만 참조하니까)
    • 저장 로직 간단.
  3. 하나의 트랜잭션으로 하나의 애그리거트를 생성/수정
    • 여러 애그리거트 생성/수정 시 사가로 해결 : 하나의 서비스에서 여러 애그리거트 생성/수정은 하나의 트랜잭션으로 묶기도.

도메인 이벤트 발행

애그리거트는 뭔가 생성되거나 중요한 변경이 발생했을 때 도메인 이벤트를 발행

변경 이벤트 발행 이유

  • 여러 서비스 데이터 일관성 유지
  • CQRS
  • 다음 프로세스 처리 목적
  • 웹 소켓 메시지 전송 or es 같은 텍스트 DB 업데이트
  • 사용자에게 알림
  • 사용자 행동 분석
interface DomainEvent {}

interface OrderDomainEvent extends DomainEvent {}

class OrderCreatedEvent implements OrderDomainEvent {}

interface DomainEventEnvelope<T extends DomainEvent> {
  String getAggregateId();
  Message getMessage();
  String getAggregateType();
  String getEventId();

  T getEvent();
}

이벤트 강화 : 컨슈머에 필요한 정보를 이벤트에 담음. -> 서비스에 대한 쿼리 불필요(서비스 쿼리 오버헤드 감소). but, 컨슈머 요건 변경 시 이벤트 클래스도 변경

도메인 이벤트 확실하게 발행 방법(Eventuate Tram프레임워크)

  • DB 업데이트 트랜잭션의 일부로 이벤트를 OUTBOX 테이블에 삽입 -> 트랜잭션 커밋 -> 테이블에 삽입된 이벤트를 메시지 브로커에 발행
  • Main 클래스 - import TramJdbcKafkaConfiguration.java
    public class MessageProducerJdbcImpl implements MessageProducerImplementation {
      // ... 생략 ...
      @Override
      public void send(Message message) {
        String id =  eventuateCommonJdbcOperations.insertIntoMessageTable(idGenerator,
                  message.getPayload(),
                  message.getRequiredHeader(Message.DESTINATION),
                  message.getHeaders(),
                  eventuateSchema);
    
        message.setHeader(Message.ID, id);
      }
    }