2carrot84
by 2carrot84
11 min read

Categories

  • book

Tags

  • DDD
  • Domain Driven Design
  • 도메인 주도 개발 시작하기

지난번 포스팅에 이어 최범균님의 도메인 주도 개발 시작하기 책 챕터 3,4 에 대해 정리한 내용을 포스팅 하려고 합니다.

chapter 3. 애그리거트
chapter 4. 리포지터리와 모델 구현

지난번 포스팅은 아래 링크에서 보실 수 있습니다.

[책] 도메인 주도 개발 시작하기 - Chapter 1,2

chapter 3. 애그리거트

3.1 애그리거트

  • 상위 수준 개념을 이용해서 전체 모델을 정리하면 전반적인 관계를 이해하는데 도움이 된다.
  • 도메인 객체 모델이 복잡해지면 개별 구성요소 위주로 모델을 이해하게 되고 전반적인 구조와 큰 수준에서 도메인간의 관계를 파악하기 어려워진다.
  • 주요 도메인 요소 간의 관계를 파악하기 어렵다는 것은 코드를 변경하고 확장하는 것이 어려워 진다는 것을 의미한다.
  • 복잡한 도메인을 이해하고 관리하기 쉬운 단위로 만들려면 상위수준에서 모델을 조망할 수 있는 방법이 필요
    • 애그리거트 : 관련된 객체를 하나의 군으로 묶어 준다.
      • 상위 수준에서 도메인 모델 간의 관계를 파악
  • 애그리거트는 일관성을 관리하는 기준도 된다.
    • 모델을 보다 잘 이해할 수 있고, 애그리거트 단위로 일관성을 관리
      • 복잡한 도메인을 단순한 구조로 만들어준다.
      • 도메인 기능 확장/변경하는데 필요한 노력도 줄어든다.
  • 한 애그리거트에 속한 객체는 유사하거나 동일한 라이프 사이클을 갖는다.
  • 한 애그리거트에 속한 객체는 다른 애그리거트에 속하지 않는다.
    • 경계를 설정할 때 기본이 되는 것은 도메인 규칙과 요구사항이다.

3.2 애그리거트 루트

  • 애그리거트는 여러 객체로 구성되기 때문에 한 객체만 상태가 정상이면 안된다.
    • 도메인 규칙을 지키려면 애그리거트에 속한 모든 객체가 정상 상태를 가져야 한다.
      • 애그리거트 전체를 관리할 주체가 필요 (애그리겉의 루트 엔티티)

        3.2.1 도메인 규칙과 일관성

  • 애그리거트 루트의 핵심 역활은 애그리거트의 일관성이 깨지지 않도록 하는 것
    • 애그리거트가 제공해야 할 도메인 기능을 구현
  • 애그리거트 외부에서 애그리거트에 속한 객체를 직접 변경하면 안 된다.
    • 논리적인 데이터 일관성이 깨지게 되는 것
  • 불필요한 중복을 피하고 애그리거트 루트를 통해서만 도메인 로직을 구현하게 만드려면
    • 단순히 필드를 변경하는 set 메서드를 public 범위로 만들지 않는다.
      • 일관성이 깨질 가능성이 줄어든다.
      • 의미가 드러나는 매서드를 사용해서 구현할 가능성이 높아진다.
    • 밸류 타입은 불변으로 구현
      • 밸류 객체가 불변이면 값을 변경하는 방법은 새로운 밸류 객체를 할당하는 것 뿐

3.2.2 애그리거트 루트의 기능 구현

  • 애그리거트 루트는 애그리거트 내부의 다른 객체를 조합해서 기능을 완성
    • 애그리거트 루트가 구성요소의 상태만 참조하는 것이 아니라, 기능 실행을 위임하기도 한다.

