[MicroService] 3. MicroService Architecture
비즈니스 로직 (Business Logic) : 시스템이 목표로 하는 비즈니스 영역의 규칙, 흐름, 개념
- 소프트웨어의 가치는?
by Clean Architecture
- 행위 기치 : 소프트웨어의 기능
- 구조 가치 : 소프트웨어의 아키텍처 → 소프트웨어를
Soft하게 만드는 것→ 코드나 설계의 구조를 깔끔히 하는 대신 기능 구현만 목표하면, 엉망이 된 소프트웨어 대처에 더 많은 비용 발생
- 문제 영역에서 비즈니스 로직을 분석 및 이해하고, 프로그래밍 언어로 잘 표현하는 것이 개발자의 역할
- 기능이 정확하게 동작하는 것과 더불어, 이해하기 쉽고 변경하기 쉬운 시스템을 만드는 것
관심사의 분리 (Separation of Concerns) : 시스템의 각 영역이 처리하는 관심사가 분리해 관리되어야 함
- 각 영역은 고유의 관심사에 의해 분리되어 집중되어야 함 → 모듈화, 계층화
- 비즈니스 로직은 어플리케이션의 핵심 영역이므로, 기술에 영향을 적게 받게 설계해야
- 기술과 비즈니스 로직을 분리했을 때, 어플리케이션의 복잡성이 낮아지고 유지보수성이 높아짐
- 비즈니스 로직을 모두가 이해할 수 있게 구조화된 객체 모델로 표현되어야 함
→ 유연하고 확장성 있는
MSA시스템을 개발하려면, 마이크로서비스의 내부 구조를 어떻게 유연하게 만들지 고민해야!
데이터베이스 중심 아키텍처 (Database Centric Architecture) : 데이터 중심 서비스 구현
- 특정한 관계형 데이터베이스에 의존한 데이터 모델링을 수행한다.
- 물리 테이블 모델을 중심에 두고 어플리케이션을 구현한다. (예시 :
SpringBoot Application)Controller,Service,DTO,Repository & Entity로 어플리케이션을 구성SQL매핑 프레임워크인MyBatis나Java Persistence API인JPA로 데이터를 처리
데이터베이스 중심 아키텍처에서 비즈니스 로직은 서비스에 존재해야 한다. 그러나,
- 흐름 제어 로직만 서비스에 존재하고, 비즈니스 개념이나 규칙은 테이블이나
SQL질의로 존재한다.DTO는SQL질의를 통해 정보를 가져오는 정보 묶음 (Information Holder)의 역할밖에 할 수 없다.
- 간단한 처리 로직의 경우에는 적합하나, 업무가 다양해지면 점점 복잡성을 제어할 수 없음
- 업무 개념이 특정 관계형 테이블 데이터베이스의 테이블로 표현되어, 데이터 질의어인
SQL가 필요한 경우가 있음 - 서비스의 비즈니스 개념과 규칙이 대부분 데이터베이스에 표현 → 성능이 데이터베이스에 의존
- 데이터가 늘어남에 따라 데이터베이스의 성능은 지속적으로 떨이짐
- 데이터베이스 서버의
Scale-out과SQL질의문 튜닝에 의존하게 됨
- 데이터베이스 서버의
- 데이터가 늘어남에 따라 데이터베이스의 성능은 지속적으로 떨이짐
MSA 시스템의 확장성과 유연성을 위한 내부 어플리케이션의 아키텍처 구조
- 클라우드의 풍부한 자원 환경에서는, 어플리케이션 자체의 성능보단 어플리케이션의 확장성과 유연성이 더 중요하다!
계층형 아키텍처 (Layered Architecture)
- 티어 (
Tear) : 물리적인 장비나 서버 컴퓨터 등의 물리 계층 - 레이어 (
Layer) : 물리적인 티어 내부에서 어플리케이션이 처리할 관심사를 구분하는 논리 계층- 프레젠테이션 (
Presentation) : 화면 표현 및 전환 처리 - 비즈니스 로직 (
Business Logic) : 비즈니스 개념, 규칙, 흐름 제어 - 데이터 액세스 (
Data Access) : 데이터 처리
- 프레젠테이션 (
계층형 아키텍처를 개발하기 위하여 지켜야 할 규칙!
- 상위 계층이 하위 계층을 호출하는 단방향성을 유지한다.
- 상위 계층은 하위의 여러 계층을 모두 알 필요 없이 바로 밑의 근접 계층만을 활용한다.
- 상위 계층이 하위 계층에 영향받지 않게 구성해야 한다.
- 하위 계층은 자신을 사용하는 상위 계층을 알지 못하게끔 구성해야 한다.
- 계층 간의 호출은 인터페이스를 통해 호출하는 것이 바람직하다.
- 구현 클래스에 직접 의존하지 않게끔 하여 약결합을 유지해야 한다.
- 의존성 역전의 원칙 (
DIP) : ‘유연성이 극대화된 시스템은 코드 의존성이 구체가 아닌 추상에 의존한다.’ →O - 개방 폐쇄의 원칙 (
OCP) : ‘소프트웨어 객체는 확장에 열리되, 변경에 닫혀 있어야 한다.’ →X- 개체의 행위는 확장될 수 있지만, 이때 객체를 변경해서는 안된다. 그러나 상위 계층에서 하위 계층으로 제어의 흐름 (
Flow of Control)이 흐르는 계층형 아키텍처에서 소스 코드의 의존성 또한 그 방향을 따를 수 밖에 없다. - 상위 계층이 하위 계층의 구체 클래스가 아닌 추상 인터페이스에 의존시키고 그 인터페이스의 구현체를 달리해 의존성을 줄이면서 다형성을 유지할 수 있지만, 인터페이스는 그 계층이 정의하는 추상 특성의 한계를 벗어날 수 없다.
→ 즉, 하위 계층의 유형이 추가되어 확장될 때, 닫혀 있어야 할 상위 계층이 하위 계층이 정의한 특성이 영향받는다!
- 개체의 행위는 확장될 수 있지만, 이때 객체를 변경해서는 안된다. 그러나 상위 계층에서 하위 계층으로 제어의 흐름 (
DIP를 철저히 적용하여OCP가 가능하게 하는, 의존 관계 역전의 방법 또한 존재한다.
- 프레젠테이션, 비즈니스 로직, 데이터 액세스을 갖는 3계층 시스템이라 가정할 때,
- 고수준 영역인 비즈니스 로직이 저수준 영역인 데이터 액세스에 의존한다. (싱위 != 고수준 → 중요도)
- 그러나 데이터 액세스 계층에서 정의한 인터페이스가 경계를 넘어 비즈니스 로직 계층에 존재하도록 하면,
- 데이터 액세스의 구현체는 비즈니스 로직 계층의 인터페이스만을 보게 된다.
→ 아례 계층이 위 계층에 의존하게 하는 것이이 의존 관계 역전!
헥사고날 아키텍처 (Hexagonal Architecture)
DIP를 적용한 계층형 아키텍처의 한계?
- 프레젠테이션, 데이터 액세스 계층만이 아닌 다양한 인터페이스를 필요로 하는 현대 어플리케이션
- 어플리케이션을 호출하는 다양한 시스템 유형과 어플리케이션과 상호작용하는 다양한 저장소가 존재
→ 단방향 계층 구조가 가지는 근본적인 한계를 넘어서자!
- 포트 엔드 어댑터 아키텍처 (
Port and Adapter Architecture)- 저수준의 외부 영역 : 인터페이스 처리를 담당
- 인바운드 어댑터 (
Inbound Adaptor) : 서비스 외부에서 들어오는 요청을 처리REST API를 발행하는 컨트롤러- 웹 페이지를 구성하는 스프링
MVC컨트롤러 - 이벤트 메시지를 구독하는 이벤트 핸들러 등
- 아웃바운드 어댑터 (
Outbound Adaptor) : 서비스 내부의 비즈니스 로직에 의해 호출되어 외부와 연계- 데이터 액세스 처리를 담당하는
DAO - 이벤트 메시지를 발행하는 이벤트 클래스
- 외부 서비스를 호출하는 프락시 (
Proxy)
- 데이터 액세스 처리를 담당하는
- 인바운드 어댑터 (
- 고수준의 내부 영역 : 순수한 비즈니스 로직 표현
- 외부 영역과 연계되는 포트 (
Port)를 가짐 → 어댑터가 포트를 호출- 인바운드 포트 (
Inbound Port) : 내부 영역의 사용을 위해 표출된API - 아웃바운드 포트 (
Outbound Port) : 내부 영역이 외부를 호출하는 방법 정의
- 인바운드 포트 (
- 외부 영역과 연계되는 포트 (
- 저수준의 외부 영역 : 인터페이스 처리를 담당
→ 고수준의 내부 영역이 외부의 구체 어댑터에 전혀 의존하지 않게끔 한다.
클린 아키텍처 (Clean Architecture)
Robert C. Martin: ‘소프트웨어는 행위 가치와 구조 가치를 가지며, 구조 가치는 더 중요하다.’
- 왜? : 소프트웨어를 더 유연하게 하는 것이 구조 가치이므로
- 소프트웨어를 유연하게 유지하는 방법? : 구조 중에서 선택할 수 있는 사항들을 오랫동안 여는 것
- 특히 열어두어야 할 선택 사항은, 중요하지 않는 세부사항
- 엔티티 (
Entity) : 비즈니스 업무 규칙 (사업적으로 수익을 얻거나 비용을 줄일 수 있는 규칙)- 모든 시스템에는 해당 도메인의 업무를 규정하는 업무 규칙이 존재 → 시스템 내에서 자동화
- 업무 규칙과 데이터를 결합하여 엔티티 객체로 만들 수 있음
- 유스케이스 (
UseCase) : 어플리케이션 업무 규칙- 자동화된 시스템을 사용하는 처리 절차를 기술
- 어플리케이션에 특화된 업무 규칙을 표현하며, 엔티티 내의 업무 규칙을 호출해 시스템을 사용하는 흐름을 닫음
- 엔티티는 프레임워크, 데이터베이스에 의존하지 않고 유스케이스 객체를 통해 조작되는 간단한 객체로 존재해야 함
- 컨트롤러 (
Controller) : 인터페이스 어댑터 (게이트웨어를 통해 연결) - 인터페이스 (
Interface) : 프레임워크 & 디바이스 (프레젠터를 통해 연결)
- 엔티티와 유스케이스를 감싸고 있는 나머지 모든 영역이 세부 사항
- 입출력 장치, 저장소, 웹 시스템, 서버, 프레임워크, 통신 프로토클 등
→ 세부 사항과 유스케이스의 관계를
DIP를 통해 플러그인처럼 유연하게 처리해야 한다.
바람직한 마이크로서비스의 내부 아키텍처 → 클린 마이크로서비스 (Clean Microservice)
- 마이크로서비스 시스템에서 정의해야 할 마이크로서비스의 내부 구조가 다양할 수 있음
- 자율적인 마이크로서비스 팀에 의한 폴리글랏한 내부 구조를 가질 수 있기 때문
- 간단한 기능이면 모노리스를, 복잡한 기능이면 헥사고날/클린 아키텍처의 구조를 기반으로 정의하는 게 바람직
클린 마이크로서비스 아키텍처가 지향해야 할 원칙
- 지향하는 관심사에 따라 응집성을 높이고 관심사가 다른 영역과는 의존도를 낮추게 해야 한다.
- 업무 규칙을 정의하는 비즈니스 로직 영역을 다른 기술 기반 영역으로부터 분리하기 위해 노력한다.
- 세부 기술 중심, 저수준의 외부 영역과 핵심 업무 규칙이 정의된 고수준의 내부 영역으로 구분된다.
- 고수준 영역은 저수준 영역에 의존하지 않게 해야 하며, 저수준 영역이 고수준 영역에 의존하게 해야 한다.
- 저수준 영역은 언제든지 교체 및 확장이 가능해야 하며, 이 같은 변화가 고수준 영역에 영향을 줘서는 안 된다.
- 인터페이스나 추상 클래스를 지원하는
Java의 경우, 구체 클래스가 추상 인터페이스에 의존하는DIP를 적용한다. - 인터페이스는 고수준의 안정된 영역에 존재해야 하며, 저수주느이 어댑터가 이를 구현한다.
클린 마이크로서비스 아키텍처의 구조
-
내부 영역 (
Inbound Area) : 비즈니스 로직을 표현하는 영역-
도메인 (
Domain) : 내부 영역의 중심부에 존재하는 영역- 핵심 비즈니스 개념과 규칙을 구현
- 엔티티 (
Entity)와 값 객체 (VO)를 갖는 에그리거트 (Aggregate)로 존재
-
서비스 (
Service) : 내부 영역에서 도메인을 감싸는 영역- 도메인을 호출하여 업무를 처리하는 절차를 기술
-
서비스 인터페이스 (
Service I/F) : 서비스 처리를 위한 인터페이스- 외부에서 내부 영역에 존재하는 서비스를 사용할 수 있도록
API를 제공
- 외부에서 내부 영역에 존재하는 서비스를 사용할 수 있도록
-
API프록시 인터페이스 (API Proxy I/F) : 다른 서비스의API프록시 호출을 위한 인터페이스- 프록시 (
Proxy) 패턴 : 프록시에게 어떤 일을 대신하게 함- 어떤 객체를 사용할 때, 객체를 직접 참조하지 않고 이에 대응되는 프록시 객체로 대상 객체에 접근
- 프록시 (
-
레포지토리 인터페이스 (
Repository I/F) : 저장소 처리를 위한 인터페이스- 비즈니스를 처리하는 데에 필요한 기본적인 저장소 처리 사항을 추상화해 정의
- 외부 영역의 저장소 어댑터가 각 저장소에 맞는 저장소 처리 세부 기술로 구현
-
도메인 이벤트 발행 인터페이스 (
Domain Event Publish I/F) : 이벤트 메시지 발행을 위한 인터페이스- 도메인 이벤트 (
Domain Event) : 어떤 사건에 따른 상태의 변경 사항 - 하나의 도메인 이벤트를 각 명칭을 갖는 클래스로 구현 → 컨슈머 (
Consumer)에 전달되어 발행 (Publish)
- 도메인 이벤트 (
-
-
외부 영역 (
Outbound Area) : 기술 중심의 세부 사항을 의미하는 영역-
API퍼플리싱 어댑터 : 클라이언트 ↔ 서비스 인터페이스- 내부 영역의 서비스 인터페이스를 호출해
REST API를 발행하는 인바운드 어댑터 - 명시적인
REST리소스 명칭을 정의하고, 각REST메소드가 의도에 맞게 서비스 인터페이스를 호출 - 엔티티를 직접 제공하지 않고
API에 맞는DTO를 생성해 엔티티를 변환 및 매핑해 전달해야 함
- 내부 영역의 서비스 인터페이스를 호출해
-
API호출 프록시 어댑터 : 프록시 인터페이스 ↔ 다른 서비스- 내부 영역에 정의된 프록시 인터페이스를 구현하여 다른 서비스의
API를 호출하는 아웃바운드 어댑터 REST API, 소켓이나SOAP프로토콜 등 각 기술에 맞는 적절한 통신 방법을 구현해야 함
- 내부 영역에 정의된 프록시 인터페이스를 구현하여 다른 서비스의
-
저장소 처리 어댑터 : 레포지토리 인터페이스 ↔ 데이터베이스
- 데이터 처리 메커니즘의 선택 →
SQL vs OR?SQL매핑 방식 (MyBatis) :SQL질의문을 수동으로 작성할 수 있어 세밀한SQL제어가 가능OR매핑 방식 (JPA,Spring Data) : 런타임 시OR매퍼가 저장소에 따라 자동으로 질의문 생성- 질의문을 수동으로 작성할 필요가 줄어들어 균일한 질의문 품질과 생산성 향상 가능
- 데이터 처리 메커니즘의 선택 →
-
도메인 이벤트 발행 어댑터 : 이벤트 인터페이스 ↔ 메시지 브로커
- 도메인 이벤트를 발행하여 송신하는 아웃바운드 어댑터
- 실제 도메인 이벤트가 생성되는 위치는 내부 영역
- 에그리거트 패턴을 적용했을 때의 도메인 이벤트는 에그리거트에서 발생한 사건이 됨
- 도메인 이벤트 발행 어댑터는 내부 영역의 이벤트 인터페이스의 구현체
- 특정 메시지 큐나 스트림 저장소에 발행하는 역할을 수행
-
도메인 이벤트 핸들러 어댑터 : 메시지 브로커 ↔ 서비스 인터페이스
- 발행된 도메인 이벤트를 수신할 수 있는 인바운드 어댑터
- 외부에서 발행된 도메인 이벤트를 구독해서 내부 영역으로 전달
- 이벤트의 상태에 따라 적절한 서비스 인터페이스를 호출해서 내부 영역에 이벤트를 전달해야 함
-
클린 마이크로서비스의 내부 영역에 참고할 만한 패턴
- 서비스 인터페이스는 외부 영역이 내부 영역에 대해 많이 알지 못하게 함
- 서비스 인터페이스가 없다면? : 추이 종속성이 발생할 수 있고, 정보 은닉성이 보장될 수 없음
- 리포지토리 인터페이스, 도메인 이벤트 인터페이스,
API프록시 인터페이스는DIP를 지원- 고수준 영역에 인터페이스가 존재하면, 저수준 영역의 외부 어댑터가 이러한 인터페이스를 구현하도록 해야 함
- 도메인은 비즈니스 개념을 표현하는 엔티티의 역할울 수행
- 서비스는 도메인을 활용하여 시스템의 흐름 처리를 수행하는 유스케이스의 역할을 수행
간단한 비즈니스 로직의 처리 → 트랜잭션 스크립트 (Transaction Script) 패턴
- 비즈니스 개념을 표현하는 도메인 객체가 행위를 가지고 있지 않음
- 모든 비즈내스 행위, 즉 무언가를 수행하는 책임은 서비스에 있음
- 서비스가 비즈니스 절차에 따라 절차적으로 도메인 객체를 이용해 모든 처리를 수행
- 절차식 프로그래밍 방식과 같기 때문에 객체지향 지식이 없어도 일반적으로 쉽게 이해할 수 있는 구조
- 기존의 데이터베이스 중심 아키텍처에 익숙하다면 더 빠르게 적응할 수 있음
- 비즈니스가 복잡해질수록 서비스 코드의 양이 늘어남
- 서비스가 비대해지면서, 도메인 객체는 정보 묶음의 역할만을 수행
- 비즈니스 로직 처리가 서비스에서 이루어지므로, 비슷한 유스케이스로 중복된 코드가 생겨나 유지보수에 어려움
복잡한 비즈니스 로직의 처리 → 도메인 모델 (Domain Model) 패턴
- 도메인 객체가 데이터뿐만이 아니라 비즈니스 행위를 가짐
- 도메인 객체가 소유한 데이터는 도메인 객체가 제공하는 행위에 의해 은닉
- 도메인 객체는 각 비즈니스 개념 및 행위에 대한 책임을 수행
- 서비스는 비즈니스 유스케이스를 구현하기 위해 서비스의 행위를 도메인 객체에 일부분 위임하여 처리
- 서비스의 책임이 도메인으로 적절히 분산되므로 서비스 메소드가 단순해짐
- 도메인 모델 패턴의 도메인 모델은 객체지향 설계의 객체 모델
- 각기 적절한 책임을 가진 여러 클래스들로 구성되므로 이해하기 쉽고 관리 및 테스트가 용이
- 잘 만들어진 도메인 모델은 복잡한 비즈니스 로직의 처리에 유용
- 잘 정의된 도메인 모델은 코드의 양을 줄이고 재사용성을 높일 수 있음
복잡한 도메인 모델의 단순화 → 에그리거트 (Aggregate) 패턴
- 도메인 모델링 : 객체간의 관계를 참조로 표현
- 일대다 (
One-to-Many) 관계의 객체를 쉽게 사용할 수 있음- 업무가 복잡해지면 참조로 인한 다단계 계층 구조가 생기고, 그로 인해 참조 관계가 복잡해짐
→ 즉, 복잡한 도메인 모델은 모델 내부의 경계가 불명확하다. 이 문제를 어떻게 해결할까?
- 에그리거트 : 데이터 변경의 단위로 다루는 연관 객체의 묶음
Root Entity에서 개념적으로 묶인 엔티티의 집합- 1개 이상의 연관된 엔티티와 값 객체 (
VO)로 구성된 하나의 묶음 전체 - 개별 객체 수준의 모델 → 에그리거트 단위의 모델을 통해 도메인 모델을 단순화
에그리거트를 한 단위로 일관되게 처리하기 위한 규칙
- 에그리거트 루트만 참조한다.
- 에그리거트 내 상세 클래스를 바로 참조하지 않고 루트를 통해서만 참조해야 한다. 수정할 때도 동일하다.
- 에그리거트 간의 참조는 객체를 직접 참조하는 대신 기본 키를 사용한다.
- 기본 키를 사용하면 느슨하게 연관되고 수정이 필요없는 에그리거트를 함께 수정하는 실수를 방지한다.
- 하나의 트랜잭션으로 하나의 에그리거트만을 생성 및 수정한다.
클린 마이크로서비스의 내부 영역에 참고할 만한 패턴
- 어댑터는 각각의 인터페이스에 대한 동기/비동기 통신 및 저장소 처리를 작업한다.
- 외부 영역은 내부 영역에 존재하는 서비스 인터페이스를 사용하는 인바운드 어댑터와,
내부 영역에서 선언한 아웃바운드 인터페이스를 구현하는 아웃바운드 어댑터로 구성된다.
- 외부 영역은 내부 영역에 존재하는 서비스 인터페이스를 사용하는 인바운드 어댑터와,
- 어댑터는 플러그인처럼 언제든지 교체되거나 확장될 수 있어야 한다.
- 내부 영역이 먼저 정의된 후에 외부 영역의 세부 사항은 늦게 정의돼도 상관없도록 해야 한다.
Reference