2023. 5. 31. 09:57ㆍDDD/CleanArchitecture
소개
클린아키텍처: 소프트웨어 구조와 설계의 원칙 책을 읽고 정리하며 소감을 적는 포스트입니다.
'크고 작은 모든' 서비스들
서비스 지향 아키텍처
와 마이크로서비스 아키텍처
가 최근 큰 인기를 끌고 있는 이유는 다음과 같다.
- 서비스를 사용하면 상호 결합이 철저하게 분리되는 것처럼 보인다.
나중에 보겠지만, 일부만 맞는 말이다.
- 서비스를 사용하면 개발과 배포 독립성을 지원하는 것처럼 보인다.
나중에 보겠지만, 일부만 맞는 말이다.
서비스 아키텍처?
서비스를 사용한다는 것인 본질적으로 아키텍처에 해당하는가? 개념적으로는 아니다. 시스템의 아키텍처는 의존성 규칙을 준수하며 고수준의 정책을 저수준의 세부사항으로부터 분리하는 경계에 의해 정의 된다.
단순히 애플리케이션의 행위를 분리할 뿐인 서비스라면 값비싼 함수 호출에 불과하다.
결국 서비스는 프로세스나 플랫폼 경계를 가로지르는 함수 호출에 불과 하다. 아키텍처적으로 중요한 서비스도 있지만, 중요하지 않는 서비스도 존재한다.
서비스의 이점?
결합 분리의 오류
시스템을 서비스들로 분리함으로써 얻는 이점은 서비스 사이의 결합이 확실히 분리된다는 점이다. 물론 서비스는 개별 변수 수준에서는 각각 결합이 분리된다. 하지만 프로세서 내의 또는 네트워크 상의 공유 자원 때문에 결합 될 가능성이 여전히 존재한다. 더욱이 서로 공유하는 데이터에 의해 이들 서비스는 강력하게 결합되어 버린다.
개발 및 배포 독립성의 오류
서비스를 사용함에 따라 예측되는 또 다른 이점은 전담팀이 서비스를 소유하고 운영한다는 점이다. 그래서 데브옵스(dev-ops) 전략의 일환으로 전담팀에서 각 서비스를 작성하고, 유지보수하며, 운영하는 책임을 질 수 있다. 이러한 개발 및 배포 독립성은 확장 가능한(scalable)것으로 간주 된다.
대규모 엔터프라이즈 시스템을 독립적으로 개발하고 배포 가능한 수십, 수백, 수천 개의 서비스들을 이용하여 만들 수 있다고 믿는다. 시스템의 개발, 유지보수, 운영 또한 비슷한 수의 독립적인 팀 단위로 분할할 수 있다고 여긴다.
하지만 이는 일부일 뿐이다. 첫째로 대규모 인터프라이즈 시스템은 서비스 기반 시스템 이외에도 모노리틱, 컴포넌트 기반 등 다른 방식으로 구축할 수 있다.
둘째, 결합 분리의 오류
에 따르면 서비스라고 해서 항상 독립적으로 개발하고, 배포 운영 할 수 있는 것이 아니다. 데이터나 행위에서 어느정도 결합되어 있다면 결합된 정도에 맞게 개발, 배포, 운영을 조정해야만 한다.
야옹이 문제
앞의 두 가지 오류에 대한 예로 9장에 예를 들었던 택시 통합 시스템을 다시 살펴보자. 고객은 승차 요청을 할 수 있다. 고객은 승차 시간, 비용, 고급 택시 여부, 운전자 경력 등 다양한 기준에 따라 택시를 선택 할 수 있다.
확장 가능한 시스템을 구축하고 싶었기에, 수많은 작은 마이크로서비스를 기반으로 구축하였다. 아래의 다이어그램은 TaxiUI 서비스는 고객을 담당하며, 고객은 모바일 기기로 호출한다. Taxi Finder가 여러 Taxi Supplier를 찾아 적합한 택시 후보를 고객에게 보여준다.
TaxiSelector서비스는 사용자가 지정한 비용, 시간, 고급 여부 등의 조건으로 적합한 택시를 선택한다. TaxiSelector가 해당 택시를 TaxiDispatcher 서비스로 전달하면, TaxiDispatcher 서비스는 해당 택시에 배차를 지시한다.
택시 통합 서비스를 구현하기 위해 배치된 서비스들
이 시스템을 일년 이상 운영해왔다고 가정한다. 그런데 마케팅 부서와 미팅에서 마케터들은 도시에 야옹이를 배달하는 서비스를 제공한다고 발표한다.
택시 업체 한곳이 한다고 하였고 참여를 거부하는 업체도 있었다. 당연하지만 고양이 알러지가 있다면 해당 차량은 3일 동안 배차되지 않아야 한다.
여기서 우리는 어떤 서비스를 고쳐야하는가?? 전부다. 의심의 여지없이 야용이 배달 기능을 추가하려면 개발과 배포 전략을 매우 신중하게 조정해야 한다.
다시 말해 이 서비스들은 모두 결합되어 있어서 독립적으로 개발하고, 배포하거나, 유지될 수 없다.
이게 바로 횡단 관심사(cross-cutting concern)가 지닌 문제다. 모든 소프트웨어 시스템은 서비스 지향이든 아니든 이 문제에 직면하게 마련이다.
위 서비스 다이어그램에서 묘사된 것과 같은 종류의 기능적 분해는 새로운 기능이 기능적 행위를 횡단하는 상황에 매우 취약하다.
객체가 구출한다.
컴포넌트 기반 아키텍처에서는 이 문제를 어떻게 해결했을까? SOLID 설계 원칙을 잘 들여다보면, 다형적으로 확장할 수 있는 클래스 집합을 생성해 새로운 기능을 처리하도록 함을 알 수 있다.
아래의 다이어그램은 이 전략을 보여준다. 배차에 특화된 로직 부분은 Rides 컴포넌트로 추출되고, 야용이에 대한 신규 기능은 Kittens 컴포넌트에 들어간다. 두 컴포넌트는 기존 컴포넌트들에 있는 추상 기반 클래스를 템플릿 메서드(Template Method)나 전략(Strategy) 패턴 등을 이용해서 오버라이드한다.
객체 지향 방식으로 횡단 관심사를 처리하기
또한 이 기능들을 구현하는 클래스들은 UI의 제어하에 팩토리(Factories)가 생성한다. 이 전략을 따르더라도 야용이 기능을 구현하려면 TaxiUI는 어쩔 수 없이 변경해야 한다. 하지만 그 외의 것들은 변경할 필요가 없다. 추가만 하면된다.
따라서 야용이 기능은 결합이 분리되며, 독립적으로 개발하여 배포할 수 있다.
컴포넌트 기반 서비스
이제 당연한 질문은 "서비스에도 이렇게 할 수 있을까?"
다. 물론 "예"
이다.
서비스는 SOLID 원칙대로 설계할 수 있으며 컴포넌트 구조를 갖출 수도 있다. 이를 통해 서비스 내의 기존 컴포넌트들을 변경하지 않고도 새로운 컴포넌트를 추가할 수 있다.
자바의 경우, 서비스를 하나 이상의 jar 파일에 포함되는 추상 클래스들의 집합이라고 생각하라. 새로운 기능 추가 혹은 기능 확장은 새로운 jar 파일로 만든다.그러면 새로운 기능은 재배포하는 문제가 아니라 jar 파일을 추가하는 문제가 된다. 다시 말해 새로운 기능을 추가하는 행위가 개방 패쇄 원칙을 준수하게 된다.
아래의 서비스 다이어그램은 이 구조를 보여 준다. 서비스들은 이전과 달라진게 없지만 서비스의 내부는 자신만의 컴포넌트 설계로 되어 있어서 파생 클래스를 만드는 방식으로 신규 기능을 추가할 수 잇다. 파생 클래서들은 각자의 컴포넌트 내부에 놓인다.
각 서비스의 내부는 각자의 방식대로 컴포넌트를 설계할 수 있으며, 파생 클래스를 만들어서 신규 기능을 추가할 수 있다.
횡단 관심사
지금까지 배운 것은 아키텍처 경계가 서비스 사이에 있지 않다는 사실이다.
오히려 서비스를 관통하며, 서비스를 컴포넌트 단위로 분할한다.
모든 주요 시스템이 직면하는 횡단 관심사를 처리하려면, 아래의 다이어그램처럼 서비스 내부는 의존성 규칙도 준수하는 컴포넌트 아키텍처로 설계해야 한다. 이 서비스들은 시스템의 아키텍처 경계를 정의하지 않는다. 아키텍처 경계를 정의하는 것은 서비스 내에 위치한 컴포넌트다.
서비스 내부는 의존성 규칙도 준수하는 컴포넌트 아키텍처로 설계해야 한다.
결론
서비스는 시스템의 확장성과 개발 가능성 측면에서 유용하지만, 그 자체로는 아키텍처적으로 그리 중요한 요소는 아니다. 시스템의 아키텍처는 시스템 내부에 그어진 경계와 경계를 넘나드는 의존성에 의해 정의 된다. 시스템의 구성 요수고 통신하고 실행되는 물리적인 메커니즘에 의해 아키텍처가 정의되는 것은 아니다.
서비스는 단 하나의 아키텍처 경계로 둘러싸인 단일 컴포넌트로 만들 수 있다. 혹은 여러 아키텍처 경계로 분리된 다수의 컴포넌트로 구성할 수도 있다.
'DDD > CleanArchitecture' 카테고리의 다른 글
26장. 메인(Main) 컴포넌트 (0) | 2023.05.30 |
---|---|
25장. 계층과 경계 (0) | 2023.05.30 |
24장. 부분적 경계 (0) | 2023.05.29 |
23장. 프레젠터와 험블 객체 (0) | 2023.05.29 |
22장. 클린 아키텍처 (0) | 2023.05.28 |