3.2.3 트랜잭션 범위

  • 트랜잭션 범위는 작을 수록 좋다.
    • 성능(처리량)도 트랜잭션이 작을 수록 좋다.
  • 한 트랜잭션에서는 한 개의 애그리거트만 수정해야 한다.
    • 트랜잭션 충돌 발생 가능성이 낮아짐
    • 애그리거트 내부에서 다른 애그리거트의 상태를 변경하는 기능을 실행하면 안된다.
      • 자신의 책임 범위를 넘어 다른 애그리거트의 상태까지 관리하는 꼴
      • 애그리거트는 최대한 서로 독립적이어야 한다.
    • 부득이하게 한 트랜잭션으로 두개 이상의 애그리거트를 수정해야 한다면 응용 서비스에서 두 애그리거트를 수정하도록 구현
  • 다음 경우에는 한 트랜잭션에서 두개 이상의 애그리거트를 변경하는 것을 고려
    • 팀 표준 : 사용자 유스케이스와 관련된 응용 서비스의 기능을 한 트랜잭션으로 실행
    • 기술 제약 : 기술적으로 이벤트 방식을 도입할 수 없는 경우 일관성을 처리
    • UI 구현의 편리 : 운영자의 편리함을 위함 경우

3.3 리포지터리와 애그리거트

  • 객체의 영속성을 처리하는 리포지터리는 애그리거트 단위로 존재
  • 어떤 기술을 이요해서 리포지터리를 구현하느냐에 따라 애그리거트의 구현도 영향 받는다.
    • JPA 사용시 데이터베이스 관계형 모델에 객체 도메인 모델을 맞춰야 할 때도 있다.
    • 레거시 DB or DB 설계 표준을 따라야 한다면 DB 테이블 구조에 맞게 모델을 변경
  • 애그리거트 개념적으로 하나이므로 리포지터리는 애그리거트 전체를 저장소에 영속화 해야한다.

3.4 ID를 이용한 애그리거트 참조

  • 애그리거트도 다른 애그리거트를 참조 (다른 애그리거트의 루트 참조)
    • 애그리거트 간의 참조는 필드를 통해 쉽게 구현
  • ORM 기술 덕에 애그리거트 루트에 대한 참조를 쉽게 구현할 수 있고 다른 애그리거트의 데이터를 쉽게 조회할 수 있다.
    • 필드를 이용한 애그리거트 참조는 다음 문제를 야기
      • 편한 탐색 오용
        • 다른 애그리거트의 상태를 쉽게 변경 가능
      • 성능에 대한 고민
        • JPA 를 사용하면 참조한 객체를 지연로딩과 즉시로딩의 두가지 방식으로 로딩할 수 있다.
      • 확장 어려움
        • 초기에는 단일 서버에 단일 DBMS 로 서비스 제공 > 부하 분산을 위해 하위 도메인별로 시스템을 분리
      • 이런 세가지 문제를 완화할 때 사용할 수 있는 것이 ID를 이용해서 다른 애그리거트를 참조하는 것
  • ID 참조를 사용하면 모든 객체가 참조로 연결되지 않고 한 애그리거트에 속한 객체들만 참조로 연결된다.
    • 애그리거트의 경계를 명확히 하고 애그리거트간 물리적인 연결을 제거
      • 모델의 복잡도를 낮춰주고, 애그리거트간 의존을 제거하므로 응집도를 높여준다.
    • 구현 복잡도도 낮아진다.
      • 애그리거트간 참조를 지연 로딩으로 할지 즉시 로딩으로 할지 고민하지 않아도 된다.
    • 애그리거트별로 다른 구현 기술을 사용하는 것도 가능

      3.4.1 ID를 이용한 참조와 조회 성능

  • 다른 애그리거트를 ID로 참조하면 참조하는 여러 애그리거트를 읽을 때 조회 속도가 문제 될 수 있다.
    • 한 DBMS 에 데이터가 있다면 조인을 이용해서 한 번에 모든 데이터를 가져올 수 있음에도 불구하고 주문마다 상품 정보를 읽어오는 쿼리를 실행하게 된다.
  • ID를 이용한 애그리거트 참조는 지연 로딩과 같은 효과를 만드는 데 지연로딩과 관련된 대표적인 문제가 N+1 조회 문제이다.
    • 조회 대상이 N개 일때 N개를 읽어오는 한 번의 쿼리와 연관된 데이터를 읽어오는 쿼리를 N번 실행한다. (N+1 조회 문제)
    • ID 참조 방식을 사용하면서 N+1 조회와 같은 문제가 발생하지 않도록 하려면 조회 전용 쿼리를 사용하면 된다.
      • 데이터 조회를 위한 별도 DAO 를 만들고 DAO 의 조회 메서드에서 조인을 이용해 한번의 쿼리로 필요한 데이터를 로딩하면 된다.
      • 애그리거트마다 서로 다른 저장소를 사용하면 한 번의 쿼리로 관련 애그리거트를 조회할 수 없다.
        • 이때 캐시를 적용하거나 조회 전용 저장소를 따로 구성하여 성능을 높일 수 있다.

