채용 과정에서 과제전형을 수행하였다. 간단한 CRUD 애플리케이션을 REST API로 구현하는 과제였는데 패키지 구조를 육각형 아키텍처로 결정하였다. 확장성을 고려 (아주 조금) + 아키텍처에 대한 고민 및 관심을 어필(아주 많이) 하고 싶은 마음이 혼합된 결정이었다.
면접에서 주어진 질문에 대해 잘 답변 하였다면 꽤 좋은 선택이 될 수 있었겠지만 그러지 못했다. 책을 읽고 프로젝트에 한번 적용해본 것 만으로는 어떤 개념을 깊이 있게 이해 할 수 없다는 것을 깨닳았다.
육각형 아키텍처에 대해서 잘 모르는 동료 개발자에게 육각형 아키텍처를 설명한다면 어떻게 설명할 수 있을까? 이런 가상(현실)의 질문을 바탕으로 위 링크의 내용을 정리해보았다.
Grocery
Driver
- 애플리케이션의 동작 (drive)을 유도하는 것
- users, programs, automated test or batch script
Port
- 포트는 바로 인터페이스
- In-Port (Praimary Port, Driving Port) 외부에서 요청해야 동작하는 포트와 어댑터를 주요소(primary)라고 하며, 포트와 어댑터에 따라 주포트 혹은 주어댑터라고도 부릅니다.
- Out-Port (Secondary Port, Driven Port) 애플리케이션이 호출하면 동작하는 포트
Adapter
- 어댑터는 바로 애플리케이션이 포트를 이용해 연결되는 외부의 구현체
- In-Adapter (Pramary Adater, Driving Adapter)
- WebAdapter (Rest API) 가 대표적이다.
- Out-Adapter (Secondary Adapter, Driven Adapter)
- PersistenceAdapter (Repository) 가 대표적이다.
Infrastructure
- 인프라스트럭쳐, 즉 기반 요소는 다른 서비스를 사용하는
Adapter
를 포함한다. - 다른 서비스란 무엇일까? 특히 영속성 어댑터 와 Infrastructure 를 구분짓는 기준은 무엇이 있을까? 많은 포스팅을 보아도
다른 서비스
를 명확히 구분하지는 않는것 같다. - 아래의 두번째 그림을 보고나니 infrastructure 를 Sencondary Adapter 와 대치되는 개념이 아닌 포함되는 개념으로 보는 것이 옳은 것 같다.
- 이전에는 infrastructure 는 persistant adapter 와는 다른 network 를 이용하는 adapter 라는 정도의 불분명한 기준을 스스로 가지고 있었는데 잘못된 개념이었던 것 같다.


