기록이 힘이다.

3. 마이크로서비스 애플리케이션 아키텍처 본문

IT서적/도메인 주도 설계로 시작하는 마이크로서비스 개발

3. 마이크로서비스 애플리케이션 아키텍처

dev22 2023. 8. 13. 11:30
728x90

비즈니스 로직은 어디에? - 관심사의 분리

비즈니스 로직이란 보통 시스템의 목적인 비즈니스 영역의 업무 규칙, 흐름, 개념을 표현하는 용어다. 개발자의 역할은 문제 영역의 비즈니스 로직을 분석 및 이해하고 프로그래밍 언어라는 도구로 잘 표현하는 일이다. 여기서 잘 표현한다는 것은 기능이 잘 동작하는 것과 더불어 이해하기 쉽고 변경하기 쉬운 시스템을 만드는 것을 의미한다. 

 

설계 원칙 중 관심사의 분리라는 원칙이 있다. 이것은 시스템의 각 영역이 처리하는 관심사가 분리되어 잘 관리돼야 한다는 의미이고, 이 원칙은 시스템을 이해하고 변경하기 쉽게 만들어 준다. 이 원칙에 따라 각 영역은 고유 관심사에 의해 분리되고 집중돼야 한다.

 

모듈화 및 계층화도 이 같은 원칙에 기인한다. 특히 비즈니스를 표현하는 비즈니스 로직 영역과 기술 문제를 처리하기 위한 기술 영역은 철저히 분리하는 것이 좋다. 

 

기술과 비즈니스 로직을 분리했을 때 복잡성이 낮아지고 유지보수성도 높아진다. 

 

데이터베이스 중심 아키텍처의 문제점

'데이터베이스 중심 아키텍처'란 특정 관계형 데이터베이스에 의존한 데이터 모델링을 수행한 다음 이 물리 테이블 모델을 중심에 두고 애플리케이션을 구현하기 위한 사고를 하는 방식이다. 

 

앞에서 비즈니스 민첩성을 위해서는 유연성과 확장성이 중요하다고 한 바 있다. 예를 들어, 한 회사에서 비즈니스의 특정 기능을 위해 읽기에 최적화된 NoSQL 저장소로 교체하기로 결정했다고 하자. 그런데 이러한 시스템 구조에서는  저장소를 변경하려고 해도 쉽게 변경할 수가 없다. 왜냐하면 저장 기술과 비즈니스 로직이 끈끈하게 붙어 있기 때문에 저장소를 변경했을 때 모든 것을 다시 구현해야 한다고 개발팀이 판단했기 때문이다. 

 

웹, 모바일, 명령형 인터페이스(CLI), IOT 기기 등 여러 디바이스의 입출력을 지원해야 하고, 관계형 데이터베이스, 메모리 데이터베이스, NoSQL,  메시지 큐까지 다양한 저장소와의 연계가 필요하다. 

즉, 클라우드의 풍부한 자원 환경에서는 애플리케이션 자체의 성능보다는 애플리케이션의 확장성과 유연함이 더 중요하다. 따라서 앞에서 언급한 관심사의 분리 원칙에 따라 끈끈하게 결합돼 있던 비즈니스 로직 처리와 데이터 처리를 철저히 분리하는 것이 반드시 필요하다. 

 

헥사고날 아키텍처와 클린 아키텍처

 

레이어드 아키텍처

레이어드 아키텍처(계층형 아키텍처)를 구성하는 레이어는 많은 사람들이 혼동하는 물리적인 개념과 달리 논리적인 개념이다. 계층은 설계자들이 복잡한 시스템을 분리할 때 흔히 사용하는 패턴 중 하나로, 애플리케이션이 내부에서 처리하는 관심사를 논리적으로 구분한다. 

특히 인터페이스를 통한 의존성 분리는 인터페이스를 구현하는 구현체를 다양하게 해주는 다형성을 추구함으로써 제어 흐름을 간접적으로 전환하게 해준다. 

일반적인 레이어드 아키텍처에서 OCP가 위배되는 까닭은 모든 계층이 각기 자신이 제공하는 기능에 대한 추상적인 인터페이스를 직접 정의하고 소유하고 있는 구조이기 때문이다. 

헥사고날 아키텍처

레이어드 아키텍처에 DIP를 적용해도 한계가 있다. 애플리케이션을 호출하는 다양한 시스템의 유형과 애플리케이션과 상호작용하는 다양한 저장소가 존재한다. 단방향 계층구조에서는 이러한 점을 지원하기 힘들다. 다방면으로 열려있는 헥사고날 아키텍처는 이러한 문제점을 해결할 수 있다. 

'포트 앤드 어댑터 아키텍처'라고도 한다.

헥사고날 아키텍처의 가장 큰 특징은 고수준의 내부 영역이 외부의 구체 어댑터에 전혀 의존하지 않게 한다는 것이다. 이를 가능하게 하는 것이 내부 영역에 구성되는 포트다. 

 

클린 아키텍처

클린 아키텍처는 로버트 C. 마틴이 제안한 아키텍처로서 헥사고날 아키텍처의 아이디어와 매우 유사하다. 

마이크로서비스의 내부 구조 정의 

지금까지 레이어드 아키텍처와 헥사고날 아키텍처, 클린 아키텍처를 살펴봤다. 이 같은 아키텍처는 기존의 모노리스 애플리케이션 유형에도 통용되는 아키텍처로서 마이크로서비스만을 위한 아키텍처는 아니다. 그렇지만 최근 들어 더욱 강조되고 있는 까닭은 이러한 아키텍처 구조가 마이크로서비스가 지향하는 유연성, 확장성을 지원하는 구조이기 때문이다. 

 