3.5 애그리거트 간 집합 연관

  • 애그리거트 간 1-N 관계는 Set 과 같은 컬렉션을 이용해서 표현할 수 있다.
    • 개념적으로 애그리거트 간에 1-N 연관이 있더라도 성능 문제 때문에 애그리거트 간의 1-N 연관을 실제 구현에 반영하지 않는다.
  • M-N 연관을 개념적으로 양쪽 애그리거트에 컬렉션으로 연관을 만든다.
    • 앞서 1-N 연관처럼 M-N 연관도 실제 요구사항을 고려하여 M-N 연관을 구현에 포함시킬지를 결정해야 한다.

3.6 애그리거트를 팩토리로 사용하기

  • Product 를 생성 가능한지 판단하는 코드와 Product 를 생성하는 코드가 분리
    • 중요한 도메인 로직 처리가 응용 서비스에 노출
      • Store 가 Product 를 생성할 수 있는지를 판단하고 Product 를 생성하는 것은 논리적으로 하나의 도메인 기능
    • Store 애그리거트에 Product 를 생성하는 기능을 구현 (팩토리 역활 + 도메인 로직 구현)
      • 응용 서비스에서 더 이상 Store 의 상태를 확인하지 않는다. (도메인 응집도 향상)
  • 애그리거트가 갖고 있는 데이터를 이용해서 다른 애그리거트를 생성해야 한다면 애그리거트에 팩토리 메소드를 구현하는 것을 고려해보자
    • Product 의 경우 제품을 생성한 Store 의 식별자를 필요
      • Store 데이터를 이용해서 Product 를 생성. Store 상태를 이용해서 Product 생성할 수 있는 조건을 판단
      • Store 에 Product 를 생성하는 팩토리 메서드를 추가하면 Product 를 생성할 때 필요한 데이터의 일부를 직접 제공 + 중요한 도메인 로직을 함께 구현
      • Store 애그리거트가 Product 애그리거트를 생성할 때 많은 정보를 알아야 한다면 다른 팩토리에 위임하는 방법도 있다.

chapter 4. 리포지터리와 모델 구현

4.1 JPA 를 이용한 리포지터리 구현

4.1.1 모듈 위치

  • 리포지터리 인터페이스는 애그리거트와 같이 도메인 영역에 속하고, 리포지터리를 구현한 클래스는 인프라 스트럭처 영역에 속한다.
    • 인프라 스트럭처에 대한 의존을 낮춰야 한다.

4.1.2 리포지터리 기능 구현

  • 리포지터리가 제공하는 기본기능은 다음 두 가지
    • ID로 애그리거트 조회하기
    • 애그리거트 저장하기
  • 인터페이스는 애그리거트 루트를 기준으로 작성한다.
  • 애그리거트를 조회하는 기능의 이름으로 널리 사용되는 규칙은 ‘findBy프러퍼티 명(프러퍼티 값)’ 형식을 사용
    • ID에 해당하는 애그리거트가 존재하면 Order 를 리턴하고 존재하지 않으면 null or Optional 을 리턴
  • save() 메서드는 전달받은 애그리거트를 저장한다.
    • 이 인터페이스를 구현한 클래스는 JPA 의 EntityManager 를 이용해서 기능을 구현
  • 애그리거트를 수정한 결과를 저장소에 반영하는 메서드를 추가할 필요는 없다.
    • 트랜잭션 범위에서 변경한 데이터를 자동으로 DB 에 반영
  • ID가 아닌 다른 조건으로 애그리거트를 조회할때는 findBy 뒤에 조건 대상이 되는 프러퍼티 이름을 붙힌다.
    • ID 외에 다른 조건으로 애그리거트를 조회할 때에는 JPA 의 Criteria 나 JPQL 을 사용
  • 애그리거트를 삭제하는 기능이 필요할 수도 있다.
    • EntityManager 의 remove() 메서드를 이용해서 삭제기능을 구현

