🖥️ Back-end

DDD (Domain Driven Design) : 도메인 주도 설계 도전 1일차

승승장규 2025. 3. 13. 22:39

간단하게 복잡한 소프트웨어 시스템을 개발할 때, 비즈니스 도메인(업무 영역)을 중심으로 설계하고 구현하는 접근 방식이라고 이해를 해보겠습니다.

 

  • 도메인 : 시스템이 해결하고자 하는 비즈니스 문제의 영역 ex) 쇼핑몰, 금융 시스템...
  • 엔티티 : 고유한 식별자를 가진 객체 ex) User 객체, Order 객체...
  • 값 객체 : 고유한 식별자가 필요 없는 객체. 주로 그 자체에 속성 값이 중요하다.  ex) 주소를 나타내는 Address 객체
  • 애그리게이트 : 관련 엔티티와 값 객체의 묶음. 한 개의 애그리게이트 루트가 전체 애그리게이트를 대표하고, 이 루트를 통해서만 애그리게이트에 접근할 수 있다. ex) 루트 = Order 객체, 애그리게이트 = OrderItem 객체...
  • 레포지토리 : 엔티티의 영속성을 관리하는 객체로 주로 데이터베이스와 통신을 담당.
  • 서비스 : 도메인 로직과 비즈니스 맥락을 캡슐화한 객체로 엔티티가 할 수 없는 복잡한 로직을 처리
  • 도메인 이벤트 : 비즈니스 도메인 내에서 발생하는 중요한 사건으로 다른 부분에서 상태 변화를 알려준다. ex) 생성자를 통한 값의 변경...

DDD 설계를 할 때 지켜야 할 사항들

  • 유비쿼터스 언어 : 개발자, 비즈니스 전문가가 공유하는 언어를 사용하여 소통하고, 코드에 반영하여 도메인 모델을 작성.
  • 애그리게이트 루트 : 애그리게이트는 한 개의 애그리게이트 루트를 통해서만 접근하도록 설계해서 애그리게이트 내의 불변성과 일관성을 보장해야 함.
  • 도메인 이벤트를 활용 : 도메인 내에서 발생하는 중요한 사건을 추적하고 다른 구성 요소에 알리기 위해 이벤트를 사용. ex) 이벤트 기반 아키텍처를 활용해서 시스템 간 느슨한 결합을 유지
  • 모듈화 : 도메인을 모듈로 나누어 각 모듈이 독립적인 비즈니스 기능을 담당하게 설계해서 각 모듈 간에 최소한의 의존성을 유지함.
  • 레포지토리를 통한 영속성 관리 : 도메인 모델이 infrastructure 기술에 직접 의존하지 않아야 한다. 도메인 계층은 순수하게 비즈니스 로직을 나타내며, 데이터 저장소와 같은 외부 환경과 독립적으로 설계해야 하는데, 이를 구현하기 위해 infrastructure 계층에 구체적인 저장소 구현을 두고, 도메인 계층에는 해당 저장소 인터페이스만 제공하는 방식으로 설계를 해야 한다.
  • 도메인 서비스 : 복잡한 비즈니스 로직을 도메인 모델 외부에서 처리.
  • 애플리케이션 서비스 : 여러 엔티티를 조합하여 비즈니스 유스케이스를 처리.

 

com.example.myapp
├── entity
│      ├── Order.java
├── repository
│      ├── OrderRepository.java
├── service
|      ├── OrderService.java
├── controller
       ├── OrderController.java

 

기존에 사용하던 3 layer Architecture의 구조와 비교해 보면 굉장히 어렵다는 느낌을 많이 받았고, 지금도 계속 이해를 하기 위해 노력하는 중입니다.

