도메인 주도 설계의 계층화 아키텍처(Layered Architecture)와 DIP

2022. 1. 5. 16:58도메인주도설계

계층화 아키텍처

계층화 아키텍처

  • 웹 어플리케이션에서 사용되는 대표적이고 전형적인 아키텍처
  • 표현 영역, 응용 영역, 도메인 영역, 인프라 영역 이렇게 네 개의 영역으로 구성됩니다.
  • 하지만 계층화 아키텍처를 구글링해보면 계층수가 다르거나 명칭도 조금씩 다릅니다. 하지만 계층화 되어있다는 점에서 같은 아키텍처로 보고 있습니다.
  • 가장 큰 특징은 상위 계층에서 하위 계층으로만 의존하고 하위 계층에서 상위 계층을 의존하지 않습니다.

엄격한 계층화 아키텍처

상위 계층은 바로 아래의 계층에만 의존해야합니다.

유연한 계층화 아키텍처

유연한 계층화 아키텍처

  • 구현의 편리함을 위해 계층 구조를 유연하게 적용한 버전입니다.
  • 응용 계층에서 최하위 계층인 인프라스트럭처 계층을 의존할 수 있습니다.

계층화 아키텍처의 구성요소

표현 계층

  • 클라이언트의 요청을 받고 응답을 전달하는 역할을 합니다. (입출력 담당)
  • 클라이언트의 요청 데이터에 대한 유효성 검사를 합니다. (예: 필수 입력값 validation 체크)
  • 자원에 접근할 수 있는 권한을 검사합니다.
  • 사용자의 세션을 관리합니다.
  • Spring MVC가 이 표현 계층을 지원하는 대표적인 프레임워크입니다.
  • 클라이언트에서 온 요청 데이터를 응용 계층에서 지원하는 형식으로 변환하여 응용 계층에 전달하고 응답 데이터를 클라이언트가 지원하는 형식으로 변환하여 클라이언트에게 전달 합니다. (예: HTTP 메시지를 요청 받아, 객체로 변환하여 응용 계층에 전달하고 다시 HTTP 응답 메시지로 변환하여 클라이언트에게 전달합니다.)

응용 계층

  • 응용 계층은 도메인 모델을 이용하여 클라이언트에게 제공할 기능을 구현합니다.
  • 비즈니스 로직 수행은 도메인 모델에 위임합니다.
  • 도메인 객체간의 실행 흐름을 제어합니다.
  • 도메인 모델의 정합성(논리적 오류)을 검증합니다. (예: 아이디 중복 여부 validation 체크)
  • 트랜잭션을 관리합니다.
  • 각 계층을 연결하여 각 계층이 지원하는 모델로 객체를 변환합니다. (예: dto -> 도메인, 도메인 -> dto)
  • 외부 모듈을 사용합니다. (예: 이메일 전송)
  • 도메인 이벤트를 처리합니다. (이벤트 핸들러)

도메인 계층

  • 도메인 모델을 구현합니다.
  • 핵심 로직, 비즈니스 로직을 구현합니다. (정책, 비즈니스 규칙을 구현)
  • 도메인 이벤트를 발생시킵니다.

인프라 계층

  • 논리적인 개념을 표현하기보다는 실제 구현을 다룹니다.
  • 표현, 응용, 도메인 계층을 지원합니다. 다른 계층이 필요로하는 기술들을 구현합니다.
  • 표현, 응용, 도메인은 인프라에 지원을 받기 위해 인프라 영역에 의존합니다(?)

인프라 영역에 의존하면 생기는 두 가지 문제

표현, 응용, 도메인 영역에서 인프라 영역의 기술을 지원받기 위해 의존한다면 두 가지 문제가 발생합니다.

1. 테스트가 어려워진다.

인프라는 구현 기술들을 다루는 곳입니다. 그런데 응용 영역인 서비스에서 도메인을 저장하기 위해 마이바티스를 사용한다고 생각해봅시다. 해당 서비스를 테스트하기 위해서는 마이바티스라는 기술 구현이 완성되어 있어야하고(물론 여기서는 외부 라이브러리이기 때문에 완성됨을 보장 받지만) 마이바티스 설정들이 모두 완벽하게 되어 있어야합니다.

서비스 단위 테스트를 하려고 했을 뿐인데 마이바티스에 의존적이게 됩니다.

2. 구현 방식을 변경하기 어려워진다.

