01. 이 구조, 왜 이렇게 생겼지?

신입으로 입사한 뒤, 프로젝트 구조를 처음 봤을 때 가장 당황스러웠던 건 디렉토리였다.
내가 개인 프로젝트나 학원에서 쓸 때는 대부분 아래처럼 단순한 구조였다.

- controller
- service
- repository
- dto
- entity

그런데 회사 프로젝트는 이렇게 구성돼 있었다.

- controller
- application
- domain
- infra
- support

처음엔 그냥 “이름만 다른건가?”라고 생각했는데, 하나하나 코드를 열어보면서 알게 됐다.
디렉토리 구조가 다른 게 아니라, 애초에 설계의 출발점이 달랐다는 것.


02. Spring MVC vs DDD _ 구조 비교해보기

기준Spring MVC 구조DDD 구조
요청 흐름Controller → Service → RepositoryPresentation → Application → Domain → Infra
설계 중심데이터(DB) 중심도메인(업무 의미) 중심
트랜잭션 위치Service 내부Application 계층
역할 분리불분명 (Service에 몰림)명확 (계층별 책임 분리)
테스트 용이성단위 테스트 어려움도메인 중심 테스트 쉬움

Spring MVC 구조

@PostMapping("/orders")
public void placeOrder(@RequestBody OrderRequest req) {
    orderService.placeOrder(req);
}

@Service
public class OrderService {
    public void placeOrder(OrderRequest req) {
        if (req.totalPrice() < 10000) throw new IllegalArgumentException();
        orderRepository.save(req.toEntity());
    }
}

작을 땐 괜찮지만, 프로젝트가 커지면 어느 순간
Service가 모든 책임을 지고 있는 구조가 된다.


DDD 구조

// controller
@PostMapping("/orders")
public void placeOrder(@RequestBody OrderRequestDto dto) {
    orderUseCase.placeOrder(dto);
}
// application
@Transactional
public void placeOrder(OrderRequestDto dto) {
    Order order = dto.toDomain();
    orderService.validateMinimumPrice(order);
    orderRepository.save(order);
}
// domain
public class Order {
    public void complete() {
        if (!status.isPending()) throw new IllegalStateException();
        this.status = OrderStatus.COMPLETED;
    }
}

03. 기술보다 구조 설계가 먼저다

DDD 구조를 처음 마주했을 때 낯설었던 건,
단순히 디렉토리 이름 때문이 아니라
아예 다른 아키텍처 설계 원칙이 적용돼 있었기 때문이었다.


아키텍처란?

소프트웨어 구조를 어떻게 나눌지, 각 계층에 어떤 책임을 줄지를 결정하는 설계 방식

MVC, DDD, Hexagonal 같은 이름들은
모두 이런 “구조적 분리 기준”을 다르게 설정한 아키텍처 패턴이다.


책임과 의존 방향을 나누는 구조

DDD 구조에서는

  • 도메인은 외부 기술(JPA, MyBatis 등)을 몰라야 하고
  • 트랜잭션은 유즈케이스 단위(Application 계층)에서 처리하며
  • Infra는 도메인을 참조하지 않고 오직 인터페이스에만 구현체를 붙인다

→ 즉, 기술보다 앞서는 건 구조 설계의 기준이다.


관심사 분리는 “어떤 로직이 어디 있어야 하는가”

이 구조는 특정 기술에 종속되지 않는다.
JPA를 쓰든, MyBatis를 쓰든, 심지어 Mongo를 쓰든
도메인 계층은 오직 “비즈니스 의미”만 다루고,
“데이터 저장 방식”은 infra로 분리돼야 한다.

// domain
public interface OrderRepository {
    void save(Order order);
}
// infra
@RequiredArgsConstructor
public class OrderRepositoryImpl implements OrderRepository {
    private final JpaOrderRepository jpaRepo;

    public void save(Order order) {
        jpaRepo.save(order);
    }
}

이렇게 구성하면 도메인은 저장 방식, 프레임워크, 외부 API와 전혀 무관해진다.
결국 이게 구조 설계의 목적이다.

처음엔 “이 구조 왜 이렇게 복잡하지?”라고만 느꼈는데,
지금은 “책임과 의존을 어떻게 나눴는가”를 먼저 보게 된다.


04. 실제 프로젝트 구조로 보면 이렇게 나뉜다

회사 프로젝트 구조는 다음과 같았다.

├── application     → 유즈케이스 단위 처리
├── controller      → HTTP 요청 수신 및 응답 DTO 처리
├── domain          → Entity, VO, DomainService, Repository 인터페이스
├── infra           → JPA, MyBatis, 외부 API 등 기술 구현
├── support         → 공통 예외, 유틸리티, 설정 등
디렉토리주요 책임
controller외부 요청 처리, DTO ↔ 도메인 변환
application유즈케이스 실행, 트랜잭션 관리
domain비즈니스 핵심 로직 (상태, 규칙, 행위)
infra기술 의존 구현 (JPA, MyBatis, 외부 API 등)
support공통 유틸, 예외, 로깅, 설정

Support 계층은 어떤 역할을 하지?

Support는 도메인이나 인프라와 무관하게
전역적으로 사용하는 공통 기능을 넣는 곳이다.

예시:

✔️ 디렉토리

support
├── exception
│   ├── BusinessException.java
│   ├── GlobalExceptionHandler.java
├── util
│   └── EncryptionUtil.java

✔️ 예외 처리

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<?> handleBusinessException(BusinessException e) {
        return ResponseEntity.badRequest().body(new ErrorResponse(e.getMessage()));
    }
}
  • 예외는 domain에 넣지 않는다
  • 유틸, 로깅, 설정 등도 support에 모아서 관리

05. 구조 흐름도 비교

구조 흐름도


마무리 정리

DDD 구조를 처음 접했을 땐 낯설기만 했다.
하지만 하나씩 코드를 읽고 구조를 따라가면서 알게 됐다.

  • 설계의 기준이 다르다.
  • "데이터 중심"이 아니라 "의미 중심"으로 구조를 만든다.
  • 구조가 복잡한 만큼, 확장성과 유지보수성이 분명히 더 좋다.

핵심은 어떤 기술을 쓰느냐가 아니라, 어떤 방향으로 구조를 설계하느냐였다.
도메인을 기술로부터 분리해놓는 그 설계 방식 자체가
내가 이해한 DDD의 시작점이었다.

0개의 댓글