com.example.myapp
├── application
│   ├── service
│   │   ├── OrderService.java
│   │   ├── UserService.java
│   │   └── OrderMessageService.java
│   ├── dto
│   │   └── OrderDTO.java
├── domain
│   ├── model
│   │   ├── Order.java
│   │   ├── Product.java
│   │   └── ValueObject.java
│   ├── repository
│   │   └── OrderRepository.java
│   └── service
│       └── OrderDomainService.java
├── infrastructure
│   ├── repository
│   │   ├── JpaOrderRepository.java
│   │   ├── OrderRepositoryImpl.java
│   │   └── OrderQueryDSLRepositoryImpl.java
│   ├── client
│   │   └── UserClient.java
│   ├── configuration
│   │   └── DatabaseConfig.java
│   └── messaging
│       ├── OrderMessageConsumer.java
│       └── OrderMessageProducer.java
│
└── presentation
    ├── controller
    │   └── OrderController.java
    └── request
        └── OrderRequest.java

 

 

  • Presentation Layer : 사용자와 직접 상호작용 하는 계층. 주로 컨트롤러가 위치하며, 사용자로부터 요청을 받아 응답을 반환한다.
    • Controller : HTTP 요청을 받아 비즈니스 로직을 수행하는 서비스 계층에 요청을 전달하고 반환.
    • DTO : 계층 간의 데이터 전송을 위한 객체.
  • Domain Layer : 시스템의 핵심 비즈니스 로직을 담당하며 도메인 모델이 포함된다. 엔티티, 값 객체, 애그리게이트 등이 속한다.
    • Entity : 고유 식별자를 가진 비즈니스 객체.
    • Value Object : 고유 식별자가 없는 객체. 주로 불변 객체이며, 특정 속성의 묶음을 표 헌 한다.
    • Repository Interface : 영속성을 관리하는 인터페이스로, 실제 구현체는 Infrastructure Layer에서 구현.
  • Application Layer : 도메인 객체를 이용해 비즈니스 로직을 실행하는 계층. 주로 서비스들이 포함되며, 도메인 계층을 이용해 유스케이스를 처리한다.
    • Service : 비즈니스를 구현하며, 여러 도메인 객체를 조합하여 비즈니스 로직을 처리.
    • Application Service : 도메인 객체를 이용해 특정 비즈니스 기능을 수행하는 서비스 로직이 포함.
  • Infrastructure Layer : 하부 시스템과 통신을 담당하는 계층. 데이터베이스, 메시지 큐, 외부 시스템과의 연동 등을 처리한다. 영속성, 외부 API 연동 등이 관리된다.
    • Repository Implementation : 도메인 계층에서 정의한 레포지토리 인터페이스의 실제 구현체로, 데이터베이스 액세스를 관리한단.
    • Configuration Classes : 애플리케이션 설정을 포함한다.
    • External Services : 외부 API와의 통신을 담당하는 클래스.

 

3 Layer Architecture과 DDD의 차이점 - 데이터 접근 계층 : 데이터베이스와의 통신을 담당하는 repository

  • 3 Layer는 비즈니스 로직 계층이 데이터 접근 계층에 직접 의존한다. JPA 변경 시 비즈니스 계층까지 영향이 간다. 하지만 DDD는 데이터 접근 기술에 의존하지 않는다. 만약 데이터 접근 기술 변경이 필요하다면 Infrastructure 계층만 수정하면 된다.
  • 3 Layer는 데이터 접근 계층의 변경이 다른 계층에 직접적 영향을 미치지만, DDD는 Domain 계층과 Infrastructure 계층이 명확히 분리되어 있어 각 계층의 변경에 독립적이다.
  • 3 Layer는 비즈니스 로직을 테스트할 때 JpaRepository를 직접 Mocking 해야 하지만, DDD는 Domain repository interface를 Mocking 할 수 있어서 더 쉽게 테스트가 가능하다.
  • 3 Layer는 데이터 접근 기술의 종속성으로 도메인 모델이 독립적이지 못하지만, DDD는 모델이 독립적이고, 데이터 접근 기술 변경 시에도 도메인 모델과 비즈니스 로직에 영향이 없다.

 

 

전자상거래 시스템을 개발한다고 가정해 보자

  1. 사용자들이 상품을 주문할 수 있다.
  2. 데이터를 저장하기 위한 MySQL이 있다.
  3. 시간이 지나고... 데이터베이스를 MongoDB로 변경하고자 한다.

우선 3 Layer 방식으로 개발하는 예시를 작성해 보자

 

  • 주문 요청을 받는 Controller
  • 주문을 처리하는 Service
  • 데이터베이스에 접근하는 repository (JPA 사용)
// 데이터 접근 계층
public interface JpaOrderRepository extends JpaRepository<Order, Long> {}

