반응형

DDD START 책의 핵심 내용에 대한 정리


1. 도메인 모델 시작.


도메인이란? 소프트웨어로 해결하고자하는 문제 영역으로 다수의 하위 도메인을 가질 수 있다.


[표 1.1] 아키텍처 구성

계층(Layer)

 설명 

 사용자인터페이스(UI) 또는 표현(Presentation) 

 사용자의 요청을 처리하고 사용자에게 정보를 보여준다. 여기서 사용자는 소프트웨어를 사용하는 사람뿐만 아니라 외부 시스템도 사용자가 될 수 있다. 

 응용(Application) 

 사용자가 요청한 기능을 실행한다. 업무 로직을 직접 구현하지 않으며 도메인 계층을 조합해서 기능을 실행한다. 

 도메인  

 시스템이 제공할 도메인 규칙을 구현한다. 

 인프라스트럭처(Infrastructure) 

 데이터베이스나 메시징 시스템과 같은 외부 시스템과 연동을 처리한다. 


엔터티와 벨류

기획서, 유즈케이스, 유저스토리 등의 분석을 통해 도출된 모델은 Entity와 Value로 구분할 수 있다.

Entity는 식별자를 갖는 모델, Value는 개념적으로 완전한 하나의 데이터를 표현할 때 사용한다.




2. 아케텍처 개요.


네 개의 영역

...

DIP 주의사항

 DIP의 핵심을 고수준 모듈이 저수준 모듈에 의존하지 않도록 하기 위함, 하위 기능을 추상화한 인터페이스는 고수준 모듈 관점에서 도출하기 때문에 고수준 모듈에 같이 위치해야 한다.


도메인 영역의 주요 구성요소

[표2.1] 도메인 영역의 주요 구성요소

 요소

 설명 

 Entity 

 고유의 식별자를 갖는 객체로 자신의 라이프사이클을 갖는다. 주문(Order), 회원(Member), 상품(Product)과 같이 도메인의 고유한 개념을 표현한다. 도메인 모델의 데이터를 포함하며 해당 데이터와 관련된 기능을 함께 제공한다. 

 Value

 고유의 식별자를 갖지 않는 객체로 주로 개념적으로 하나인 도메인 객체의 속성을 표현할 때 사용된다. 배송지 주소를 표현하기 위한 주소(Address)나 구매 금액을 위한 금액(Money)과 같은 타입이 밸류 타입이다. 엔터티의 속성으로 사용될 뿐만 아니라 다른 밸류 타입의 속성으로도 사용될 수 있다. 

 Aggregate (애그리거트) 

 애그리거트는 관련된 엔터티와 밸류 객체를 개념적으로 하나로 묶은 것이다. 예를 들어, 주문과 관련된 Order 엔터티, OrderLine 밸류, Orderer밸류 객체를 '주문' 애그리거트로 묶을 수 있다. 

 Repository 

 도메인 모델의 영속서을 처리한다. 예를 들어, DBMS 테이블에서 엔터티 객체를 로딩하거나 저장하는 기능을 제공한다. 

 Domain Service 

 특정 엔티티에 속하지 않은 도메인 로직을 제공한다 '할인 금액 계산'은 상품, 쿠폰, 회원 등급, 구매 금애 등 다양한 조건을 이용해서 구현하게 되는데, 이렇게 도메인 로직이 여러 엔티티와 밸류를 필요한 경우 도메인 서비스에서 로직을 구현한다. 




3. 애그리거트


 복잡한 도메인을 이해하고 관리하기 쉬운 단위로 만들려면 상위 수준에서 모델을 조망할 수 있는 방법이 필요한데, 그 방법이 바로 애그리거트이다. 애그리거트는 관련된 모델을 하나로 모은 것이기 때문에 한 애그리거트에 속한 객체는 유사하거나 동일한 라이프사이클을 갖는다.


4. 리포지터리와 모델 구현(JPA 중심)


