DDD(Domain-Driven Design)

Theo·2025년 8월 20일
post-thumbnail

DDD란 무엇인가?

DDD(Domain-Driven Design)는 복잡한 소프트웨어 프로젝트에서 도메인(비즈니스 영역)에 집중하여 설계하는 방법론입니다. 단순히 기술적인 구조가 아닌, 비즈니스 로직과 규칙을 중심으로 소프트웨어를 설계합니다.

왜 DDD가 필요한가?

기존 개발 방식의 문제점

  • 기술 중심 설계 : 데이터베이스나 프레임워크 구조가 비즈니스 로직을 지배
  • 소통의 어려움 : 개발자와 도메인 전문가 간의 언어 차이
  • 복잡성 증가 : 비즈니스가 복잡해질수록 코드도 복잡해짐
  • 유지보수 어려움 : 비즈니스 변경 시 코드 수정이 어려움

DDD의 해결책

  • 도메인 중심 : 비즈니스 로직이 코드의 중심
  • 공통 언어 : 개발자와 도메인 전문가가 같은 용어 사용
  • 모듈화 : 도메인별로 명확한 경계 설정
  • 유연성 : 비즈니스 변화에 빠른 대응

DDD의 핵심 개념

  1. Ubiquitous Language(공통 언어)

개발자와 도메인 전문가가 같은 용어를 사용하는 것

예시 : 전자상거래

// ❌ 기술적 용어
class UserData {
    private String id;
    private List<ItemData> cart;
    private PaymentInfo payment;
}

// ✅ 도메인 언어
class Customer {
    private CustomerId customerId;
    private ShoppingCart cart;
    private PaymentMethod paymentMethod;
}
  1. Domain Model(도메인 모델)

비즈니스 규칙과 로직을 표현하는 객체들

  1. Bounded Context(경계 컨텍스트)

도메인 모델이 적용되는 명확한 경계

DDD의 전술적 패턴

  1. Entity(엔티티)

고유한 식별자를 가지며 생명주기 동안 연속성을 유지하는 객체

public class Order {
    private OrderId orderId;        // 고유 식별자
    private CustomerId customerId;
    private List<OrderItem> items;
    private OrderStatus status;
    private LocalDateTime orderDate;
    
    public Order(OrderId orderId, CustomerId customerId) {
        this.orderId = orderId;
        this.customerId = customerId;
        this.items = new ArrayList<>();
        this.status = OrderStatus.PENDING;
        this.orderDate = LocalDateTime.now();
    }
    
    // 비즈니스 로직
    public void addItem(Product product, int quantity) {
        if (status != OrderStatus.PENDING) {
            throw new IllegalStateException("확정된 주문은 수정할 수 없습니다");
        }
        items.add(new OrderItem(product, quantity));
    }
    
    public void confirm() {
        if (items.isEmpty()) {
            throw new IllegalStateException("주문 항목이 없습니다");
        }
        this.status = OrderStatus.CONFIRMED;
    }
}
  1. Value Object(값 객체)

식별자가 없고 값으로만 구분되는 불변 객체

public class Money {
    private final BigDecimal amount;
    private final Currency currency;
    
    public Money(BigDecimal amount, Currency currency) {
        if (amount.compareTo(BigDecimal.ZERO) < 0) {
            throw new IllegalArgumentException("금액은 음수일 수 없습니다");
        }
        this.amount = amount;
        this.currency = currency;
    }
    
    public Money add(Money other) {
        if (!this.currency.equals(other.currency)) {
            throw new IllegalArgumentException("통화가 다릅니다");
        }
        return new Money(this.amount.add(other.amount), this.currency);
    }
    
    // equals, hashCode 구현 필수
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Money money = (Money) obj;
        return Objects.equals(amount, money.amount) && 
               Objects.equals(currency, money.currency);
    }
}
  1. Aggregate

연관된 엔티티와 값 객체들의 집합으로 일관성 경계를 정의

public class Order { // Aggregate Root
    private OrderId orderId;
    private CustomerId customerId;
    private List<OrderItem> orderItems; // 내부 엔티티
    private ShippingAddress shippingAddress; // 값 객체
    private OrderStatus status;
    
    // 애그리게이트 내부의 일관성 보장
    public void changeShippingAddress(ShippingAddress newAddress) {
        if (status == OrderStatus.SHIPPED) {
            throw new IllegalStateException("배송된 주문의 주소는 변경할 수 없습니다");
        }
        this.shippingAddress = newAddress;
    }
    
    // 외부에서는 Aggregate Root를 통해서만 접근
    public BigDecimal getTotalAmount() {
        return orderItems.stream()
            .map(OrderItem::getSubtotal)
            .reduce(BigDecimal.ZERO, BigDecimal::add);
    }
}
  1. Repository

애그리게이트의 저장과 조회를 담당하는 인터페이스

public interface OrderRepository {
    void save(Order order);
    Optional<Order> findById(OrderId orderId);
    List<Order> findByCustomerId(CustomerId customerId);
    List<Order> findByStatus(OrderStatus status);
}

// 구현체는 인프라스트럭처 계층에
@Repository
public class JpaOrderRepository implements OrderRepository {
    
    @PersistenceContext
    private EntityManager entityManager;
    
    @Override
    public void save(Order order) {
        entityManager.persist(order);
    }
    
    @Override
    public Optional<Order> findById(OrderId orderId) {
        return Optional.ofNullable(
            entityManager.find(OrderEntity.class, orderId.getValue())
        ).map(this::toDomain);
    }
    
    // Entity ↔ Domain 변환 로직
    private Order toDomain(OrderEntity entity) {
        // 변환 로직
    }
}
  1. Domain Service (도메인 서비스)

여러 애그리게이터에 걸친 비즈니스 로직을 처리

@Service
public class OrderPricingService {
    
    public Money calculateTotalPrice(Order order, Customer customer) {
        Money itemsTotal = order.getItemsTotal();
        Money discount = calculateDiscount(customer, itemsTotal);
        Money shippingFee = calculateShippingFee(order.getShippingAddress());
        
        return itemsTotal.subtract(discount).add(shippingFee);
    }
    
    private Money calculateDiscount(Customer customer, Money total) {
        if (customer.isVip()) {
            return total.multiply(0.1); // 10% 할인
        }
        return Money.ZERO;
    }
    
    private Money calculateShippingFee(ShippingAddress address) {
        // 지역별 배송비 계산 로직
        return new Money(BigDecimal.valueOf(3000), Currency.KRW);
    }
}

DDD 아키텍처 : Hexagonal Architecture

DDD는 보통 헥사고날 아키텍처와 함께 사용됩니다.

    ┌─────────────────────────────────────┐
    │         Infrastructure              │
    │  (Database, External APIs, etc.)    │
    └─────────────┬───────────────────────┘
                  │
    ┌─────────────▼───────────────────────┐
    │         Application                 │
    │    (Use Cases, Services)            │
    └─────────────┬───────────────────────┘
                  │
    ┌─────────────▼───────────────────────┐
    │           Domain                    │
    │  (Entities, VOs, Aggregates)        │
    └─────────────────────────────────────┘

마무리

DDD는 복잡한 비즈니스 도메인을 다루는 프로젝트에서 진가를 발휘합니다. 단순한 CRUD 애플리케이션에서는 과할 수 있지만, 복잡한 비즈니스 규칙이 있는 시스템에서는 코드의 가독성과 유지보수성을 크게 향상시킵니다.

0개의 댓글