=================================================================================

// 비즈니스 계층
@Service
/*데이터 접근 기술 (JPA, MySQL)에 직접적으로 의존하고 있으므로, MongoDB로 변경하려면 비즈니스 계층의
 변경해야 한다. 이렇게 되면 변경 범위가 넓어지고, 테스트 및 유지보수가 어려워진다.
*/
public class OrderService {
    @Autowired
    private JpaOrderRepository orderRepository;

    public Order createOrder(Order order) {
        return orderRepository.save(order); // MySQL에 직접 저장
    }
}

=================================================================================

// 프레젠테이션 계층
@RestController
@RequestMapping("/orders")
public class OrderController {
    @Autowired
    private OrderService orderService;

    @PostMapping
    public ResponseEntity<Order> placeOrder(@RequestBody Order order) {
        Order order = orderService.createOrder(order);
        return ResponseEntity.ok(order);
    }
}

 

 

이제 DDD 방식으로 예시를 살펴보자

 

  • Domain 계층 : 핵심 비즈니스 로직과 도메인 모델, 데이터 접근 방식을 정의하는 interface (JPA 등 기술에 의존 X)
  • Application 계층 : 비즈니스 유스케이스를 처리하는 Service
  • Presentation 계층 : 사용자로부터 입력과 요청을 처리하는 Controller
  • Infrastructure 계층 : 데이터 접근 기술 (JPA 등)의 구체적인 구현
// 도메인 계층
@Entity
public class Order {
    private Long id;
    private OrderStatus status;
    private List<OrderItem> items = new ArrayList<>();

    public void createOrder() {
        this.status = OrderStatus.CREATED;
    }
}

public interface OrderRepository {
    void save(Order order);
    Optional<Order> findById(Long id);
}

=================================================================================

// 애플리케이션 계층
@Service
@RequiredArgsConstructor
public class OrderService {

    private final OrderRepository orderRepository;

    public void createOrder(Order order) {
        order.createOrder();
        orderRepository.save(order);
    }

    public Optional<Order> getOrderById(Long id) {
        return orderRepository.findById(id);
    }
}

=================================================================================

// 프레젠테이션 계층
@RestController
@RequiredArgsConstructor
@RequestMapping("/orders")
public class OrderController {

    private final OrderService orderService;

    @PostMapping
    public ResponseEntity<Order> createOrder(@RequestBody Order order) {
        orderService.createOrder(order);
        return ResponseEntity.ok(order);
    }
}

=================================================================================

// 인프라스트럭처 계층: JPA를 사용한 구체적인 저장소 구현
@Repository
public interface JpaOrderRepository extends JpaRepository<Order, Long>, OrderRepository {}

 

 

 

여기서 기존 데이터베이스를 MongoDB로 변경을 하는 상황이 발생한다면

// 기존 JPA관련 @Entity 어노테이션을 제거하고, MongoDB관련 어노테이션을 추가
@Document(collection = "orders")
public class Order {
    @Id
    private Long id;
    private OrderStatus status;
    private List<OrderItem> items;

    // 비즈니스 로직
    public void placeOrder() {
        this.status = OrderStatus.PLACED;
    }
}

=================================================================================

// 인프라스트럭처 계층: 기존 JpaRepository를 제거하고 MongoRepository 사용한 구체적인 저장소로 변경
@Repository
public interface MongoOrderRepository extends MongoRepository<Order, Long>, OrderRepository {}

 

 

 

이렇게 DDD 구조를 이용하면 데이터 접근 기술을 유연하게 변경할 수 있다. Domain 모델과 비즈니스 로직을 직접 수정하지 않고, Infrastructure 계층만 변경하면 쉽게 데이터 접근 기술을 바꿀 수 있으므로, 유지보수성과 확장성을 높일 수 있다.

 

 

추가로 계속 학습하면서 DDD 구조에 적응해 나아가보겠습니다.🖐️🖐️

'🖥️ Back-end' 카테고리의 다른 글

데이터베이스 락(Lock) 이란?  (0) 2025.03.31
자바의 Record 란?  (1) 2025.03.27
웹 사이트의 동작 원리  (0) 2025.03.11
Jackson 이란?  (1) 2025.03.05
RabbitMQ 란?  (0) 2025.03.04