💡 오늘 학습 키워드
- DDD
- DDD Layerd Architecture
- DDD Hexagonal Architecture
🎯 학습 내용 정리
소프트웨어가 복잡해질수록 단순히 기능을 구현하는 것보다 변경에 유연하고 유지보수가 쉬운 구조를 설계하는 것이 중요해진다.
초기에는 Controller, Service, Repository를 하나의 패키지에 작성하는 전통적인 계층형 구조만으로도 충분하지만, 서비스 규모가 커지고 비즈니스 규칙이 복잡해지면 다음과 같은 문제가 발생한다.
- 비즈니스 로직이 Controller, Service, Repository 여러 곳에 분산된다.
- 데이터베이스 변경이 도메인 로직에 영향을 준다.
- 외부 시스템(API, Message Queue, Cache 등)과 핵심 비즈니스 로직이 강하게 결합된다.
- 테스트가 어려워진다.
이러한 문제를 해결하기 위해 등장한 방식이 DDD(Domain Driven Design) 기반 Layered Architecture와 Hexagonal Architecture(Ports & Adapters Architecture)이다.
두 아키텍처 모두 핵심 목적은 동일하다.
비즈니스 핵심 영역(Domain)을 외부 기술 요소로부터 보호하고, 변경에 강한 구조를 만드는 것이다.
1. DDD(Domain Driven Design)
DDD는 단순히 패키지를 나누는 방법이 아니라 도메인 중심으로 소프트웨어를 설계하는 방법론이다.
기존 개발 방식은 데이터베이스 테이블을 기준으로 Entity를 만들고 CRUD를 구현하는 방식이 많았다.
예를 들어 주문 시스템을 개발한다고 하면
Order Table
|
Order Entity
|
Order Service
|
Order Controller
처럼 데이터 중심으로 설계한다.
하지만 DDD에서는 실제 업무 영역의 개념을 중심으로 설계한다.
예를 들어 주문 도메인에서는 다음과 같은 비즈니스 개념이 존재한다.
Order
├── 주문 생성
├── 주문 취소
├── 주문 상태 변경
├── 결제 완료 처리
└── 배송 상태 변경
즉, 데이터보다 도메인의 행동과 규칙을 중심으로 모델링한다.
DDD의 주요 구성 요소는 다음과 같다.
1) Entity
Entity는 식별자를 가지는 객체이다.
예를 들어 User는 username이 아니라 userId를 기준으로 식별된다.
@Entity
public class User {
private Long id;
private String username;
private String password;
public void changePassword(String password) {
this.password = password;
}
}
Entity는 단순 데이터 저장 객체가 아니라 자신의 상태 변경 규칙을 가진다.
잘못된 방식
user.setStatus("DELETED");
좋은 방식
user.delete();
왜냐하면 삭제라는 행위에는 비즈니스 규칙이 포함될 수 있기 때문이다.
2) Value Object
Value Object는 값 자체가 의미를 가지는 객체이다.
예를 들어 주소(Address)를 생각할 수 있다.
잘못된 구조
private String city;
private String street;
private String zipcode;
개선
public class Address {
private String city;
private String street;
private String zipcode;
}
Address라는 의미 있는 객체로 표현하면 다음과 같은 장점이 있다.
- 의미가 명확해진다.
- 검증 로직을 한 곳에서 관리할 수 있다.
- 재사용 가능하다.
3) Aggregate
Aggregate는 관련된 Entity와 Value Object를 하나의 단위로 묶은 것이다.
예를 들어 주문(Order)은 다음 객체들을 포함할 수 있다.
Order Aggregate
Order
├── OrderItem
├── DeliveryInfo
└── PaymentInfo
외부에서는 Aggregate Root를 통해서만 내부 객체를 변경한다.
order.addItem(product);
직접 접근
orderItem.setQuantity(10);
을 제한한다.
이렇게 하면 객체 내부의 불변성을 유지할 수 있다.
4. DDD Layered Architecture
DDD Layered Architecture는 시스템을 역할별 계층으로 분리한다.
대표적인 구조는 다음과 같다.
presentation
|
application
|
domain
|
infrastructure
1) Presentation Layer
외부 요청을 처리하는 계층이다.
대표적인 구성
- Controller
- Request DTO
- Response DTO
역할
- HTTP 요청 처리
- Validation
- DTO 변환
예
@RestController
@RequiredArgsConstructor
public class OrderController {
private final OrderService orderService;
@PostMapping("/orders")
public OrderResponse create(
@RequestBody OrderRequest request
){
return orderService.create(request);
}
}
Controller는 비즈니스 로직을 가지지 않는다.
잘못된 구조
if(order.getPrice() > 10000){
discount();
}
이런 로직은 Domain 또는 Application Layer에 존재해야 한다.
2) Application Layer
Application Layer는 사용자의 요청 흐름을 조정하는 역할을 한다.
주요 책임
- Transaction 관리
- Domain 객체 호출
- 여러 Domain 조합
예
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository orderRepository;
@Transactional
public void create(OrderCommand command){
Order order =
Order.create(command);
orderRepository.save(order);
}
}
중요한 점은 Application Layer가 비즈니스 규칙을 가지면 안 된다는 것이다.
Application은 "무엇을 실행할지" 결정하고,
Domain은 "어떻게 동작해야 하는지" 결정한다.
3) Domain Layer
DDD에서 가장 중요한 계층이다.
Domain Layer는 시스템의 핵심 비즈니스 로직을 담당한다.
구성
domain
├── entity
├── valueobject
├── domain service
├── repository interface
└── domain event
예
public class Order {
private OrderStatus status;
public void cancel(){
if(status == PAID){
throw new IllegalStateException(
"결제 완료 주문은 취소할 수 없습니다."
);
}
status = CANCELLED;
}
}
주문 취소라는 비즈니스 규칙은 Domain이 책임진다.
4) Infrastructure Layer
Infrastructure Layer는 기술적인 구현을 담당한다.
예
- Database
- Redis
- Kafka
- External API
- File System
구조
domain
OrderRepository(interface)
infrastructure
OrderRepositoryImpl
JpaOrderRepository
Domain
public interface OrderRepository {
Order save(Order order);
Optional<Order> findById(Long id);
}
Infrastructure
@Repository
@RequiredArgsConstructor
public class OrderRepositoryImpl
implements OrderRepository {
private final JpaOrderRepository repository;
@Override
public Order save(Order order){
return repository.save(order);
}
}
Domain은 JPA를 모른다.
따라서 데이터베이스가 변경되어도 Domain은 영향을 받지 않는다.
3. Hexagonal Architecture
Hexagonal Architecture는 Ports and Adapters Architecture라고도 한다.
핵심 개념은 다음과 같다.
애플리케이션 핵심 로직을 중앙에 두고, 외부 시스템은 Adapter를 통해 연결한다.
구조
REST API
|
Controller
|
Port
|
---------------
| Application |
| Domain |
---------------
|
Port
|
Persistence
Message Queue
External API
1) Port
Port는 외부와 내부를 연결하는 인터페이스이다.
예
public interface PaymentPort {
PaymentResult pay(
PaymentCommand command
);
}
애플리케이션 입장에서는 결제 기능만 필요하다.
어떤 결제 시스템을 사용하는지는 관심 없다.
2) Adapter
Adapter는 Port의 실제 구현체이다.
예
PaymentPort
|
|
KakaoPaymentAdapter
|
|
Kakao API
구현
@Component
@RequiredArgsConstructor
public class KakaoPaymentAdapter
implements PaymentPort {
private final KakaoClient client;
public PaymentResult pay(
PaymentCommand command
){
return client.request(command);
}
}
나중에 결제 시스템을 변경해도 Adapter만 변경하면 된다.
DDD Layered Architecture와 Hexagonal Architecture 차이를 정리해보면 다음과 같다.
| 구분 | Layered | Hexagonal |
| 중심 개념 | 계층 분리 | 의존성 방향 제어 |
| 목적 | 책임 분리 | 외부 기술 격리 |
| 핵심 영역 | Domain Layer | Application + Domain |
| 데이터 접근 | Repository Pattern | Outbound Adapter |
| 외부 API | Infrastructure | Adapter |
| 테스트 | 가능 | 더 쉬움 |
두 방식은 경쟁 관계가 아니다.
실제 프로젝트에서는 함께 사용한다.
MSA 환경에서의 적용
MSA에서는 서비스마다 독립적인 도메인이 존재한다.
예:
order-service
domain
├── order
├── payment
└── delivery
application
└── OrderApplicationService
infrastructure
├── JpaRepository
├── KafkaProducer
└── FeignClient
외부 서비스 호출도 Adapter로 처리한다.
예
Order Service
UserClient Port
|
Feign Adapter
|
user-service
Order Domain은 User Service가 Feign인지 Kafka인지 알 필요가 없다.
MSA는 서비스가 분리되어 있지만 내부 구조가 엉켜 있으면 유지보수가 어렵다.
예
Controller
|
Service
|
Repository
|
External API
구조에서는 모든 변경이 Service에 영향을 준다.
반면 DDD + Hexagonal 구조에서는
Adapter
|
Application Port
|
Domain
형태로 의존성이 흐른다.
핵심 비즈니스 로직은 항상 보호된다.
물류 플랫폼의 Order Service를 기준으로 하면
order-service
├── presentation
│ └── OrderController
│
├── application
│ └── OrderService
│
├── domain
│ ├── Order
│ ├── OrderItem
│ └── OrderRepository
│
└── infrastructure
├── JpaOrderRepository
├── KafkaOrderProducer
└── PaymentFeignAdapter
흐름
Client
↓
Controller
↓
Application Service
↓
Domain Model
↓
Repository Port
↓
Repository Adapter
↓
Database
이 구조에서는 데이터베이스, 메시지 시스템, 외부 API 변경이 Domain에 영향을 주지 않는다.
DDD Layered Architecture는 비즈니스 영역을 중심으로 책임을 분리하는 설계 방식이다.
Hexagonal Architecture는 비즈니스 로직이 외부 기술에 의존하지 않도록 의존성 방향을 제어하는 설계 방식이다.
둘을 함께 적용하면 다음과 같은 장점을 얻을 수 있다.
- 비즈니스 로직 보호
- 테스트 용이성 증가
- 기술 변경 영향 최소화
- MSA 서비스 독립성 향상
- 유지보수성과 확장성 향상
특히 MSA 환경에서는 하나의 서비스가 작은 애플리케이션처럼 독립적으로 운영되기 때문에 DDD와 Hexagonal Architecture 적용이 더욱 중요하다.
📚 한줄 정리
DDD Layered Architecture는 도메인 중심으로 책임을 분리하고, Hexagonal Architecture는 외부 기술과 도메인을 분리하여 변경에 강한 구조를 만드는 설계 방식이다.
'Bootcamp > Fundamentals' 카테고리의 다른 글
| Spring AI Fundamentals (0) | 2026.07.31 |
|---|---|
| Webhook을 이용한 Slack 알림 전송과 Kafka/RabbitMQ 적용 (0) | 2026.07.30 |
| Kafka (0) | 2026.07.28 |
| 대규모 시스템 설계와 RabbitMQ (0) | 2026.07.27 |
| Redis를 활용한 Session Clustering, Leaderboard 그리고 Cache (0) | 2026.07.24 |