MSA 심장부를 설계하다: Theme1. Hexagonal Architecture

dev_will_d·2024년 3월 28일
1
post-thumbnail

참고

Hexagonal Architecture - 훌륭한 아키텍처는 코드를 보는 좋은 눈을 길러준다.

Architecture의 중요성에 대해 이전 Hexagonal Architecture글에서 많이 언급한거 같다. 이번 시간에는 MSA의 환경에서 Hexagonal Architecture의 효용성과 어떻게 코드를 작성하면 좋을지 이야기 하는 시간을 가져보겠다.


'외부로 부터 비지니스를 지키고 집중하자'는 핵심 철학을 가진 Hexagonal Architecture의 시각적 그림은 위와 같다. 그러나 위의 그림을 보고 Hexagonal Architecture를 이해 하는것은 무리가 있다. 조금더 실전적인 그림을 보자

Hexagonal Architecture 시나리오

이 그림은 필자가 핵사고날 아키텍처를 직접 사용하면서 정리한 시나리오다. 이 시나리오를 만들면서 생각한 생각을 공유하고자 한다.

1. In Adapter 동작

  • 외부 호출에 대한 상호작용을 담당하는 In Adapter의 대표적 사례로는 Controller, Kafka Consumer, Spring Batch가 있다. In Adapter에서는 결국 In Port(UseCase)를 통해 Business를 담당하는 Service를 호출 할 것이다. 이때 요청과 함께 받아온 데이터 AsyncTask, Request Body, Header를 Command로 변환하여 호출한다.

👨🏻‍💻질문) Command로 변환해서 호출하는 이유가 뭔가요?

  • Command는 Service의 함수를 호출하는 실행 단위이다. Command를 만든 이유는 크게 2가지다.
    첫째, 어떠한 값 요청이 들어오든 결국 Command로 변환해서 호출 해야 되니 Validation Check를 Command에서만 하면 된다. 즉, Validation Check의 시점을 하나로 모는 효과를 가진다.
    둘째, 필자는 Header로 부터 전달 받은 값을 서비스 로직에서 사용하기 위해 SuvletHttpRequest를 Service에 전달하는 코드를 본적이 있다. 이 코드는 테스트 코드를 작성할때 문제가 된다. 테스트 코드는 외부의 의존성으로 부터 자유로워야 하고 순수해야 하는데 SuvletHttpRequest에 의해 외부 제약이 생긴다. 이러한 문제를 예방하고자 필자는 Command를 만들었다. Command를 통해 외부 의존성으로 부터 자유롭고 순수해질 수 있다. 그렇다면 flat하게 데이터를 Service에 전달하면 되지 않냐는 반문을 받을 수 있다. 그러나 이 방법은 첫번째 의도를 지킬 수 없다.

2. 여러개의 In Port와 Service

  • 하나의 도메인에서 관심사에 따라 여러개의 서비스가 분리될 수 있다는 점을 강조하기 위해 여러개의 UseCase와 Service를 그렸다.

👨🏻‍💻질문) 하나의 도메인에 존재하는 여러 서비스가 서로가 서로를 참조해도 되나요?

  • Hexagonal Archiecture에서 Service가 외부와 상호작용 한다면 반드시 Port를 통해 상호작용해야 한다는 중요한 원칙에 위배 되기에 필자는 이 부분에 있어서 참조하면 안된다고 생각한다. 원칙을 위배 하는 행위는 지속가능한 개발을 막는 요소 중 하나다. 우리가 원칙을 배워야 하는 이유이기도 하다. 그럼 무엇이 중요할까? Service를 나눈다면 경계를 분명히 하여 잘 나누자.

3. Out Port, Out Adapter

  • Out Port는 Service가 외부의 의존성(Out Adapter)을 호출하기 위한 접합부다. Out Port의 특징중 하나는 추상화 되어 있다는 점이다. 즉, Service 입장에서는 어떠한 외부(Out Adapter)와 통신하는지 모른다. Persistence일 수 있고, Service일 수 있다.

👨🏻‍💻질문) Out Port를 호출할때 flat하게 값을 파라미터로 전달하는 이유는 뭔가요?

  • 위에서 Command에 대한 얘기를 했다. Command는 분명히 Validation Check라는 효용성이 있다. 있었기에 Command를 만들었다. 그러나 Out Port를 호출할때 값 객체를 만들어 봤을때 큰 효용성이 없다. 없어서 만들지 않았다.

👨🏻‍💻질문) Port와 상호작용한 결과를 Response로 정의한 이유는 무엇인가요?

  • Service는 어떠한 외부와 통신하는지 모른다. 그저 추상화된 Port를 통해 외부와 상호작용을 하는것이 전부다. Response를 만든 이유는 만약 특정 의존성의 종속적인 값을 리턴 받는다면 외부의 의존성이 변하는 순간(ex) Spring Data JPA -> Spring Data MongoDB) Service의 비지니스 로직에 영향을 준다. Hexagonal Architecture에서는 Business를 잘 지키고 집중하는 것이 목표다. 즉, Open-Closed Principle을 지켜 비지니스를 지키기 위해 Response를 만들었다.

Package 구조

그렇다면 실제 프로젝트에서는 어떻게 package가 구성되는지 살펴보자. Package 구조는 아래와 같다.
아래와 같은 package 구조를 통해서 위에서 설명한 효용성을 이룰 수 있다.


확장성

각자 도메인에 대해 위의 시나리오를 기반으로 개발하면 여러 육각형들이 생겨날 것이다. 각각의 육각형들은 각각의 Business를 담당하고 필요에 의해 서로 상호작용 할 것이다. 원칙을 준수한 시나리오를 기반으로 각각의 육각형들을 확장해 나갔다. 분명 원칙을 지켜나갔다면 유지보수를 넘어 지속 가능한 소프트웨어가 될것이라 기대한다.

마무리

왜 갑자기 MSA 심장부를 설계하다 (MSA Common) 시리즈에서 Hexagonal Architecture를 설명했는지 의아해 하시는분이 있을것 같다. 일관성은 지속가능한 소프트웨어를 구축하는데 가장 중요한 덕목이다. 일관성을 지키기 위해서 우리는 원칙을 지켜야 한다. Architecture는 이러한 원칙을 지키게 한다. Common을 설계하는 이유가 중복된 코드를 줄여 효율성을 높이자는 것도 맞는 말이지만 더 중요한 사실은 팀의 원칙을 설계하는것이 아닐까? 이러한 관점에서 필자는 Architecture가 진정한 Common이라 생각했다. 그래서 이번 시리즈에 반드시 설명해야 하는 부분이라고 생각하여 글을 작성해 봤다.

긴글 읽어 주셔서 감사합니다😀

profile
질문의 질이 답의 질을 결정한다.

0개의 댓글