4.2 스프링 데이터 JPA 를 이용한 리포지터리 구현

  • 스프링 데이터 JPA 는 지정한 규칙에 맞게 리포지터리 인터페이스를 정의하면 리포지터리를 구현한 객체를 알아서 만들어 스프링 빈으로 등록
  • 스프링 데이터 JPA 는 다음 규칙에 따라 작성한 인터페이스를 찾아서 인터페이스를 구현한 스프링빈 객체를 자동으로 등록
    • org.springframework.data.repository.Repository<T, ID> 인터페이스 상속
    • T 는 엔티티 타입을 지정하고 ID 는 식별자 타입을 지정
  • 스프링 데이터 JPA 를 사용하려면 지정한 규칙에 맞게 매서드를 작성해야 한다.
    • 엔티티를 저장하는 메서드
      • Order save(Order entity)
      • void save(Order entity)
    • 식별자를 이용해서 엔티티를 조회할 때 findById() 메서드를 사용
      • Order findById(OrderNo id)
      • Optional findById(OrderNo id)
    • 특정 프러퍼티를 이용해서 조회할 때는 findBy 프러퍼티 이름 (프러퍼티 값) 형식의 메서드를 사용
      • List findByOrderer(Orderer orderer)
    • 중첩 프러퍼티도 가능
      • List findByOrdererMemberId(MemberId memberId)
    • 엔티티를 삭제하는 메서드는 다음 두 형태를 갖는다
      • void delete(Order order)
      • void deleteById(OrderNo id)

4.3 매핑구현

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

  • 애그리거트와 JPA 매핑을 위한 기본규칙은 다음과 같다.
    • 애그리거트 루트는 엔티티이므로 @Entity 로 매핑 설정한다.
  • 한 테이블에 엔티티와 밸류 데이터가 같이 있다면
    • 밸류는 @Embeddable 로 매핑 설정
    • 밸류 타입 프러퍼티는 @Embedded 로 매핑 설정

4.3.2 기본 생성자

  • 엔티티와 밸류의 생성자는 객체를 생성할 때 필요한 것을 전달 받는다.
    • Receiver 가 불변타입이면 set 메서드, 기본 생성자(파라미터가 없는)가 필요 없다.
  • JPA 에서 @Entity 와 @Embeddable 로 클래스를 매핑하려면 기본 생성자를 제공 해야 한다.
    • DB 에서 데이터를 읽어와 매핑된 객체를 생성할 때 기본 생성자 사용
    • 기본 생성자는 JPA Provider 가 객체를 생성할 때만 사용
      • 다른 코드에서 기본 생성자를 사용하지 못하도록 protected 로 선언

4.3.3 필드 접근 방식 사용

  • JPA 는 필드와 메서드의 두 가지 방식으로 매핑을 처리할 수 있다.
  • 메서드 방식을 사용하려면 프러퍼티를 위한 get/set 메서드를 구현
    • 도메인의 의도가 사라짐
    • 객체가 아닌 데이터 기반으로 엔티티를 구현할 가능성이 높아진다.
    • set 메서드는 내부 데이터를 외부에서 변겨할 수 있는 수단
      • 캡슐화를 깨는 원인이 될 수 있다.
  • 엔티티가 객체로서 제 역활을 하려면 외부에 set 메서드 대신 의도가 잘 드러나는 기능을 제공해야 한다.
  • 객체가 제공할 기능 중심으로 엔티티를 구현하게끔 유도하려면 JPA 매핑 처리를 필드 방식으로 서택해서 불필요한 get/set 메서드를 구현하지 말아야 한다.

4.3.4 AttributeConverter 를 이용한 밸류 매핑 처리

  • int, long, String, LocalDate 와 같은 타입은 DB 테이블의 한개 컬럼에 매핑된다.
  • 밸류 타입의 프러퍼티를 한개 컬럼에 매핑해야 할 때도 있다.
    • 두개 이상의 프러퍼티를 가진 밸류 타입을 한 개 컬럼에 매핑하려면 @Embeddable 처리할 수 없다.
      • 이럴 때 사용할 수 있는 것이 AttributeConverter 이다.
  • AttributeConverter 는 밸류 타입과 칼럼 데이터간의 변환을 처리하기 위한 기능을 정의하고 있다.

4.3.5 밸류 컬렉션 : 별도 테이블 매핑

  • Order 엔티티는 한 개 이상의 OrderLine 을 가질 수 있다. OrderLine 에 순서가 있다면 List 타입을 이용해서 컬렉션을 프러퍼티로 지정할 수 있다.
    • 밸류 컬렉션을 별도 테이블로 매핑할 때는 @ElementCollection 과 @CollectionTable 을 함께 사용한다.
      • @OrderColum 은 순서를 위한 인덱스 값을 저장

