포스팅에서의 클린아키텍처는 Ports and Adapters 아키텍처, 헥사고날 아키텍처(육각형 아키텍처) 모두 같은 것을 의미한다.
이번 포스팅에서는 지난 포스팅에서이야기한 클린 아키텍처(Ports and Adapter) 의 구성과 이를 반영한 프로젝트의 패키지 구조에 대해 이야기를 해보려고한다.
참고 포스팅
[아키텍처] 클린아키텍처(Ports and Adapters): 왜 클린 아키텍처를 공부하게 되었는가?
왜 클린 아키텍처를 공부하게 되었는가? 나는 모놀리식 아키텍처, 계층형 아키텍처(MVC) 기반의 개인 프로젝트와 짧지만, 레거시 프로젝트를 담당해 개발한 경험이 있다. 그리고 프로젝트를 개발
sykeem.tistory.com
클린아키텍처에서 가장 중요한 것은 의존성 규칙이다. 모든 계층 간 의존성은 안쪽(도메인)을 향해야 한다. 이는 기존의 익숙한 아키텍처(=계층형 아키텍처)에서는 불가능하여, 클린 아키텍처에서는 의존성을 역전 기법을 사용하여, 의존성을 도메인을 향하도록 하였다.
영속성 계층(PersistanceAdapter)을 향하던 의존성을 Out Port 를 통해 뒤집는다. Port 는 각 어댑터(도메인 계층을 제외한 외부 계층)이 도메인 계층과 상호작용하기 위해 존재한다.
그렇다면 Adapter 는 무엇일까? 또 다른것들은?
클린 아키텍처의 구성
클린아키텍처의 계층 구조는 다음과 같다.
- Domain 계층: Domain 로직(=비즈니스규칙) 이 존재한다.
- Application 계층: 도메인로직에 접근하는 유스케이스들과 포트가 존재한다.
- Adapter 계층: 외부 계층으로 웹계층, 영속성계층(데이터베이스), 외부 인터페이스, UI가 존재할 수 있다.
Domain 계층
도메인 코드에서는 어떤 영속성 프레임워크나 UI 프레임워크가 사용되는지 알 수 없기(알 필요도 없다.) 때문에 특정 프레임 워크에 특화된 코드를 가질 수 없고, 비즈니스 규칙에 집중할 수 있다.
Application 계층
Application 계층에서는 유스케이스가 존재하며, 각 유스케이스는 계층형 아키텍처에서 서비스에 해당한다. 일반적으로 계층형 아키텍처에서 서비스는 넓은 서비스 문제를 많이 겪곤 하지만, Application 계층에서는 각 유스케이스(서비스)를 세분화하여 처리한다.
내가 만들고 있는 Product 도메인의 Application 계층의 서비스 중 하나로 나는 크게 Command & Query 로 요청을 분류하고, 각 유스케이스를 다루는 서비스를 만들었다. 그리고 현 사진의 ProductQueryServiceImpl은 각 QueryHandler 를 통해 유스케이스를 처리하도록 하였다.
Adapter 계층
Adapter 계층에서는 크게 In 과 Out 으로 나눌 수 있으며, 애플리케이션과 상호작용하는 다양한 Adapter 가 존재한다.
- In: Application 를 호출한다. (=애플리케이션을 주도한다.)
- Out: Application 에 의해 호출된다. (=애플리케이션에 의해 주도된다.)
애플리케이션 과 어댑터 간의 통신이 가능하려면 애플리케이션이 각각 포트를 제공해야한다. 그리고 각 포트는 어댑터의 속성(In/Out)에 따라 주도하는 어댑터와, 주도되는 어댑터로 나뉘어 진다.
- In(put) Port: 외부로부터 값을 전달받는 포트로, 애플리케이션에 있는 유스케이스 클래스 중 하나에 의해 구현되고 주도하는 어댑터(driving adapter)에 의해 호출되는 인터페이스가 된다.
- Out(put)Port: 외부로 부터 값을 내보내는 포트로, 주도되는 어댑터(driven adapter)에 의해 구현되고 애플리케이션에 의해 호출되는 인터페이스가 된다.
아까 위의 ProductQueryServiceImpl 내의 유스케이스중 하나(TrackProductQueryHandler 의 내부 모습이다. 유스케이스 내부에는 ProductRepository를 호출하는 모습을 볼 수있는데, 이는 Output Port 에 해당하는 인터페이스이다.
아직 Product 도메인의 웹계층이 개발되지 않아 Member 도메인에서의 웹계층의 모습을 가져왔다. Member 도메인 개발 당시에는 Application 계층에서의 개발이 Command와 Query로 나누어 개발하지 않고, 하나의 MemberApplication으로 개발이 되었다. (개념은 동일하다.) MemberApplication 는 Input Port에 해당하는 인터페이스이다.
클린 아키텍처를 구성하는 계층에서의 필요한 구조는 알았다. 그렇다면 이를 어떻게 구성해야할까?
패키지 구성
클린아키텍처의 구성을 보면, 각 계층의 책임이 분명하게 나뉘어져있다. 각 계층별 작업이 독립적으로 가능하다는 것 을 의미한다. 그렇다면, 기존의 계층으로 패키지를 구성하면될까?
계층형 패키지 구조
Product
ㄴ domain
| ㄴ Product
| ㄴ ProductRepository
| ㄴ ProductQueryService
| ㄴ ProductApplicationService
|
ㄴ persistance
| ㄴ ProductPersistanceAdapter
|
ㄴ web
ㄴ ProductController
이런 구조가 맞는 것일까? 그렇다고 볼 수 없다.
1. 애플리케이션의 기능조각(functional slice)이나 특성(feature)을 구분 짓는 패키지 경계가 없다.
추가적인 구조가 없다면, 아주 빠르게 서로 연관되지 않은 기능들끼리 예상하지 못한 부수 효과를 일으킬 수 있는 괴물 같은 클래스가 만들어 질 수 있다.
2. 애플리케이션이 어떤 유스케이스들을 제공하는지 파악할 수 없다.
특정 기능을 찾기 또는 개발하기 위해서는 어떤 서비스가 이와 관련된 기능이 구현되었는지를 추측해야하며, 해당 서비스 내의 어떤 메서드가 해당 책임을 수행하는지 알아야한다.
3. 패키지 구조를 통해 목표로하는 아키텍처를 파악할 수 없다.
이 구조는 설계한 클린 아키텍처를 전혀 생각해낼 수 없다. 어떤 기능이 웹 어댑터에서 호출되고, 영속성 어댑터가 도메인 계층에 어떤 기능을 제공하는지 한눈에 파악하기 힘들다. 이 구조로 OutPut Port, Input Port가 어디에 있는지 파악할 수 있겠는가?
아키텍처적으로 표현력 있는 패키지 구조
클린 아키텍처에서 구조적으로 핵심적인 요소는 엔티티, 유스케이스, Input / Output Port, 주도하는/주도되는 어댑터 이다. 이 요소들을 애플리케이션의 아키텍처를 표현하는 패키지 구조로 나열할 수 있다.
Product
ㄴ Adapter
| ㄴ in
| ㄴ out
ㄴ domain
ㄴ core
|
ㄴ application
ㄴ ProductApplicationServiceImpl
ㄴ handler
ㄴ dto
ㄴ mapper
ㄴ ports
ㄴ in
| ㄴ ProductApplicationService
ㄴ out
ㄴ ProductRepository
책의 내용과는 차이가 있지만, 나는 이렇게 구성했다.
구조의 각 요소들은 패키지에 하나씩 직접 매핑된다. 실제 클린아키텍처의 구조는 도메인 계층과 애플리케이션 계층이 나뉘어지지만 나는 하나의 도메인 패키지 내, core(도메인계층)과 application(애플리케이션계층)을 위치 시켰다.
- 클린아키텍처를 아는사람, 모르는 사람 모두 도메인 계층이 분리되어 있음을 알 수 있는 방법이라 생각했다.(그런데 지금 생각해보니, 오히려 이것이 헷갈릴 수도 있겠다는 생각이 든다..관련하여 다른사람들의 생각이 궁금하다.)
이렇게보면, 기술적으로 나눠진 패키지 구조처럼 보인다.
그렇다면, 지금의 패키지 구조는 어떤 점이 좋을까? 나는 이렇게 상상하고 기대한다.
특정 기능 관련하여, 화이트보드 혹은 회의실에서 열띤 이야기가 이뤄진다.
A 도메인 로직은 수정이 되어야하고, 관련된 부분또한 수정이 되어야합니다.
자리에 돌아와 우리는 이렇게 작업을 할 수 있다.
1. 팀원별로 역할을 분담하여 같은 기능에 대하여, 동시에 작업할 수 있다.
2. 많고 복잡한 도메인들 중 회의 중 이야기한 로직 수정과 관련된 부분(유스케이스)를 찾기 쉽다.
(영속성계층 경우 adapter/out/해당하는Service, 그리고 그에 해당하는 Handler 를 수정하면된다.)
정리하면, 작업해야할 부분을 빠르게 찾을 수 있다. (과거와 비교하여) 해당 기능이 어느곳에 있는지 찾을 필요도 없다.너무나도 명확하다. 그리고 해당 작업을 여러 동료들과 동시에 작업할 수 있다. 명확하게 나눠진 경계와 명세를 통한 동시작업이 가능한 것이다. 이 뿐만 아니다. 이렇게 수정이 동시에 많이 될 수 있는데, 가장 중요한 도메인 은 보호가 된다. 다른 계층의 잘못된 개발로 인해 문제가 생기지 않는다. 잠재적인 위협도 없다.
다시보니, 도메인 중심(비즈니스중심)의 프로젝트가 구성될 수 있다. 그렇다. 지난 포스팅에서 이야기했듯이, 클린 아키텍처는 도메인 주도 설계를 지원하는 아키텍처이다.
정리
클린아키텍처의 구성은 크게 3계층으로 이야기 할 수 있다.
- Domain 계층: Domain 로직(=비즈니스규칙) 이 존재한다.
- Application 계층: 도메인로직에 접근하는 유스케이스들과 포트가 존재한다.
- Adapter 계층: 외부 계층으로 웹계층, 영속성계층(데이터베이스), 외부 인터페이스, UI가 존재할 수 있다.
그리고 Adapter와 Application 의 상호작용을 위해서는 Port 가 필요하며, Input, Output 으로 구성되며, 이에따라 Input Adapter, Output Adapter 로 나뉘어 진다.
- In: Application 를 호출한다. (=애플리케이션을 주도한다.)
- Out: Application 에 의해 호출된다. (=애플리케이션에 의해 주도된다.)
- In(put) Port: 외부로부터 값을 전달받는 포트로, 애플리케이션에 있는 유스케이스 클래스 중 하나에 의해 구현되고 주도하는 어댑터(driving adapter)에 의해 호출되는 인터페이스가 된다.
- Out(put)Port: 외부로 부터 값을 내보내는 포트로, 주도되는 어댑터(driven adapter)에 의해 구현되고 애플리케이션에 의해 호출되는 인터페이스가 된다.
클린 아키텍처를 준수하는 것만으로는 부족하다. 클린아키텍처임을 외쳐야한다. 구조의 각 요소들은 패키지에 하나씩 직접 매핑하여, 아키텍처적으로 표현력을 강조한다.
참고 자료
만들면서 배우는 클린 아키텍처 - YES24
우리 모두는 낮은 개발 비용으로 유연하고 적응이 쉬운 소프트웨어 아키텍처를 구축하고자 한다. 그러나 불합리한 기한과 쉬워보이는 지름길은 이러한 아키텍처를 구축하는 것을 매우 어렵게
www.yes24.com
GitHub - KEEMSY/shoes-ordering-system: shoes-ordering-system
shoes-ordering-system. Contribute to KEEMSY/shoes-ordering-system development by creating an account on GitHub.
github.com
'개인공부 > 아키텍처' 카테고리의 다른 글
[ 아키텍처 ] 로드밸런싱 패턴 (1) | 2023.10.29 |
---|---|
[ 개인 공부 ] 코드의 디커플링에 관하여 (1) | 2023.10.24 |
[ 아키텍처 ] 클린아키텍처에서의 유스케이스 구현하기 (0) | 2023.08.02 |
[아키텍처] 클린아키텍처(Ports and Adapters): 왜 클린 아키텍처를 공부하게 되었는가? (0) | 2023.07.20 |
[아키텍처] 클린아키텍처(Ports and Adapters) (0) | 2023.07.20 |