3. 마이크로서비스 애플리케이션 아키텍처
3장은 MSA 내부 아키텍처에 대한 내용이다. 실무에서 단기간의 프로젝트에서 설계에 신경 쓰지 않고, 기능 구현에만 몰두하면 이후 유지보수가 매우 힘들어지게 된다. 초기 단계에서의 시스템 설계는 매우 중요한데 이번 장에서는 MSA 내부 아키텍처에 대해 다룬다.
3. 1. 비즈니스 로직은 어디에? - 관심사의 분리
유연하고 확장성 있는 MSA 시스템을 위해서는 마이크로서비스의 내부 구조를 잘 만드는 것도 중요하다. 애플리케이션의 유지보수성을 높이기 위해서는 개인에 의존하기보다는 누구라도 유지보수를 할 수 있게 만들어야 한다. 모든 로직을 SQL 문으로 처리하기보다는 코드로 비즈니스 로직을 구현하는 것을 고려해야 한다. 그러나 실무에서 기능을 개선하면서 Go로 구현된 비즈니스 로직을 모두 SQL 문으로 바꿔야 했던 적이 있는데, 페이지 네이션이 필요한 기능의 경우에는 SQL 문으로 구현하는 것이 필요하기도 하다.
3. 1. 1. 데이터베이스 중심 아키텍처의 문제점
데이터베이스 중심 아키텍처는 데이터를 RDB를 중심으로 하여 애플리케이션을 구현하는 방식으로 위와 같은 구조를 보인다. 이런 아키텍처는 대부분의 성능을 데이터베이스에 의존한다. 비즈니스 로직이 SQL로 구현되면 애플리케이션에서는 처리할 것이 없게 된다. 데이터가 늘어남에 따라 데이터베이스의 성능이 저하된다. 이러한 경우에 사용하는 데이터 저장소를 변경하게 되면 모든 것을 다시 구현해야 할 수도 있다. 이를 피하기 위해 비즈니스 로직 처리와 데이터 처리를 철저하게 분리해야 한다.
3. 2. 헥사고날 아키텍처와 클린 아키텍처
3. 2. 1. 레이어드 아키텍처
레이어드 아키텍처를 구성하는 레이어는 보통 프레젠테이션, 비즈니스 로직, 데이터 액세스의 3개의 논리 계층로 구분한다.
레이어드 아키텍처는 레이어 간 응집성를 높이고 의존도를 낮추기 위해 다음의 규칙을 둔다.
- 상위 계층이 하위 계층을 호출하는 단 방향성을 유지한다.
- 상위 계층은 하위의 여러 계층을 모두 알 필요 없이 바로 밑의 근접 계층만 활용한다.
- 상위 계층이 하위 계층에 영향을 받지 않게 구성해야 한다.
- 하위 계층은 자신을 사용하는 상위 계층을 알지 못하게 구성해야 한다.
- 계층 간의 호출은 인터페이스를 통해 호출하는 것이 바람직하다. (구현 클래스에 직접 의존하지 않음으로써 약한 결합을 유지해야 한다.)
일반적인 레이어드 아키텍처는 OCP가 위배되는데 그 이유는 모든 계층이 자신이 제공하는 기능에 대한 추상적인 인터페이스를 직접 정의하고 소유하고 있기 때문에 이로 인해 상위 계층이 하위 계층의 인터페이스에 의존하게 된다.
이를 해결하기 위해서는 데이터 액세스 계층의 인터페이스를 비즈니스 로직에서 정의하게 하면 된다. 같은 이유로 비즈니스 로직의 인터페이스를 프레젠테이션 계층에서 정의를 해야 하는 것도 고려를 해야 하나, 비즈니스 로직이 핵심 영역이므로 이는 중요도가 떨어지게 된다.
3. 2. 2. 헥사고날 아키텍처
현대 애플리케이션에는 다양한 인터페이스를 필요로 하는데, 레이어드 아키텍처의 단방향 계층 구조로는 이를 지원하기 힘들다. 이는 위와 같은 헥사고날 아키텍처로 해결할 수 있다.
헥사고날 아키텍처는 내부 영역과 외부 영역으로 나뉜다.
- 내부 영역: 순수한 비즈니스 로직을 표현하는 기술 독립적인 영역
- 외부 영역: 외부에서 들어오는 요청을 처리하는 인 바운드 어댑터와 비즈니스 로직에 의해 호출되어 외부와 연계되는 아웃바운드 어댑터로 구성
이 아키텍처에서 중요한 부분은 내부 영역에 구성되는 포트이다. 인바운드 포트는 외부에서 내부를 사용하기 위해 표출되는 API이며, 아웃바운드 포트는 내부에서 외부를 사용하기 위한 API이다.
3. 2. 3. 클린 아키텍처
클린 아키텍처는 헥사고날 아키텍처와 유사하다. 위와 같이 여러 겹으로 표현되며 엔티티, 유스케이스, 세부사항 영역으로 구분한다.
- 엔티티: 해당 도메인의 업무를 규정하는 핵심 업무 규칙. 업무 규칙을 데이터와 결합하여 객체로 만들 수 있다.
- 유스케이스: 자동화된 시스템을 사용하는 처리 절차.
- 세부사항: 입출력 장치, 저장소, 웹 시스템, 서버, 프레임워크, 통신 프로토콜 등
3. 3. 마이크로서비스의 내부 구조 정의
3. 3. 1. 바람직한 마이크로서비스의 내부 아키텍처: 클린 마이크로서비스
위에서 언급한 레이어드, 헥사고날, 클린 아키텍처 등은 모놀리식 시스템을 통제하기 위한 구조이다. 하나의 아키텍처를 가지는 모놀리식 시스템과 달리 마이크로서비스의 내부 구조는 다양한 내부 구조를 가질 수 있다. MSA에서 각 서비스의 목적과 용도에 따라 적절한 개발 언어, 저장소, 내부 아키텍처를 정의하는 것이 바람직하다.
3. 3. 2. 내부 영역 - 업무 규칙
트랜잭션 스크립트 패턴
트랜잭션 스크립트 패턴은 도메인 객체가 행위를 가지지 않고 서비스가 모든 행위를 가지는 패턴이다. 간단한 비즈니스 처리에 용이하며, 데이터베이스 중심 아키텍처에서 많이 사용되는 패턴이다.
도메인 모델 패턴
도메인 모델 패턴은 객체지향 설계를 이용한 패턴이다. 도메인 객체가 행위를 가지고 있으며, 비즈니스 행위에 대한 책임을 수행한다. 이를 통해 각 클래스로 책임이 분산되므로 제대로 설계한다면 코드의 재사용성을 높일 수 있다.
도메인 주도 설계의 애그리거트 패턴
애그리거트 패턴은 도메인 모델 패턴의 1개 이상의 엔티티를 애그리거트로 묶는 패턴이다. 이 패턴에서는 애그리거트를 일관되게 처리하기 위해 다음의 규칙을 부여한다.
- 애그리거트 루트만 참조한다. 애그리거트 내 상세클래스를 바로 참조하지 않고 루트를 통해 참조해야 한다. 수정도 마찬가지이다.
- 애그리거트 간 참조는 객체를 직접 참조하는 대신 기본 키를 사용한다. 기본 키를 사용하면 느슨하게 연관되고 수정이 필요하지 않은 애그리거트을 함께 수정하는 실수를 방지한다.
- 하나의 트랜잭션으로 하나의 애그리거트만 생성, 수정한다.
3. 3. 3. 외부 영역 - 세부사항
API 퍼블리싱 어댑터
API 퍼블리싱 어댑터는 REST API를 처리하는 인바운드 어댑터이다. 내부 영역의 서비스 인터페이스를 호출하여 API에 맞게 데이터를 변환하여 전달하는 것이 바람직하다.
API 프락시 어댑터
API 프락시 어댑터는 다른 서비스의 API를 호출하는 아웃바운드 어댑터이다. 다른 서비스의 API는 REST API, 소켓, SOAP 등 다양한 프로토콜로 구현되어 있을 수 있으니 적절한 통신 방법을 사용해야 한다.
저장소 처리 어댑터
데이터 처리 메커니즘으로는 SQL 매핑 방식과 OR 매핑 방식이 있다. SQL 매핑 방식은 SQL 문을 수동으로 직접 작성하여 사용하는 것이고, OR 매핑 방식은 사용하려는 객체에 따라 자동으로 SQL 문이 생성되는 방식이다. JAVA 기준으로 SQL 매핑 방식의 프레임워크는 마이바티스가 있고, OR 매핑 방식으로는 JPA가 있다. 추가로 팀 전체가 OR 매핑 방식에 익숙해지면 SQL 문의 품질과 생산성 향상을 꾀할 수 있다.
도메인 이벤트 발행 어댑터
서비스 간 비동기 통신에서 전달되는 정보가 도메인 이벤트이다. 도메인 이벤트가 생성되는 위치는 내부 영역이며, 도메인 이벤트 발생 어댑터는 내부 영역의 도메인 이벤트를 아웃바운드로 특정 저장소에 발행하는 역할을 수행한다.
도메인 이벤트 핸들러
도메인 이벤트 핸들러는 발행된 도메인 이벤트를 수신하기 위한 인바운드 어댑터이다. 외부에서 발행된 도메인 이벤트를 구독하여 내부로 전달하는 일을 수행한다.
마이크로서비스의 각종 내부 아키텍처의 구조에 대한 내용을 보았다. 처음에는 이해하기 어려웠으나 두번째 읽으면서 패턴들에 대한 부분이 이해가 되기 시작했다. 이번 장에서는 마이크로서비스의 유연함을 위해 내부 아키텍처도 유연함이 필요하다는 것을 배울 수 있었다.
출처: https://engineering-skcc.github.io/microservice%20inner%20achitecture/inner-architecture-3/