Develop/java,spring

DDD Start (Domain-Driven Design) 도메인 주도 설계

kudl 2020. 12. 29. 22:59

계층(아키텍처) 구성

계층(Layer) 설명
사용자 인터페이스(UI), 표현(Presentation) 요청을 처리하고 사용자에게 정보를 보여준다.
(Controller)
응용(Application) 요청한 기능을 도메인 계층을 조합해서 기능을 실행한다.
업무 로직은 도메인 계층에서 구현한다.
(Service)
도메인 시스템에 제공할 도메인 규칙을 구현한다.
(Aggregate, Entity)
인프라스트럭쳐(Infrastructure) 데이터베이스나 메세징 시스템 같은 외부시스템과 연동한다.
(JPA, Message Queue, External API)

계층 구조는 그 특성상 상위 계층에서 하위 계층으로의 의존만 존개하고 하위 계층은 상위 계층에 의존하지 않는다.

표현계층은 응용계층에 의존하고 응용 계층이 도메인 계층에 의존하지만 인프라스트럭처 계층이 도메인에 의존하거나 도메인이 응용 계층에 의존하지 않는다.

응용(서비스) > 도메인(엔티티, 어그리게이트) > 인프라스트럭처(DB, 룰엔진, 메세징)

응용(서비스) > 인프라스트럭처(DB, 룰엔진, 메세징)

도메인(엔티티, 애그리거트) > 인프라스트럭처(DB, 룰엔진, 메세징)

 

도메인 영역의 구성 요소

요소 설명
엔티티(Entity) 고유의 식별자를 갖는 객체로 자신의 라이프 사이클을 갖는다.
벨류(Value) 고유의 식별자를 갖지 않는 객체로 주로 개념적으로 속성을 표시할때 사용한다.
애그리거트(Aggregate) 엔티티와 밸류 객체를 개념적으로 하나로 묶은 것이다.
레파지토리(Repository) 도메인 모델의 영속성을 처리한다.
도메인서비스(Domain Service) 특정 엔티티에 속하지 않은 도메인 로직을 제공한다.
ex) 할인 금액 계산같은 경우 상품, 쿠폰, 금액 등 다양한 조건을 통해 도메인 서비스에서 구현해야한다.

 

애그리거트(Aggeregate)

애그리거트는 모델을 이해하는데 도움을 주며 일관성을 관리하는 기준이된다.

애그리거트 루트가 제공하는 메소드는 도메인 규칙에 따라 애그리거트에 속한 객체의 일관성이 깨지지 않도록 구현해야 하며 도메인 모델의 엔티티나 밸류에 공개 set 메서드를 넣지 않는다면 일관성이 깨질 가능성이 줄어든다.

애그리거트 루트는 애그리거트 내부의 다른 객체를 조합해서 기능을 완성한다.

애글거트에서 트랜잭션 범위는 작을 수록 좋으며 한 트랙잭션에서 한개의 애그리거트만 수정해야한다. 두개 이상의 애그리거트를 수정하면 트랙잭션 충돌이 발생할 가능성이 크다(주문 애그리거트에서 배송정보를 기반으로 회원 정보 변경 기능이 있을때 회원 애그리게이트 수정은 하면 안되고 회원 애그리게이트에서 수정이 되게 해야한다)

애그리거트도 다른 애그리거트를 참조할 수 있다. 이때에는 ID를 이용한 참조 방식을 사용하여 복잡도를 낮추고 한 애그리거트에서 다른 애그리거트를 수정하는 일을 방지해야한다.

 

레파지토리(Repository)

애그리거트 루트와 JPA 매핑을 위한 기본 규칙은 아래와 같다.

애그리거트 루트는 엔티티이므로 @Entity 로 매핑설정한다.
한 테이블에 엔티티와 벨류 데이터가 같이 있다면
  - 벨류는 @Embeddable로 매핑 설정한다.
  - 벨류 타입 프로퍼티는 @Embedded로 매핑 설정한다.

하이버네이트는 클래스를 상속한 프록시 객체를 이용해서 지연 로딩을 구현하는데 사위 클래스의 기본생성자를 호출할 수 있어야 하므로 @Entity, @Embeddedable 기본 생성자는 private이 아닌 protected로 지정을 해야하며 @Access를 이용해서 명시적으로 접근 방식을 지정하지 않으면 @Id나 @EmbeddedId 위치에 따라 접근 방식을 결정하므로 @Access(AccessType.FIELD) 와 같이 명시적으로 지정해 주는게 좋다.