엔티티와 밸류 기본 매핑 구현

  • 애그리거트 루트는 엔티티이므로 @Entity로 매핑 설정한다.
  • 한 테이블에 엔티티와 밸류 데이터가 같이 있다면,
    • 밸류는 @Embeddable로 매핑 설정한다.
    • 밸류 타입 프로퍼티는 @Embedded로 매핑 설정한다.
  • AttributeConverter를 이용한 밸류 매칭 처리.(115p)
  • 밸류 컬랙션을 별도 테이블로 매핑할 때
    • @ElementCollection과 @CollectionTable을 함께 사용한다. (120p)

※ JPA에서 식별자 타입은 Serializable 타입이어야 하므로 식별자로 사용될 밸류 타입은 Serializable 인터페이스를 상속받아야 한다.




5. 리포지터리의 조회 기능(JPA 중심)


 기본적으로 아래와 같은 스펙(Specification) 인터페이스를 구현하여, 다양한 조합을 만들 수 있다.

public interface Specification<T> {

public boolean isSatisfiedBy(T agg);

}

(143 page)


JPA를 위한 스펙(Specification)은 CriteriaBuilder와 Predicate를 이용해서 검색 조건을 구현해야 하는데 다음의 인터페이스를 구현해야 한다.

public interface Specification<T> {

public boolean toPredicate(Root<T> root, CriteriaBuilder cb);

}

(147 page)


위와 같은 스펙(Specification) 인터페이스를 구현한 스펙들을 사용할 수 있도록 리포지터리(Repository)를 구현해야 하며, 관련 인터페이스는 다음과 같다.

public interface OrderRepository {

public List<Order> findAll(Specification<Order> spec);

}

(153 page)



조회 전용 기능 구현

 

리포지터리는 애그리거트의 저장소를 표현하는 것으로서 다음 용도로 리포지터리를 사용하는 것은 적합하지 않다.

  • 여러 애그리거트를 조합해서 한 화면에 보여주는 데이터 제공
  • 각종 통계 데이터 제공

JPA와 하이버네이트를 사용하면 동적 인스턴스 생성, 하이버네이트의 @Subselect 확장 기능, 네이티브 쿼리를 이용해서 조회 전용 기능을 구현할 수 있다.

  • 동적 인스턴스 생성 : TypedQuery 사용 시 query 내의 문자열에 new 키워드와 생성할 인스턴스의 완전한 클래스 이름을 지정하고 사용함
  • @Subselect : 쿼리 결과를 @Entity로 매핑할 수 있는 유용한 기능으로 쿼리 실행 결과를 매핑할 테이블처럼 사용한다.



6. 응용 서비스와 표현 영역

사용자에게 기능을 제공하려면 도메인과 사용자를 연결해 줄 표현 영역과 응용 영역이 필요하다.

  • 표현영역은 사용자의 요청을 해석한다.(URL, 요청 파라미터, 쿠키, 헤더 등)
  • 응용서비스는 기능을 실행하는데 필요한 입력값을 메서드 파라미터로 전달받고 실행 결과를 반환한다.


응용서비스의 역할

  • 도메인 객체간의 흐름을 제어한다.
  • 도메인의 상태 변경을 트랜잭션으로 처리해야 한다.

한 응용 서비스 클래스의 역할이 많아지고 크기가 커지는 것 보다 클래스가 많아지더라도 가능한 구분되는 기능별로 구분하는 것이 낫다.(178-179 page) 그리고 인터페이스가 명확하게 필요하기 전까지는 응용서비스에 대한 인터페이스를 작성하는 것이 좋은 설계는 아니다.(181page)



표현영역

  • 사용자가 시스템을 사용할 수 있는 (화면)흐름을 제공하고 제어한다.
  • 사용자의 요청을 알맞은 응용 서비스에 전달하고 결과를 사용자에게 제공한다.
  • 사용자의 세션을 관리한다.

값 검증, 권한검사, ...

조회전용기능은 11장 CQRS에서...