4.3.6 밸류 컬렉션 : 한 개 칼럼 매핑

  • 밸류 컬렉션을 별도 테이블이 아닌 한 개 칼람에 저장해야 할 때도 있다.
    • AttributeConverter 를 사용하면 밸류 컬렉션을 한 개 칼람에 쉽게 매핑할 수 있다.
      • AttributeConverter 를 사용하려면 밸류 컬렉션을 표현하는 새로운 밸류 타입을 추가 해야 한다.

4.3.7 밸류를 이용한 ID 매핑

  • 식별자라는 의미를 부각시키기 위해 식별자 자체를 밸류 타입으로 만들 수 있다.
    • @Id 대신 @EmbeddedId 애너테이션을 사용
      • JPA 에서 식별자 타입은 Serializable 타입이어야 한다.
  • 밸류 타입으로 식별자를 구현할 때 장점
    • 식별자에 기능을 추가할 수 있다.

4.3.8 별도 테이블에 저장하는 밸류 매핑

  • 애그리거트에서 루트 엔티티를 뺀 나머지 구성요소는 대부분 밸류이다.
    • 단지 별도 테이블에 데이터를 저장한다고 해서 엔티티 인것은 아니다.
  • 밸류가 아니라 엔티티가 확실하다면 해당 엔티티가 다른 애그리거트는 아닌지 확인해야 한다.
    • 독자적인 라이프 사이클을 갖는다면 구분되는 애그리거트일 가능성이 높다.
      • ex. 상품상세의 고객의 리뷰
  • 애그리거트에 속한 객체가 밸류인지 엔티티인지 구분하는 방법은 고유 식별자를 갖는지를 확인하는 것이다.
    • 매핑되는 테이블의 식별자를 애그리거트 구성요소의 식별자와 동일한 것으로 착각하면 안된다.
    • @SecondaryTable 의 name 속성은 밸류를 저장할 테이블을 지정
      • pkJoinColumns 속성은 밸류 테이블에서 엔티티 테이블을 조인할 때 사용할 컬럼을 지정
    • @AttributeOverride 를 적용했는데 해당 밸류 데이터가 저장된 테이블 이름을 지정

4.3.9 밸류 컬랙션을 @Entity 로 매핑하기

  • 개념적으로 밸류인데 구현 기술의 한계나 팀 표준 때문에 @Entity 를 사용해야 할 때도 있다.
  • JPA 는 @Embeddable 타입의 클래스 상속 매핑을 지원하지 않는다.
    • 상속구조를 갖는 밸류 타입을 사용하려면 @Entity 를 이용해서 상속 매핑으로 처리해야 한다.
  • 한 테이블에 Image 와 그 하위 클래스를 매핑하므로 Image 클래스에 다음 설정을 사용한다.
    • @Inheritance 애너테이션 적용
    • strategy 값으로 SINGLE_TABLE 사용
    • @DiscriminatorColumn 애너테이션을 이용하여 타입 구분용으로 사용할 칼럼 지정
      • Image 를 상속받은 클래스는 @Entity 와 @Discriminator 를 사용해서 매핑을 설정한다.
  • Image 가 Entity 이므로 Product 는 @OneToMany 를 이용해서 매핑을 처리한다.
  • @Entity 에 대한 @OneToMany 매핑에서 컬랙션의 clear() 메서드를 호출하면 삭제과정이 효율적이지 않다.
    • 애그리거트의 특성을 유지하면서 이 문제를 하려면 결국 상속을 포기하고 @Embeddable 매핑된 단일 클래스로 구현해야 한다.
    • 코드 유지보수와 성능의 두가지 측면을 고려해서 구현 방식을 선택해야 한다.

4.3.10 ID 참조와 조인 테이블을 이용한 단방향 M-N 매핑

  • 요구사항을 구현하는데 집합 연관을 사용하는 것이 유리하다면 ID 참조를 이용한 단방향 집합 연관을 적용해 볼 수 있다.
    • 집합의 값에 밸류 대신 연관을 맺는 식별자가 온다.
      • @ElementCollection 을 이용하기 때문에 Product 를 삭제할 때 매핑에 사용한 조인 테이블의 데이터도 함께 삭제된다.