AttributeConverter는 JPA 2.1에 추가된 인터페이스로 밸류 타입과 컬럼 데이터의 변환 처리 기능을 한다. AttributeConverter 구현 클래스에 @Converter(autoApply = true) 속성을 지정하게 되면 @Converter 를 지정하지 않아도 모든 모델에 대해 일괄 적용이 가능하다.

벨류 컬랙션을 별도 테이블에 매핑할때는 @ElementCollection과 @CollectionTable을 사용하여 매핑 할수 있다. @CollectionTable은 밸류를 저장할 테이블을 지정할때 사용한다. 외부키가 두개 이상인 경우에는 @JoinColumn을 배열을 이용해서 외부키 목록 지정을 할 수 있다.

벨류에 매핑되는 테이블이 다를경우에는 @SecondaryTable과 @AttributeOverride를 사용하여 매핑 할 수 있다. @SecondaryTable의 name속성에 벨류를 저장할 테이블을 지정하고 pkJoinColumns 속성은 join 컬럼을 지정한다. @AttributeOverrid를 사용하여 벨류 데이터가 저장된 테이블을 지정한다.

 

응용 서비스 역할

응용 서비스의 주요 역할은 도메인 객체를 사용해서 사용자의 요청을 처리하는 것이다. 도메인 로직이 들어가 있으면 안된다. 도메인 로직이 들어가게 되면 코드의 응집성이 떨어지며 응용 서비스에 동일한 도메인 로직을 구현할 가능성이 높아진다. 응용 서비스 구현시 동일한 로직을 구현해야 할 경우 공통되는 로직을 별도 클래스로 구현해서 서비스에서 사용하는 것도 방법이다.

 

표현 영역

포현 영역은 사용자의 요청에 알맞은 응용 서비스에 전달하고 결과를 사용자에게 제공한다. 요청 값에 대한 검증은 표현 영역, 응용 영역 두곳에서 수행 할 수 있지만 원칙적으로는 응용영역에서 처리하는게 좋다.

 

애그리거트 트랜잭션 관리

선점 잠금(Pessimistic Lock)은 애그리커드를 구한 스레드가 애그리거트 사용이 끝날때까지 다른 스레드가 해당 애그리거트를 수정하는것을 막는 방식이다. 대부분의 DBMS가 for update 같은 쿼리를 사용하여 잠금 장치 기능을 제공한다. jpa에서는 LockModeType.PESSIMISTIC_WRITE 값을 적용하여 선점 잠금 방식을 적용 할 수 있다. 이때 hints를 사용하여 최대 대기 시간을 지정할 수 도 있다.

비선점 잠금(Optimistic Lock)은 변경한 데이터를 DBMS에 반영하는 시점에 변경 가능 여부를 확인 하는 방식이다. JPA에서 @Version 어노테이션을 붙이고 매핑되는 테이블에 버전을 저장할 컬럼을 추가해주면 된다. 애그리거트 루트에 변경된 값이 없으면 버전이 변경되지 않으므로 강제 버전 증가를 위해 LockModeType.OPTIMISTIC_FORCE_INCREMENT 값을 적용한다.

오프라인 선점 잠금(Offline Pessimistic Lock)은 지라에서 볼수 있는 기능으로 한 문서를 수정할 때 발생하는 충돌을 방지할 수 있도록 문서를 다른사람이 수정하고 있다는 안내 문구를 표시해준다. 오프라인 선점 잠금을 위해 LockManager 를 사용한다.

 

Bounded Context

도메인 모델은 특정한 Context(문맥)하에 완전한 의미를 갖는다. 같은 제품이라도 카탈로그 컨텍스트와 재고 컨텍스트에서 의미가 서로 다르다. 이렇게 구분되는 경계를 Bounded Context 라고 한다. Bounded Context는 서로 연결되기 때문에 API 호출, 이벤트 등을 통해 서로 관계를 맺는다. Bounded Context의 경계가 명확해 지고 이를 전체 구조로 표현한것을 컨텍스트 맵(Context Map) 이라고 하며 컨텍스트 맵은 전체 시스템의 이해 수준을 보여 주기도 한다.

'Develop > java,spring' 카테고리의 다른 글

Rest, Restful, Restful API 차이  (0) 2020.12.29
JWT(Json Web Token) 이란?  (0) 2020.12.15
git 명령어  (0) 2020.12.01
Okta(옥타) SSO(OIDC - Open ID Connect Application) 로그인  (1) 2020.11.30
Java Stream GroupingBy 사용  (0) 2020.11.26