7. 도메인 서비스


 도메인 영역의 코드를 작성하다 보면 한 애그리거트로 기능을 구현할 수 없을 때가 있다. 억지로 한 애그리거트에 넣기 보다는 도메인 서비스를 활용해서 도메인 개념을 명시적으로 드러내면 된다. 도메인 서비스를 구현하는 데 필요한 상태는 애그리거트나 다른 방법으로 전달받는다. 트랜잭션 처리와 같은 로직은 응용 로직이므로 도메인 서비스가 아닌 응용 서비스에서 처리해야 한다. 일반적으로 도메인 서비스의 패키지 위치는 다른 도메인 요소와 같이 위치하고, 외부 의존적인 경우에 따라 인터페이스로 추상화해야 한다.




8. 애그리거트 트랜잭션 관리


  두 스레드가 같은 DBMS를 사용하는 하나의 애그리거트를 사용할 경우 먼저 조회 및 변경을하는 스레드가 있을 때 다른 스레드의 접근을 막거나 다른 스레드의 변경이 있을 경우 다시 조회 후 수정이 이루어지도록 해야한다. 이런 상황은 트랜잭션과 관련이 있는데 Pessimistic-Lock(선점잠금)과 Optimistic-Lock(비선점잠금)을 이해해하고 잘 쓸 수 있어야 한다.


  • Pessimistic-Lock(선점잠금)
    • 공유데이터에 대한 동기화라고 보면 된다.
  • Optimistic-Lock(비선점잠금)
    • version 속성을 활용하여 데이터에 대한 변경을 제어한다.



9. 도메인 모델과 BOUNDED CONTEXT


 모델은 특정한 컨텍스트(문맥)하에서 완전한 의미를 갖는다. 같은 제품이라도 카탈로그 컨텍스트와 재고 컨텍스트에서 의미가 다르다. 이렇게 구분되는 경계를 갖는 컨텍스트를 DDD에서는 BOUNDED CONTEXT라고 부른다. BOUNDED CONTEXT는 모델의 경계를 결정하며 한 개의 BOUNDED CONTEXT는 논리적으로 한 개의 모델을 갖는다. BOUNDED CONTEXT는 용어를 기준으로 구분한다.


 BOUNDED CONTEXT는 도메인 모델뿐만 아니라 도메인 기능을 사용자에게 제공하는데 필요한 표현영역, 응용서비스, 인프라영역 등을 모두 포함한다. 단순한 서비스-DAO로 구성된 CRUD 방식을 사용하거나 CQRS패턴과 같은 형태로 구현할 수도 있다.


 BOUNDED CONTEXT는 하나의 마이크로서비스가 될 수 있으며, 이러한 BOUNDED CONTEXT 사이의 연결은 REST-API나 메시지큐를 통해서 처리할 수 있다.




10. 이벤트


 도메인 객체에 서로 다른 도메인 로직이 섞이면 트랜잭션이나 성능 그리고 설계에 문제를 일으킬 수 있다. 서로 다른 도메인 로직을 이벤트 처리를 통해 DeCoupling하여 한 기능을 하는 로직의 cohesion을 높이고 성능이 향상되도록 한다. 


이벤트 처리 지원 방법

  • 이벤트, 핸들러, 디스패처로 방식과 스프링 AOP를 통해서 구현할 수 있다.
  • REST-API로 처리되도록 구현한다.
  • Scheduler처럼 Forwarder를 구현한다.



11. CQRS (Command and Query Responsibility Segregation)

 단일 모델에서의 조회문제로 인하여 상태를 변경하는 명령(Command)을 위한 모델과 상태를 제공하는 조회(Query)를 위한 모델을 분리하는 패턴이다. 서비스 상황에 맞게 조회의 성능을 위해  MyBatis를 사용하거나 메모리 기반의 NoSQL을 사용할 수도 있다.







※ 인터넷 DDD 자료들

- DDD 관련 지디넷 게시글들





반응형

+ Recent posts