사실들
- 앨리스터 콕번 (Alistair Cockburn) 이 제안한 아키텍처이다.
Driver
와 실행 환경 (run-time devices, DB) 에 관계 없이 같은 동작을 보장한다.
Port
는 Application의 실행 환경, 구체적인 기술에 무지하다.- port 에
HttpServletRequest
같은 클래스에대한 의존이 있으면 안된다.
Motivation
- 최근 몇년동안 애플리케이션의 비즈니스 로직이 user interface 에 드러나는 경우가 많다. 이런 코드는 테스트를 어렵게 만들며 변경을 어렵게 만든다.
- 많은 프로젝트에서 아키텍쳐에 새로운 layer를 추가하는 방식으로 이 문제를 해결하고자 하였다. 이때 아키텍처를 도입하며 개발자는 이런 다짐을 한다
“이 layer 에는 진짜 절대로 비즈니스 로직을 두지 않을 거야!!!!!”
- 나도 다른 팀 프로젝트를 진행하며 비슷한 고민을 하였던 적이 있다.
- 분기를 controller 에서 해야 하는가 service 에서 해야 하는가. -
service
- repository 에 대한 단건 조회시 NotFound 예외 처리에 대한 코드가 중복되는데 이를 Query Layer를 추가해 해결하자! - 당시엔 좋은 결정이라고 생각했지만 적용 이후에 느낀점은 별로였다.
- 아키텍처가 불필요하게 복잡해졌다. 아키텍처 적으로 충분히 고민하지 않고 도입된 컴포넌트 였기에 이를 영속성 계층으로 보아야 할지 서비스 계층으로 보아야 할지 새로운 레이어로 보아야 할지도 고민, 논의가 필요해졌다.
- 이후에 잠정적으로 내린 결론은 예외 전환 AOP를 커스텀하게 활용하거나 NotFound 예외 처리 정도는 영속성 계층에서 담당하도록 하는 방법이다. 이 방법이 훨씬 좋아보인다.
테스트할 컴포넌트만 늘어나고 아키텍처를 위한 아키텍처처럼 느껴지기 시작했다.
- 하지만 위와 같은 개발자의 다짐, 약속에도 불구하고 몇년뒤 그 프로젝트를 보면 추가된 새로운 레이어역시 비즈니스 로직 범벅이 되어 오래전 고민했던 문제를 다시 만나게 될 것이다.
- 팀 프로젝트에서 Query Layer를 추가하고 겪었던 문제이다.
- 역시 당시에는 점점 복잡해지는 의존관계를 눈치채지 못한채 해당 계층을통해 서로다른 도메인의 CREATE/UPDATE/DELETE 요청을 감추는 아키텍처적으로 훌륭한 결정이라고 생각했다.
- 역시 이후에 잠정적으로 내린 결론은 위와 같은 방식은 앞서 서술한 여러 단점 때문에 효율적이지 않다는 것이다. 이 문제는 팀원 중 한분이 제안하여 적용을 시도해보았던 CQRS, 혹은 이 글의 주제인 육각형 아키텍처가 좋은 해결방법으로 보인다.
분명 NotFound 에 대한 예외를 위해 추가된 layer 였지만 점점 해당 컴포넌트의 책임이 커지고 서로 다른 도메인에서 필요로 하는 형태에 맞게 repository 에서 조회한 엔티티를 가공하는 비즈니스 로직으로서의 역할까지 담당하게 되었다.
- 이 외에도 application logic 이 외부 database 혹은 다른 service 에 강하게 결함 되어있다면 외부
infrastructure
의 가용 상태가 application 의 개발에 영향을 줄 것이다. 이는 개발을 지연 시키며 사람 사이의 안좋은 관계를 낳을 수 있다.
Nature of the Solution
위의 두가지 문제는 모두 비즈니스 로직과 외부 엔티티와의 interaction 이 얽혀서 발생한 문제이다. 이제부터 언급할 비대칭성은 “left” 와 “right” 에 관련된 것이 아니라 애플리케이션의 “inside” 와 “outside” 에 관한 것이다. 지켜야 할 규칙은 “inside” 에 참여하는 코드가 “outside” 에 노출되면 안된 다는 것이다.
육각형은 시각적으로 아래의 두가지를 강조한다,
- inside-outside asymmetry
- 포트는 여러개가 될 수 있다.
고민거리
- 여러 하위 도메인간의 Application 에서의 의존관계에 대한 Rule 및 패키지 구조
- 웹 Adapter 에서 받은 요청을 Application 에 전달하는 방식
- 그대로 전달
request
→command
등의 추가적인 model 을 두어 전달request
→domain entity
로 바꾸어 model 로 사용하기
- 영속성 Adapter 를 위한 Model 을 추가 할 것인가?
- 예 (XXXJpaEntity 를 별도로 사용)
- Query
- Application 을 ByPass 할 것인가?
- 매핑은 어떻게 할 것인가
- Command
- Update 와 같은 비즈니스 로직을 어떻게 구현 할 것인가?
- 아니오 (그냥 domain model 에 @Entity 와 같은 어노테이션 허용)
- 엔티티 간의 연관관계 참조 및 Lazy Loading 사용, Join 을 사용하는 범위에 대한 Rule