바람직한 마이크로서비스의 내부 아키텍처: 클린 마이크로서비스

분리해도 복잡성은 이전되고 그 안의 복잡성을 통제할 필요가 있다는 사실은 변하지 않는다.

마이크로서비스의 내부 구조를 정의할 때 반드시 고려해야 할 한 가지는 마이크로서비스 시스템에서 정의해야 할 마이크로서비스의 내부 구조가 다양할 수 있다는 것이다. 왜냐하면 마이크로서비스는 앞에서 살펴본 것처럼 자율적인 마이크로서비스 팀에 의한 폴리글랏한 내부 구조를 가질 수 있기 때문이다. 

-지향하는 관심사에 따라 응집성을 높이고 관심사가 자른 영역과는 의존도를 낮추게 해야 한다.

-업무 규칙을 정의하는 비즈니스 로직 영역을 다른 기술 기반 영역으로부터 분리하기 위해 노력한다. 

-세부 기술 중심, 저수준의 외부 영역과 핵심 업무 규칙이 정의된 고수준의 내부 영역으로 구분한다.

-고수준 영역은 저수준 영역에 의존하지 않게 해야 하며, 저수분 영역이 고수준 영역에 의존하게 해야 한다. 

-저수준 영역은 언제든지 교체, 확장 가능해야 하며, 이 같은 변화가 고수준 영역에 영향을 줘서는 안 된다. 

-자바처럼 인터페이스 및 추상 클래스를 지원하는 언어의 경우 저수준 영역의 구체 클래스가 고수준 영역의 추상 인터페치스에 의존하게 하는 의존성 역전의 원칙을 적용한다. 

-인터페이스는 고수준의 안정된 영역에 존재해야 하며, 저수준의 어댑터가 이를 구현한다.

내부 영역 - 업무 규칙

업무 규칙을 정의하는 내부 영역에는 서비스 인터페이스, 서비스 구현체, 도메인, 리포지토리 인터페이스, 도메인 이벤트 인터페이스, API 프락시 인터페이스가 존재한다. 

서비스 인터페이스는 외부 영역이 내부 영역에 대해 너무 많이 알지 못하게 하는 역할을 한다. 

서비스 인터페이스가 없다면 추이 종속성이 발생할 수 있다(정보 은닉 효과도 있다).

리포지토리 인터페이스, 도메인 이벤트 인터페이스, API 프락시 인터페이스는 의존 관계 역전의 원칙을 지원한다. 더 안정된 곳인 고수준 영역에 인터페이스가 존재하고 저수준의 외부 어댑터가 이러한 인터페이스를 구현하게 한다.

다음으로 고민해야 될 사항은 비즈니스 로직의 핵심인 서비스와 도메인이다. 서비스와 도메인은 클린 아키텍처의 유스케이스와 엔티티의 역할과 같다. 도메인은 비즈니스 개념을 표현하고 서비스는 도메인을 활용해 시스템 흐름 처리를 수행한다. 

 

트랜잭션 스크립트 패턴

절차식 프로그래밍 방식과 같기 때문에 객체지향 지식이 없어도 일반적으로 쉽게 이해할 수 있는 구조이고 기존 데이터베이스 중심 아키텍처에 익숙하다면 더 쉽게 적응할 수 있다. 이 패턴은 비즈니스가 간단한 경우에는 쉽게 적용할 수 있따.

 

도메인 모델 패턴

도메인 객체가 데이터뿐만 아니라 비즈니스 행위를 가지고 있으며, 도메인 객체가 소유한 데이터는 도메인 객체가 제공하는 행위에 의해 은닉된다.

 

도메인 객체는 각 비즈니스 개념 및 행위에 대한 책임을 수행하고, 서비스는 비즈니스 유스케이스를 구현하기 위해 서비스의 행위를 도메인 객체에 일부분 위임해서 처리한다. 

핵심은 도메인 모델이기 때문에 객체지향 지식에 대한 경험과 역량이 필요하다. 복잡한 비즈니스 로직이 많은 마이크로서비스의 구조로 선택하는 것이 좋다. 

 

도메인 주도 설계의 애그리거트 패턴

점점 복잡해질 수 있는 객체 모델링의 단점을 보완한 패턴이라 볼 수 있다. 도메인 모델링을 하다 보면 객체 간의 관계를 참조로 표현하게 되는데 참조로 정의할 경우 일대다 관계의 객체를 쉽게 사용할 수 있다는 장점이 있다. 그렇지만 업무가 복잡해지면 참조로 인한 다단계 계층 구조가 생기고 점점 참조 관계가 복잡해지고 무거워질 수 있다.

최상위에 존재하는 엔티티를 중심으로 개념의 집합을 분리한 것이 애그리거트 패턴이다. 

 

외부 영역 - 세부사항

외부 영역은 내부 영역의 서비스 인터페이스를 사용하는 인바운드 어댑터와 내부 영역에서 선언한 아웃바운드 인터페이스를 구현하는 다양한 어댑터로 구성한다. 어댑터는 플러그인처럼 언제든지 교체되거나 확장될 수 있어야 한다. 따라서 내부 영역이 먼저 정의된 후에 외부 영역의 세부사항은 늦게 정의돼도 상관없도록 해야 한다. 이 같은 방식으로 소프트웨어를 부드럽게 만든다. 

 

 

 

저장소 처리 어댑터

일반적으로 트랜잭션 스크립트 패턴을 사용할 경우 SQL 매핑 방식을 사용하고, 도메인 모델 패턴을 사용할 경우 OR 매핑 방식을 많이 선택한다. 

SQL 매핑 방식의 프레임워크로는 마이바티스가 가장 많이 사용되고 OR 매핑 방식으론ㄴ JPA나 스프링 데이터가 많이 사용된다.