4.4 애그리거트 로딩 전략

  • JPA 매핑을 설정할 때 항상 기억해야 할 점은 애그리거트에 속한 객체가 모두 모여야 완전한 하나가 된다는 것이다.
    • 애그리거트 루트를 로딩하면 루트에 속한 모든 객체가 완전한 상태여야 한다.
      • 연관 매핑의 조회 방식을 죽시 로딩 (FetchType.EAGER) 으로 설정하면 된다.
  • 컬랙션에 대해 로딩 전략을 FetchType.EAGER 로 설정하면 오히려 즉시로딩 방식이 문제가 될 수 있다.
    • 이 매핑을 사용할 때 Product 를 조회하면 Image, Option 을 위한 테이블을 조인한 쿼리를 실행한다.
      • 이 쿼리는 카타시안 조인을 사용하고 중복을 발생시킨다.
      • 실제 메모리에는 중복 데이터가 알맞게 제거해서 변환된다.
      • 애그리거트가 커지면 문제가 될 수 있다.
  • 애그리거트가 완전해야 하는 이유
    • 상태를 변경하는 기능을 실행할 때 애그리거트 상태가 완전해야 한다.
    • 표현 영역에서 애그리거트의 상태 정보를 보여줄 때 필요
      • 별도의 조회 전용 기능과 모델을 구현하는 방식이 더 유리
    • JPA 는 트랜잭션 범위 내에서 지연 로딩을 허용하기 때문에 실제로 상태를 변경하는 시점에 필요한 구성요소만 로딩해도 문제가 되지 않는다.
  • 애그리거트 내의 모든 연관을 즉시 로딩으로 설정할 필요는 없다.
    • 지연로딩은 동작 방식이 항상 동일하기 때문에 즉시 로딩처럼 경우의 수를 따질 필요가 없다.
      • 즉시로딩보다 쿼리 실행 횟수가 많아질 가능성이 더 높다.

4.5 애그리거트의 영속성 전파

  • 애그리거트가 완전한 상태여야 한다는 것은 저장하고 삭제할 때도 하나로 처리해야 함을 의미한다.
    • @Embeddable 매핑 타입은 함께 저장, 삭제되므로 cascade 속성을 설정하지 않아도 된다.
    • 애그리거트에 속한 @Entity 타입에 대한 매핑은 cascade 속성 설정이 필요하다.
    • @OneToOne, @OneToMany 는 cascade 속성의 기본값이 없으므로 cascadeType.PERSIST, cascadeType.REMOVE 를 설정한다.

4.6 식별자 생성 기능

  • 실별자는 세가지 방식 중 하나로 생성한다.
    • 사용자가 직접 생성
    • 도메인 로직으로 생성
    • DB 를 이용한 일련번호 사용
  • 식별자 생성 규칙이 있다면 엔티티를 생성할 때 식별자를 엔티티가 별도 서비스로 식별자 생성 기능을 분리해야 한다.
    • 식별자 생성 규칙은 도메인 규칙이므로 도메인 영역에 위치시켜야 한다.
  • 식별자 생성 규칙을 구현하기에 적합한 또 다른 장소는 리포지터리이다.
    • DB 자동 증가 칼럼을 식별자로 사용하면 식별자 매핑에서 @GeneratedValue 를 사용한다.

4.7 도메인 구현과 DIP

  • DIP 에 따르면 @Entity, @Table 은 구현 기술에 속하므로 도메인 모델은 구현 기술인 JPA 에 의존하지 말아야 하는데 이 코드는 도메인 모델이 영속성 구현 기술인 JPA 에 의존하고 있다.
  • ArticleRepository 인터페이스는 도메인 패키지에 위치하는데 구현 기술인 스프링 데이터 JPA 의 Repository 인터페이스를 상속한다.
    • 도메인이 인프라에 의존
  • DIP 를 적용하는 주된 이유는 저수준 구현이 변경되더라도 고수준이 영향을 받지 않도록 하기 위함이다.
    • 리포지터리와 도메인 모델의 구현 기술은 거의 바뀌지 않는다.??
  • DIP 를 완벽하게 지키면 좋겠지만 개발 편의성과 실용성을 가져가면서 구조적인 유연함을 어느정도 유지하는게 좋다.

마무리

그럼 이만. 🥕👋🏼🖐🏼

참고자료🤣

도메인 주도 개발 시작하기