마이크로서비스 내부아키텍처 - 2회 : 클린 아키텍처와 헥사고널 아키텍처
Updated:
마이크로서비스 내부아키텍처 - 2회 : 클린 아키텍처와 헥사고널 아키텍처
마이크서비스 내부 아키텍처에 대해 살펴보자. 3회에 걸쳐서 기존 아키텍처의 문제점 및 이를 개선하고자 했던 아키텍처 변화양상과 이를 반영한 어플리케이션 구조에 대해 알아 보겠다.
클린 아키텍처와 헥사고널 아키텍처
그동안 어플리케이션의 구조를 유연하게 하기 위한 많은 아이디어들이 존재해 왔다. 이번 포스트에서는 가장 일반적으로 많이 쓰이는 레이어드 아키텍처(Layered Architecture )에 대해 설명하고 최근의 마이크로서비스 설계자들이 마이크로서비스 내부구조를 유연하게 가져가기 위해 적용하고 있는 헥사고널 아키텍처(Hexagonal Architecture )와 클린 아키텍처(Clean Architecture)에 대해 알아보도록 하겠다.
레이어드 아키텍처
레이어드 아키텍처(계층 형 아키텍처:Layered Architecture)를 구성하는 레이어는 많은 사람들이 혼동하는 물리적인 티어(Tier)의 개념과 달리 논리적인 개념이다. 티어는 물리적인 장비, 서버 컴퓨터 등의 물리 층을 의미하고 레이어는 티어 내부의 논리적인 분할을 의미한다. 아래 그림과 같이 물리적인 서버 티어의 레이어(이하:계층)를 프레젠테이션(Presentation) , 비지니스 로직(Business Logic), 데이터 액세스(Data Access) 3개의 논리적인 계층으로 구분할 수 있다.
계층은 설계자들이 복잡한 시스템을 분리할 때 흔히 사용하는 패턴 중 하나로 어플리케이션이 내부에서 처리하는 관심사를 논리적으로 구분한다. 다음은 마틴 파울러가 그의 책 ‘엔터프라이즈 애플리케이션 아키텍처 패턴(Enterprise Application Architecture Pattern)’ 에서 구분한 레이어드 아키텍처 패턴 전형적인 유형이다. 아키텍트가 의도하는 방향에 따라 여러가지로 구분 가능하나 일반적으로 프레젠테이션, 비즈니스 로직, 데이터 액세스의 3계층으로 구분하는 경향이 일반적이다.
프레젠테이션 층의 관심사는 화면 표현 및 전환 처리이고 비즈니스 로직층의 관심사는 비즈니스 개념 및 규칙, 흐름 제어이며 데이터 액세스 층의 관심사는 데이터 처리이다.
레이어드 아키텍처는 레이어 간 응집성을 높이고 의존도를 낮추기 위해 몇 가지의 규칙들을 가지고 있는데 다음과 같다.
- 상위 계층이 하위 계층을 호출하는 단 방향성을 유지한다.
- 상위 계층은 하위의 여러 계층을 모두 알 필요 없이 바로 밑의 근접 계층만 활용한다.
- 상위 계층이 하위 계층에 영향을 받지 않게 구성해야 한다.
- 하위 계층은 자신을 사용하는 상위 계층을 알지 못하게 구성해야 한다.
- 계층 간의 호출은 인터페이스를 통해 호출하는 것이 바람직하다. (구현 클래스에 직접 의존하지 않음으로써 약한 결합을 유지해야 한다.)
특히 인터페이스를 통한 의존성 분리는 인터페이스를 구현하는 구현체를 다양하게 해주는 다형성을 추구함으로써 제어 흐름을 간접적으로 전환하게 해준다. 아래 그림을 보면 상위 계층은 직접적으로 하위 계층을 호출하지 않고 추상적인 인터페이스를 의존한다. 이럴 경우 하위레이어는 추상적 인터페이스를 만족하는 다양한 방식의 구현체를 선택적으로 적용할 수 있다.
이러한 방식은 로버트 C 마틴이 정의한 객체지향의 원칙의 의존성 역전 원칙(Dependency Inversion Principle) 을 만족하는 것처럼 보인다. 의존성 역전의 원칙은 ‘유연성이 극대화된 시스템’이란 소스 코드 의존성이 추상에 의존하며 구체에는 의존하지 않아야 한다’라고 말하기 때문이다.
그렇지만 개방 폐쇄의 원칙(OCP: Open-Closed Principle) 까지 살펴본다면 문제가 있다. OCP는 소프트웨어 개체는 확장에는 열려 있어야 하고 변경에는 닫혀 있어야 한다는 원칙이다. 이는 개체의 행위는 확장할 수 있어야 하지만 이때 개체를 변경해서는 안된다는 말이다.
그러나 계층 방식은 인터페이스를 통해 의존성을 낮춘다 해도, 일반적으로 추상적인 인터페이스가 각 계층의 제일 상위에 위치하는 구조로 제어의 흐름(Flow of Control)이 상위에서 하위로 흐르게 되고 이에 따른 소스코드의 의존성은 제어의 흐름을 따르게 된다. 따라서 하위 계층의 유형이 추가되어 확장될 때 닫혀 있어야 할 상위계층이 의존을 받을 수 밖에 없다.
프레젠테이션, 비지니스로직, 데이터 액세스 의 3계층으로 살펴보면 제일 마지막에 있는 데이터 액세스 계층이 변경되었을 때 비지니스로직 계층이 변경되면 안되는데 위의 그림을 보면 비지니스로직 계층의 클래스가 데이터 액세스 계층에 존재하는 인터페이스를 의존하기 때문에 데이터 계층의 영향을 받게 된다.
문제는 데이터 엑세스 인터페이스 위치이다. 데이터 엑세스 인터페이스는 데이터 엑세스 계층에 존재한다. 일면 당연해 보이지만 그런 방식은 항상 하위 계층에 의존해야 한다. 그렇지만 어플리케이션에서는 비지니스 로직이 핵심이라고 하였다. 즉 문제 중심 영역이다. 그렇기 때문에 비지니스 로직을 보통 고수준 영역이라고 하고 프레젠테이션 계층 및 데이터 액세스 계층을 저수준 영역이라고 한다. 고수준 영역은 문제 중심 영역이므로 보호를 받아야 하고 따라서 저수준 영역의 변경, 확장에 영향을 받지 않아야 한다. 그러나 위에서처럼 일반적인 레이어드 아키텍처의 규칙만을 따르면 고수준 영역이 저수준영역에 의존하게 되고 영향을 받게 된다.
여기서 의존성의 역전 원칙(DIP)적용의 필요성이 생긴다. DIP는 데이터 액세스 계층에서 정의한 인터페이스의 위치를 경계 넘어 비지니스로직 계층으로 이동시킨다. 그러면 데이터 액세스 계층의 구현체는 비지니스 로직의 계층의 인터페이스를 바라볼 수 밖에 없다.
즉 데이터 액세스 계층이 구현해야 할 인터페이스를 아래 그림과 같이 보다 고수준의 비지니스 로직 계층에서 정의하게 함으로써 기존에 위에서 아래로 흘렀던 의존관계를 역전시키고 고수준이 저수준의 변경에 영향을 받지 않도록 해 주는 것이다.
헥사고날 아키텍처
허나 레이어드 아키텍처에 DIP를 적용해도 한계가 존재한다. 프레젠테이션 계층 와 데이터 액세스 계층을 보통 저수준 계층으로 정의한다고 했는데 현대 어플리케이션의 활용은 이러한 2가지 계층 말고도 다양한 인터페이스를 필요로 한다. 즉 어플리케이션을 호출하는 시스템의 유형과 어플리케이션과 상호작용하는 다양한 저장소가 존재한다. 헥사고날 아키텍처(육각형, Hexagonal Architecture) 는 이러한 문제점을 해결할 수 있다.
헥사고날 아키텍처는 엘리아스 쿡번(Alistair Cockburn )이 제시한 아키텍처로 ‘ 포트 엔 어댑터 아키텍처(ports and adapters architecture)’ 라고도 부른다. 아래 그림을 통해 간단히 살펴보면 고수준의 비지니스 로직을 표현하는 내부 영역과 인터페이스 처리를 담당하는 저수준의 외부 영역으로 나눈다.
내부 영역 은 순수한 비지니스로직을 표현하는 기술 독립적인 영역이다.그리고 외부영역과 연계되는 포트를 가지고 있다.
외부 영역 은 외부에서 들어오는 요청을 처리하는 인 바운드 어댑터(Inbound Adapter)와 비지니스 로직에 의해 호출되어 외부와 연계되는 아웃바운드(Outbound Adapter) 어댑터로 구성된다.
헥사고널의 가장 큰 특징은 고수준의 내부 영역이 이런 어댑터에 전혀 의존하지 않게 한다는 것이다. 그것을 가능하게 하는 것이 내부 영역에 구성되는 포트이다. 포트는 인 바운드 / 아웃 바운드 포트로 구분되는데, 인 바운드 포트는 내부 영역 사용을 위해 표출된 API이며, 외부 영역의 인 바운드 어댑터가 호출한다. 아웃바운드 포트는 내부 영역이 외부를 호출하는 방법을 정의한다. 여기서 DIP 원칙과 같이 아웃바운드 포트가 외부의 아웃바운드 어댑터를 호출하여 외부 시스템과 연계하는 것이 아니라 아웃바운드 어댑터가 아웃바운드 포트에 의존하여 구현된다.
외부영역에 존재하는 어댑터 의 종류를 살펴보면 인 바운드 어댑터는 REST API를 발행하는 컨트롤러, 웹 페이지를 구성하는 스프링 MVC 컨트롤러, 커맨드 핸들러, 이벤트 메시지 구독 핸들러 등이 될 수 있고, 아웃바운드 어댑터는 데이터 액세스 처리를 담당하는 DAO , 이벤트 메시지를 발행하는 클래스, 외부 서비스를 호출하는 프록시 등이 될 수 있다.
클린 아키텍처
클린 아키텍처(Clean Architecture)는 로버트 C 마틴이 제안한 아키텍처로 헥사고널 아키텍처의 아이디어와 매우 유사하다. 앞서 언급한 것처럼 로버트는 소프트웨어는 행위적/구조적 두 종류의 가치를 가지며 구조적 가치가 더 중요하다고 말한다. 왜냐하면 소프트웨어를 부드럽게 만드는 것이 구조적 가치이기 때문이다. 그렇다면 소프트웨어를 부드럽게 유지하는 방법은 무엇일까? 즉 구조 중에 선택할 수 있는 것을 가능한 오랫동안 열어두는 것이다. 특히 열어둬야 할 선택사항은 바로 중요치 않은 세부사항이다.
마틴은 아래 그림같이 클린 아키텍처를 여러 겹의 둘러싸인 영역으로 표현하며 엔티티, 유스케이스, 그 외 세부사항으로 구분한다.
제일 중앙에는 엔티티 가 있다. 업무 규칙은 사업적으로 수익을 얻거나 비용을 줄일 수 있는 규칙 또는 절차를 말한다. 이런 업무 규칙은 수동으로 처리할 수 있지만 시스템으로도 자동화 할 수 있다. 예를 들면 쇼핑몰의 물건을 사고 파는 규칙, 은행의 이자 계산 규칙 ,도서대여 시스템의 대여/반납 규칙 등 모든 시스템에는 해당 도메인의 업무를 규정하는 핵심 업무 규칙들이 존재한다. 그리고 핵심업무규칙은 보통 데이터를 요구한다. 따라서 핵심 규칙과 데이터는 본질적으로 결합되어 있기 때문에 객체로 쉽게 만들 수 있다. 이런 유형을 ‘엔티티’ 객체 라 한다.
그 다음 엔티티를 감싸는 객체는 유스케이스이다. 유스케이스는 자동화된 시스템을 사용하는 처리 절차를 기술한다. 유스케이스는 애플리케이션에 특화된 업무 규칙을 표현하며 엔티티 내부의 핵심 업무 규칙을 호출하며 시스템을 사용하는 흐름을 담는다. 이때 엔티티 같은 고수준은 저수준의 유스케이스를 알게 하면 안된다.
엔티티는 간단한 객체 여야 하며 프레임워크 데이터베이스 또는 여타 복잡한 것에 의존되어서는 안되고 유스케이스 객체을 통해서만 엔티티를 조작해야 한다.
그리고 그 다음 유스케이스를 감싸고 있는 모든 영역들은 세부사항 이다. 세부사항은 입출력 장치, 저장소, 웹 시스템, 서버, 프레임워크, 통신 프로토콜이 될 수 있으며, 세부사항과 유스케이스 관계를 의존관계 역전의 법칙을 이용하여 플러그인처럼 유연하게 처리해야 한다. 이런 명확한 결합의 분리는 테스트 성 및 개발 독립성, 배포 독립성 강화할 수 있다.