인프라의 구현기능을 변경하기 위해서는 서비스의 코드도 수정되야합니다. 당연하겠죠?

DIP(Dependency Inversion Principle) - 의존 역전 원칙

이 문제를 해결하는 것이 바로 DIP입니다.

DIP란?
저수준 모듈이 고수준 모듈에 의존하도록 하는 것.
  • 고수준 모듈: 의미 있는 핵심 기능을 제공하는 모듈(여기서는 응용 영역, 도메인 영역이 해당된다.)
  • 저수준 모듈: 고수준 모듈이 핵심 기능을 구현하기 위해 필요로하는 하위 기능을 구현한 모듈(여기서는 인프라 영역이 해당된다.)

고수준 모듈이 제대로 동작하려면 저수준 모듈을 사용해야합니다.

그런데 고수준 모듈이 저수준 모듈을 사용하면 앞서 언급했던 두 가지 문제가 발생하게 됩니다.

DIP는 인터페이스를 이용해서 저수준 모듈이 반대로 고수준 모듈을 의존하게 만듭니다.

계층화 아키텍처에서의 DIP
(DIP 적용 전) 고수준 모듈이 저수준 모듈을 의존한다
(DIP 적용 후) 저수준 모듈이 고수준 모듈을 의존하게 한다.

  • DIP를 이용하면 테스트시, 구현 객체에 직접 의존하지 않기 때문에 실제 구현객체가 없어도 Mock 프레임워크를 이용하여 테스트가 가능합니다. 또한 테스트를 위한 구현객체의 설정도 필요하지 않습니다.
  • 구현 방식을 변경할 때도 서비스의 코드를 변경할 필요가 없어집니다.

DIP 주의사항

DIP 잘못된 설계

인터페이스를 저수준 모듈에서 추출하는 경우가 있습니다.

이는 잘못된 구조입니다.

여전히 고수준 모듈에서 저수준 모듈을 의존하고 있는 형태가 됩니다.

인터페이스는 비즈니스 로직을 위한 메서드를 정의하고 있기 때문에 고수준 모듈이고 이를 구현하는 기술이 저수준 모듈이 됩니다.

지금까지 인프라 영역에 의존하지 않기 위해 DIP에 대해 알아보았습니다.
하지만 무조건 인프라에 대한 의존을 없애는 것이 좋은 것은 아닙니다.
* 구현의 편리함은 DIP가 주는 다른 장점(변경의 유연함, 테스트가 쉬움)만큼 중요하기 때문에 DIP의 장점을 해치지 않는 범위에서 응용 영역과 도메인 영역에서 구현 기술에 대한 의존을 가져가는 것이 현명합니다.

예를들어 스프링에서 트랜잭션 처리를 위해 스프링이 제공하는 @Transactional을 사용하는 것입니다.
@Transactional을 사용하면 한 줄로 트랜잭션을 처리할 수 있는데 @Transactional을 사용하지 않고 코드에서 스프링에 대한 의존을 없애려면 복잡한 스프링 설정을 사용해야합니다. 의존은 없앴지만 특별히 테스트를 더 쉽게 할 수 있다거나 유연함을 증가시켜주지는 못합니다. 단지 설정만 복잡해지고 개발 시간만 늘어날 뿐입니다.
마찬가지로 JPA의 @Entity, @Table 등을 사용하는 것이 XML 설정을 이용하는 것보다 편리합니다.

 

DDD를 꼭 계층화 아키텍처로 구현할 필요는 없습니다!

헥사고날 아키텍처라는 것도 있습니다.
이 시간에 말하고자 하는 포인트는 기존에 없던 도메인 레이어가 생긴다는 것 입니다.

헥사고날 아키텍처의 개념과 예제에 대해서는 다음 링크를 참조해주세요
https://zkdlu.tistory.com/4

 

헥사고날 아키텍처(Hexagonal Architecture)

기존의 계층형 아키텍처는 DIP를 적용해도 한계가 있고, 도메인이 인프라에 의존하게 되면서 도메인적인 관심사와 기술적인 관심사가 섞이게 됩니다. MSA에서는 여러 종류의 어플리케이션을 호

zkdlu.tistory.com

https://mesh.dev/20210910-dev-notes-007-hexagonal-architecture/

 

헥사고날(Hexagonal) 아키텍처 in 메쉬코리아 :: MESH KOREA | VROONG 테크 블로그

mesh.dev