◆ Micro Service의 내부 구조 정의
▶ 바람직한 Micro Service의 내부 아키텍처: 클린 Micro Service
● 지금까지 언급한 아키텍처 구조는 점점 복잡해지는 모노리스 소프트웨어를 통제하기 위해 오랫동안 고민해온 결과로 그에 비해 Micro Service는 복잡해진 모노리스의 각 기능들을 쪼개기 때문에 어느 정도 복잡성을 덜어낼 수 있지만 분리해도 복잡성은 이전되고 그 안의 복잡성을 통제할 필요가 있다는 사실은 변하지 않음
● Micro Service의 내부 구조를 정의할 때 반드시 고려해야 할 한 가지는 Micro Service 시스템에서 정의해야 할 마이크로서 비스의 내부 구조가 다양할 수 있다는 것인데 왜냐하면 Micro Service는 앞에서 살펴본 것처럼 자율적인 Micro Service 팀에 의한 폴리글랏한 내부 구조를 가질 수 있기 때문
● 마이크로소프트에서는 Micro Service 설계에 대한 유용한 가이드를 자사의 개발자 사이트를 통해 공유하고 있음
● Micro Service는 언어로 ASP.NET Core, 저장소로 SQL Server. 아키텍처로 DDD(Domain Driven Design) & CQRS 패턴을 채택했고 Micro Service는 언어로 자바, 저장소로 오라클 데이터베이스, 아키텍처로 DDD를 선택한 것을 볼 수 있는데 각 서비스의 개발 언어와 저장소가 다양하고 아키텍처 구조까지도 다양하다는 사실을 알 수 있음
● 이처럼 Micro Service 아키텍처에서 각 서비스는 각기 목표 와 활용도에 따라 명확하게 분리돼야 하고 각 서비스의 목적에 따라 적절한 개발 언어 및 저장소, 내부 아키텍처를 정의하는 것이 바람직
● 조회나 아주 간단한 기능의 경우 헥사고날 아키텍처나 클린 아키텍처 방식의 구조를 고수할 필요는 없을 것이지만 비즈니스 규칙이 복잡한 서비스는 헥사고날 아키텍처 나 클린 아키텍처의 구조를 기반으로 정의하는 것이 바람직
● 헥사고날 아키텍처나 클린 아키텍처가 지향하는 원칙
⊙ 지향하는 관심사에 따라 응집성을 높이고 관심사가 다른 영역과는 의존도를 낮추게 해야 함
⊙ 업무 규칙을 정의하는 비즈니스 로직 영역을 다른 기술 기반 영역으로부터 분리하기 위해 노력
⊙ 세부 기술 중심, 저수준의 외부 영역 과 핵심 업무 규칙이 정의된 고수준의 내부영역으로 구분
⊙ 고수준 영역은 저수준 영역에 의존하지 않게 해야 하며 저수준 영역이 고수준 영역에 의존하게 해야 함
⊙ 저수준 영역은 언제든지 교체, 확장 가능해야 하며, 이 같은 변화가고수준 영역에 영향을 줘서는 안됨
⊙ 자바처럼 인터페이스 및 추상 클래스를 지원하는 언어의 경우 저수준 영역의 구체 클래스가 고수준 영역의 추상 인터페이스에 의존하게 하는 의존성 역전의 원칙을 적용
⊙ 인터페이스는 고수준의 안정된 영역에 존재해야 하며, 저수준의 어댑터가 이를 구현
● Micro Service의 내부 구조
⊙ 내부 영역에서는 맨 안쪽에 도메인이 존재하고 도메인을 서비스가 감싸는데 도메인에는 핵심 비즈니스 개념과 규칙을 구현하며 서비스에서는 도메인을 호출해서 업무를 처리하는 절차를 기술하고 외부 영역과 연계하기 위해 서비스 인터페이스를 보유
⊙ 서비스 인터페이스는 외부에서 내부 영역을 사용할 수 있도록 API를 제공하고 서비스가 이를 구현
⊙ 내부 영역에 있는 또 다른 인터페이스는 저장소 처리를 위한 인터페이스로 리포지토리(Repository) 인터페이스
⊙ 리포지토리 인터페이스는 외부 영역에서 정의하지 않고 내부 영역에서 정의하는데 비즈니스를 처리하는 데 필요한 기본적인 저장소 처리 사항을 추상화 해 정의하고 그렇게 하면 외부 영역의 저장소 어댑터는 이 리포지토리 인터페이스를 각 저장소에 맞는 저장소 처리 세부 기술로 구현
⊙ 외부 영역에는 저장소 처리 어댑터 뿐 만 아니라 다양한 인바운드, 아웃바운드를 처리하는 어댑터가 위치하는데 REST API를 처리하는 어댑터, 이벤트 메시지를 처리하는 어댑터, 메시지를 구독하는 메시지 컨슈머 어댑터, 다른 Micro Service의 API를 호출하는 프락시 어댑터 등이 위치하는데 이 같은 저장소 처리 어댑터, 이벤트 발행 어댑터, API 호출 프락시 어댑터 등 모든 아웃바운드 어댑터는 의존 관계 역전의 원칙을 적용해 외부 영역에서 내부 영역에 의존하도록 설계
▶ 내부 영역 – 업무 규칙
● 업무 규칙을 정의하는 내부 영역에는 서비스 인터페이스, 서비스 구현체, 도메인, 리포지토리 인터페이스, 도메인 이벤트 인터페이스, API 프락시 인터페이스가 존재
● 서비스 인터페이스는 외부 영역이 내부 영역에 대해 너무 많이 알지 못하게 하는 역할을 담당
● 서비스 인터페이스가 없다면 추이 종속성이 발생할 수 있음(정보 은닉 효과도 있음)
● 리포지토리 인터페이스, 도메인 이벤트 인터페이스, API 프락시 인터페이스는 의존 관계 역전의 원칙을 지원
● 더 안정된 곳인 고수준 영역에 인터페이스가 존재하고 저수준의 외부 어댑터가 이러한 인터페이스를 구현
● 서비스와 도메인은 클린 아키텍처의 유스케이스 와 엔티티의 역할과 같음
● 도메인은 비즈니스 개념을 표현하고 서비스는 도메인을 활용해 시스템 흐름 처리를 수행
● 내부 영역 특히 서비스와 도메인의 관계를 구현할 때 참고할 만한 유용한 패턴이 있는데 바로 마틴 파울러의 엔터프라이즈 애플리케이션 아키텍처 패턴에서 언급한 트랜잭션 스크립트 패턴과 도메인 모델 패턴
● 트랜잭션 스크립트 패턴
⊙ 트랜잭션 스크립트(Transaction Script) 패턴에서는 비즈니스 개념을 표현하는 도메인 객체가 행위를 가지고 있지 않기 때문에 모든 비즈니스 행위, 즉 무엇인가를 수행하는 책임은 서비스에 있음
⊙ 서비스가 비즈니스 절차에 따라 절차적으로 도메인 객체를 이용해 모든 처리를 수행
⊙ 이런 방식에서는 시간이 지남에 따라 서비스가 비대해지고 도메인 객체는 점점 정보 묶음의 역할만 수행하게 될 뿐
⊙ 서비스는 유스케이스 처리의 단위이고 대부분의 비즈니스 로직 처리가 서비스에서 이루어지므로 비슷한 유스케이스의 경우 서비스에 중복되는 코드가 계속 생겨날 수 있는데 이러한 점이 유지보수를 어렵게 할 수 있음
⊙ 트랜잭션 스크립트 패턴은 절차식 프로그래밍 방식과 같기 때문에 객체지향 지식이 없어도 일반적으로 쉽게 이해할 수 있는 구조이고 기존 데이터베이스 중심 아키텍처에 익숙하다면 더 쉽게 적응할 수 있음
⊙ 이 패턴은 비즈니스가 간단한 경우에는 쉽게 적용할 수 있지만 비즈니스가 복잡해질 경우 서비스 코드의 양이 점점 증가하는 등 데이터베이스 중심 아키텍처에서 겪었던 문제점이 발생할 여지가 크기 때문에 간단한 비즈니스를 처리할 때 적용하는 것이 좋음
● 도메인 모델 패턴
⊙ 도메인 모델(Domain Model) 패턴은 도메인 객체가 데이터 뿐만 아니라 비즈니스 행위를 가지고 있으며 도메인 객체가 소유한 데이터는 도메인 객체가 제공하는 행위에 의해 은닉
⊙ 도메인 객체는 각 비즈니스 개념 및 행위에 대한 책임을 수행하고 서비스는 비즈니스 유스케이스를 구현하기 위해 서비스의 행위를 도메인 객체에 일부분 위임해서 처리
⊙ 서비스의 책임들이 도메인으로 적절히 분산되기 때문에 서비스가 비대해지지 않고 서비스 메서드는 단순해 짐
⊙ 도메인 모델 패턴의 도메인 모델은 객체지향 설계의 객체 모델
⊙ 거대한 서비스 클래스 대신 각기 적절한 책임을 가진 여러 클래스로 구성되므로 이해하기 쉽고 관리 및 테스트하기 쉽고 여기서 더 진화해서 도메인 주도 설계의 애그리거트(Aggregate) 패턴을 적용할 수 있는 구조
⊙ 핵심은 도메인 모델이기 때문에 객체지향 지식에 대한 경험과 역량이 필요
⊙ 잘 만들어진 도메인 모델은 복잡한 비즈니스 로직을 처리하는 데 유용하며 잘 정의된 도메인 모델은 코드의 양을 줄이고 재사용성도 높임
⊙ 복잡한 비즈니스 로직이 많은 Micro Service의 구조로 선택 하는 것이 좋음
● 도메인 주도 설계의 애그리거트 패턴
⊙ 애그리거트 패턴은 에릭 에반스의 도메인 주도 설계에 등장하는 패턴으로 점점 복잡해질 수 있는 객체 모델링의 단점을 보완한 패턴이라 볼 수 있음
⊙ 도메인 모델링을 하다 보면 객체 간의 관계를 참조로 표현하게 되는데 참조로 정의할 경우 일대다(one-to- many) 관계의 객체를 쉽게 사용할 수 있다는 장점이 있지만 업무가 복잡해지면 참조로 인한 다단계 계층 구조가 생기고 점점 참조 관계가 복잡해지고 무거워질 수 있음
⊙ 복잡한 도메인 모델은 모델 내부의 경계가 불명확한데 예를 들면 어떤 도메인 모델이 일대다 관계를 맺고 있고 다(many) 측에 있는 클래스의 총 개수를 일(one) 측에 있는 클래스에서 집계해야 하는 규칙이 있다고 보면 서비스에서 이러한 로직을 처리할 때 다(many) 측에 클래스가 추가되면 일(one) 측의 클래스에서 집계한 값을 수정해야 하는데 다 측의 클래스만 추가하고 집계한 값을 수정하지 않는다면 비즈니스 일관성이 깨질 것인데 도메인 모델이 커짐에 따라 이러한 문제가 복잡해지고 꼬일 수 있는데 이를 개선할 방안으로 최상위에 존재하는 엔티티(Root Entity)를 중심으로 개념의 집합을 분리한 것이 애그리거트 패턴
⊙ 애그리거트 패턴
⊙ 애그리거트 패턴에서는 이처럼 애그리거트를 한 단위로 일관되게 처리하기 위해 다음과 같은 규칙을 부여
→ 애그리거트 루트만 참조
→ 애그리거트 내 상세 클래스를 바로 참조하지 않고 루트를 통해 참조
→ 수정할 때도 마찬가지인데 애그리거트 간의 참조는 객체를 직접 참조하는 대신 기본키를 사용
→ 기본키를 사용하면 느슨하게 연관되고 수정이 필요하지 않은 애그리거트를 함께 수정하는 실수를 방지
→ 하나의 트랜잭션으로 하나의 애그리거트만 생성 및 수정
댓글