DDD(Domain-Driven Design, 도메인 주도 설계)는 복잡한 소프트웨어 시스템을 설계할 때 비즈니스 도메인(업무 영역) 을 중심에 두고 개발하는 방법론이에요. 에릭 에반스(Eric Evans)의 책 *“Domain-Driven Design: Tackling Complexity in the Heart of Software”*에서 처음 제안되었고, 특히 도메인 복잡성이 높은 시스템에서 강력한 효과를 발휘합니다.
✅ DDD란?
간단히 말해 DDD는:
- 비즈니스의 핵심 로직(도메인) 을 가장 중요한 것으로 보고,
- 도메인 전문가와 개발자가 협력하여,
- 소프트웨어 모델을 업무 용어와 일치시키고,
- 유지보수 가능하고 유연한 설계를 만드는 것을 목표로 해요.
✅ DDD의 핵심 개념
| 개념 | 설명 |
|---|---|
| Entity | 식별자가 있어 고유하게 구분되는 객체 (ex. Order, User) |
| Value Object | 식별자 없이 값 자체로 의미가 있는 객체 (ex. Address, Money) |
| Aggregate | 도메인 객체들의 군집. 하나의 루트 엔티티(Aggregate Root)가 존재 |
| Repository | 도메인 객체를 저장하고 조회하는 인터페이스 (findById, save) |
| Service (도메인 서비스) | 특정 엔티티에 속하지 않는 비즈니스 로직 담당 |
| Factory | 복잡한 객체 생성 로직 담당 |
| Bounded Context | 특정 도메인 모델이 적용되는 명확한 경계 |
✅ Spring Boot에서 DDD 코드상 특징
Spring Boot에서 DDD를 적용하면 다음과 같은 패키지 구조 및 역할 분리가 나타납니다:
📁 예시 디렉토리 구조
com.example.order
┣ domain
┃ ┣ model
┃ ┃ ┣ Order.java (Entity)
┃ ┃ ┣ OrderLine.java (Value Object)
┃ ┣ repository
┃ ┃ ┗ OrderRepository.java
┃ ┣ service
┃ ┃ ┗ OrderService.java (도메인 서비스)
┣ application
┃ ┗ OrderApplicationService.java (유스케이스 담당)
┣ infrastructure
┃ ┗ JpaOrderRepository.java (Repository 구현)
┣ api
┃ ┗ OrderController.java (Web layer)
📌 코드 예시
Entity 예시
@Entity
public class Order {
@Id
private Long id;
private String customerName;
@ElementCollection
private List<OrderLine> orderLines;
// 도메인 로직
public void addItem(OrderLine item) {
orderLines.add(item);
}
}
Value Object 예시
@Embeddable
public class OrderLine {
private String productName;
private int quantity;
private int price;
public int totalPrice() {
return quantity * price;
}
}
Repository
public interface OrderRepository {
Optional<Order> findById(Long id);
void save(Order order);
}
Domain Service
public class OrderService {
public void cancelOrder(Order order) {
if (order.isCancelable()) {
order.cancel();
}
}
}
Application Service
@Service
public class OrderApplicationService {
private final OrderRepository orderRepository;
public OrderApplicationService(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
public void placeOrder(OrderDto dto) {
Order order = new Order(...); // 팩토리 패턴도 가능
orderRepository.save(order);
}
}
✅ 요약
- DDD는 비즈니스 도메인 중심의 설계 방식
- Spring Boot에서는 레이어를 분리하고, 도메인 모델에 집중
- Entity / Value Object / Repository / Service 등 개념 명확히 분리
- 복잡한 비즈니스 로직을 다룰 때 유지보수성과 확장성이 뛰어남
좋은 OrderRepository.java와 JpaOrderRepository.java 두 개의 파일이 있지만, 실제로는 Repository는 하나인 게 맞아요. 그 이유를 간단하게 말하면:
OrderRepository는 인터페이스(역할),
JpaOrderRepository는 그 인터페이스의 구현체(구현) 입니다.
✅ 왜 이렇게 나누는가?
1. 도메인 계층과 기술 계층의 분리 (DDD 철학)
-
OrderRepository는 도메인 레이어에 위치:“이 주문을 저장하고 찾을 수 있다"는 비즈니스 관점의 역할만 선언함.
-
JpaOrderRepository는 인프라 레이어에 위치:실제로 JPA를 써서 “어떻게” 저장할지 구현함.
➕ 이 구조의 장점
| 장점 | 설명 |
|---|---|
| 느슨한 결합 | 도메인 로직은 JPA 같은 기술에 의존하지 않음 |
| 유연성 | 나중에 JPA → MongoDB로 바꿔도 도메인 코드는 그대로 |
| 테스트 용이 | 도메인 테스트 시, 구현체(JPA 등) 없이 가짜(Mock)로 대체 가능 |
✅ 예시로 다시 정리
OrderRepository.java (도메인에 있음)
public interface OrderRepository {
Optional<Order> findById(Long id);
void save(Order order);
}
JpaOrderRepository.java (인프라에 있음)
@Repository
public class JpaOrderRepository implements OrderRepository {
private final SpringDataJpaOrderRepository jpaRepo;
public JpaOrderRepository(SpringDataJpaOrderRepository jpaRepo) {
this.jpaRepo = jpaRepo;
}
@Override
public Optional<Order> findById(Long id) {
return jpaRepo.findById(id);
}
@Override
public void save(Order order) {
jpaRepo.save(order);
}
}
SpringDataJpaOrderRepository.java (Spring Data JPA 인터페이스)
public interface SpringDataJpaOrderRepository extends JpaRepository<Order, Long> {
}
✅ 요약
OrderRepository: “역할” 정의 (비즈니스 중심, 도메인 영역)JpaOrderRepository: “구현” 정의 (기술 중심, 인프라 영역)- 이렇게 분리함으로써 기술에 종속되지 않는 유연한 아키텍처를 구성할 수 있음