본문 바로가기

Computer Science

[Software Architecture] 도메인 주도 설계(Domain-Driven Design, DDD)

728x90
도메인 주도 설계(Domain-Driven Design, DDD)란?
  • 에릭 에반스가 제안한 소프트웨어 설계 방법론
  • 비즈니스 도메인을 중심으로 소프트웨어를 설계하고 개발하는 방식
  • 쉽게 말해 비즈니스의 핵심 개념과 로직을 코드에 잘 녹여내는 것이 DDD의 핵심

DDD의 핵심 개념
도메인(Domain)이란?
  • 소프트웨어가 해결하려는 특정 비즈니스 영역
  • 예를 들어 쇼핑몰이라면 상품, 주문, 결제 등이 도메인이 될 수 있음
바운디드 컨텍스트 (Bounded Context)

 

  • 하나의 도메인은 여러 개의 서브 도메인으로 나뉠 수 있음
  • 하지만 각 도메인은 명확한 경계(Context)를 가져야 함
  • 예를 들어, 주문(Order)이라는 개념이 배송팀과 고객센터에서 다르게 해석될 수도 있기 때문각 도메인의 경계를 명확히 나누는 것이 중요
    • 배송팀의 주문 → 배송 상태를 추적하는 정보 중심
    • 고객센터의 주문 → 환불 및 고객 응대 정보 중심
  • 따라서 두 팀이 공유하는 주문이라는 개념이라도, 서로 다르게 바라볼 수 있고 이를 바운디드 컨텍스트를 통해 분리하는 것이 중요함
엔티티 (Entity) vs 밸류 객체 (Value Object)
  • 엔티티(Entity)
    • 고유한 ID를 가지며, 상태가 변할 수 있는 객체
    • 예 : User, Order, Product
class Order {
    private Long id;  // 고유한 식별자
    private List<OrderItem> items;
    private String status;
}

 

 

  • 밸류 객체 (Value Object, VO)
    • ID 없이 값 자체로 의미를 가지는 객체
    • 불변(Immutable) 값이며 변경이 아닌 새로운 값으로 교체하는 방식 사용
    • 예: Money, Address, DiscountPolicy
class Money {
    private final BigDecimal amount;
    private final String currency;
}

 

 

애그리게이트 (Aggregate)

 

  • 하나 이상의 엔티티(Entity)와 VO(Value Object)로 이루어진 트랜잭션 단위 그룹
  • 애그리게이트 루트(Aggregate Root)에서 데이터 변경을 관리하며, 외부에서 직접 내부 엔티티를 변경 불가
  • 예 : 주문과 주문 아이템
class Order {  // Aggregate Root
    private Long id;
    private List<OrderItem> items;
    private String status;

    public void addItem(OrderItem item) {
        items.add(item);
    }
}

class OrderItem {  // 내부 엔티티 (외부에서 직접 접근 X)
    private Long productId;
    private int quantity;
}

 

  • Order가 애그리게이트 루트이므로, OrderItem을 변경하려면 반드시 Order를 통해야 함
애그리게이트 루트(Aggregate Root)
  • 애그리게이트 안에는 1개 이상의 엔티티 또는 밸류 객체가 존재
  • 하나의 애그리게이트를 대표하는 엔티티애그리게이트 루트라고 함
도메인 애그리게이트 애그리게이트 루트 포함된 엔티티 및 밸류 객체
회원 회원 회원 정보 회원 포인트
주문 주문 주문 정보 배달 주문자, 배달 정보, 배달 추적 정보, 배달 주소 정보
음식 음식 음식 정보 X
결제 결제 결제 정보 X

 

  • 회원 도메인에서는 회원 정보가 애그리게이트 루트이며 회원 포인트 그 내부의 엔티티 혹은 밸류 객체일 수 있음
  • 주문 도메인에서는 주문 정보가 애그리게이트 루트이며 배달 정보, 배달 주소 같은 것부차적인 엔티티 또는 VO로 포함
  • 애그리게이트 루트직접 다른 애그리게이트의 내부 엔티티를 참조하면 안 됨
    • 예를 들어, 회원 애그리게이트가 주문 애그리게이트 내부의 배달 정보를 직접 참조하면 안 됨
    • 대신 주문 정보의 ID를 저장해서 참조하는 방식(참조 ID 또는 도메인 이벤트 사용)이 바람직
  • 애그리게이트 내부의 변경반드시 애그리게이트 루트를 통해야 함
    • 예를 들어, 회원 포인트를 변경하려면 회원 정보(애그리게이트 루트)를 통해 변경
    • 주문 정보를 통해서만 배달 정보를 변경

 

 

 

도메인 서비스 (Domain Service)

 

  • 하나의 엔티티 또는 애그리게이트에서 처리하기 어려운 복잡한 도메인 로직을 담당하는 서비스
  • 주로 여러 애그리게이트를 조합해야 하는 경우사용
  • 예 : 할인 정책 적용
class DiscountService {
    public Money applyDiscount(Order order, Coupon coupon) {
        // 할인 로직
    }
}

 

리포지토리 (Repository)

 

  • 애그리게이트 단위데이터를 저장하고 조회하는 역할
  • JPA와 함께 사용되며 CrudRepository 또는 JpaRepository를 활용 가능
interface OrderRepository extends JpaRepository<Order, Long> {
    Optional<Order> findById(Long id);
}

 

애플리케이션 서비스 vs 도메인 서비스

 

  • 애플리케이션 서비스 (Application Service)
    • 트랜잭션 관리, 도메인 서비스 호출 등의 역할
    • @Service 로 구현되는 일반적인 서비스 계층
@Service
class OrderService {
    private final OrderRepository orderRepository;
    private final DiscountService discountService;

    public void placeOrder(Long userId, List<Long> productIds) {
        // 주문 생성
        Order order = new Order(userId, productIds);
        
        // 할인 적용
        Money discount = discountService.applyDiscount(order, coupon);
        
        orderRepository.save(order);
    }
}

 

  • 도메인 서비스 (Domain Service)
    • 애그리게이트 단위에서 처리하기 어려운 도메인 로직을 구현하는 서비스
class DiscountService {
    public Money applyDiscount(Order order, Coupon coupon) {
        // 할인 정책에 따른 가격 계산
    }
}

DDD를 사용해야 하는 경우
  • MSA 아키텍처에서 서비스 간 경계를 명확히 나누고 싶을 때
  • 비즈니스 로직이 복잡하여 객체지향적인 설계가 필요할 때
  • 장기적으로 유지보수와 확장이 중요한 대규모 프로젝트일 때

DD를 사용하지 않아도 되는 경우
  • 비즈니스 로직이 단순하고 CRUD 위주일 때
  • 빠른 개발이 중요하고 유지보수보다는 MVP 출시가 우선일 때
  • DDD의 개념을 잘못 적용하면 오히려 코드가 복잡해질 수 있음

DDD와 MSA의 관계
  • MSA에서는 서비스 간 경계를 명확하게 해야 하는데 DDD의 바운디드 컨텍스트 개념이 큰 도움을 줌
  • 서비스 간 데이터 공유 시 애그리게이트 단위로 API를 설계하는 것이 중요
  • 도메인 서비스, 애그리게이트, 이벤트 소싱 등의 개념이 MSA 설계와 잘 맞아떨어짐

 

728x90