서비스는 시스템의 확장성과 개발 가능성 측면에서 유용하지만, 그 자체로는 아키텍처 적으로 그리 중요한 요소는 아니다. 시스템의 아키텍처는 시스템 내부에 그어진 경계와 경계를 넘나드는 의존성에 의해 정의된다.
서비스 아키텍처?
- 서비스를 사용한다는 것이 본질적으로 아키텍처에 해당하는 지에 대해 생각해보면, 이 개념은 명백히 사실이 아니다.
- 시스템의 아키텍처는 의존성 규칙을 준수하며 고수준의 정책을 저수준의 세부사항으로부터 분리하는 경계에 의해 정의된다. 단순히 애플리케이션의 행위를 분리할 뿐인 서비스라면 값비싼 함수 호출에 불과하며, 아키텍처 관점에서 꼭 중요하다고 볼 수는 없다.
- 결국 서비스는 프로세스나 플랫폼 경계를 가로지르는 함수 호출에 지나지 않는다. 아키텍처 적으로 중요한 서비스도 있지만, 중요하지 않은 서비스도 존재한다.
서비스의 이점?
결합 분리의 오류
- 시스템을 서비스들로 분리 함으로써 얻게 되리라 예상되는 큰 이점 하나는 서비스 사이의 결합이 확실히 분리된다는 점이다.
- 이 말에는 어느 정도 일리가 있지만, 꼭 그런것은 아니다.
- 개별 변수 수준에서는 각각 결합이 분리되지만 프로세서 내의 또는 네트워크 상의 공유 자원 때문에 결합될 가능성이 여전히 존재함
- 서로 공유하는 데이터에 의해 이들 서비스는 강력하게 결합되어 버린다.
- 예를 들어 서비스 사이를 오가는 데이터 레코드에 새로운 필드를 추가한다면, 이 필드를 사용해 동작하는 모든 서비스는 반드시 변경되어야 한다.
개발 및 배포 독립성의 오류
- 서비스를 사용함에 따라 예측되는 또 다른 이점은 전담팀이 서비스를 소유하고 운영한다는 점이다.
- 일리가 있지만 극히 일부일 뿐이다. 대규모 엔터프라이즈 시스템은 서비스 기반 시스템 이외에도, 모노리틱 시스템이나 컴포넌트 기반 시스템으로도 구축할 수 있다는 사실은 역사적으로 증명되어 왔다. 따라서 서비스는 확장 가능한 시스템을 구축하는 유일한 선택지가 아니다.
- 결합 분리의 오류에 따르면 서비스라고 해서 항상 독립적으로 개발하고, 배포하며, 운영할 수 있는 것은 아니다. 데이터나 행위에서 어느 정도 결합되어 있다면 결합된 정도에 맞게 개발, 배포, 운영을 조정해야만 한다.
야옹이 문제

- 9장에서 예를 들었던 택시 통합 시스템을 다시 살펴보자. 이 시스템은 해당 도시에서 운영되는 많은 택시 업체를 알고 있고, 고객은 승차 요청을 할 수 있다는 점을 상기하자. 고객은 승차 시간, 비용, 고급 택시 여부, 운전사 경력 등 다양한 기준에 따라 택시를 선택할 수 있다고 가정하자.
- 확장 가능한 시스템을 구축하고 싶었기에 수많은 작은 마이크로 서비스를 기반으로 구축하기로 결정
- 서비스 설명
- TaxiUI 서비스는 고객을 담당하며, 고객은 모바일 기기를 이용해서 택시를 호출
- TaxiFinder 서비스는 여러 TaxiSupplier의 현황을 검토하여 사용자에게 적합한 택시 후보들을 선별함
- TaxiFinder 서비스는 해당 사용자에 할당된 단기 데이터 레코드에 후보 택시들의 정보를 저장함
- TaxiSelector 서비스는 사용자가 지정한 비용, 시간, 고급 여부 등의 조건을 기초로 후보 택시 중에서 적합한 택시를 선택함
- 그 후, TaxiSelector 서비스가 해당 택시를 TaxiDispatcher 서비스로 전달하면, TaxiDispatcher 서비스는 해당 택시에 배차 지시를 한다
- 이 상황에서 도시에 야옹이를 배달하는 서비스를 제공하겠다는 계획이 발표되고 해당 기능을 구현하기 위해 서비스 다이어그램을 살펴보면 이들 서비스 모두를 변경해야 한다. 해당 기능을 추가하려면 개발과 배포 전략을 매우 신중하게 조정해야만 함
다시 말해 이 서비스들은 모두 결합되어 있어서 독립적으로 개발하고, 배포하거나, 유지될 수 없다.
→ 이게 바로 횡단 관심사가 지닌 문제- 위의 서비스 다이어그램에서 묘사된 것과 같은 종류의 기능적 분해는 새로운 기능이 기능적 행위를 횡단하는 상황에 매우 취약하다.
객체가 구출하다

- SOLID 설계 원칙을 잘 들여다보면, 다형적으로 확장할 수 있는 클래스 집합을 생성해 새로운 기능을 처리하도록 함을 알 수 있다.
- 위 다이어그램의 클래스들은 이전에 보았던 서비스들과 거의 일치하지만 경계를 보면 의존성 규칙을 준수한다는 점을 알 수 있다.
- 원래 서비스의 로직 중 대다수가 객체 모델의 기반 클래스들 내부로 녹아들었었지만, 이번에는 배차에 특화된 로직 부분은 Rides 컴포넌트로, 야옹이에 대한 신규 기능은 Kittens 컴포넌트로 분리되었음
- 이 두 컴포넌트는 기존 컴포넌트들에 있는 추상 기반 클래스를 템플릿 메서드나 전략 패턴 등을 이용해서 오버라이드 한다.
두 개의 신규 컴포넌트인 Rides 와 Kittens가 의존성 규칙을 준수한다는 점, 또한 이 기능들을 구현하는 클래스들은 UI의 제어 하에 팩토리가 생성한다는 점에 주목!
- 이 전략을 따르더라도 TaxiUI는 어쩔 수 없이 변경해야만 하지만 다른 부분은 변경할 필요가 없다. 대신 야옹이 기능을 구현한 새로운 jar 파일이나 젬(GEM), DLL을 시스템에 추가하고, 런타임에 동적으로 로드하면 된다. ⇒ 결합이 분리되며, 독립적으로 개발하여 배포할 수 있다.
컴포넌트 기반 서비스
- 위의 방식을 서비스에도 적용할 수 있을까? YES
- 서비스는 SOLID 원칙대로 설계할 수 있으며 컴포넌트 구조를 갖출 수도 있다.
- 자바의 경우, 서비스를 하나 이상의 jar 파일에 포함되는 추상 클래스들의 집합이라고 생각하고, 새로운 기능 추가 혹은 기능 확장은 새로운 jar 파일로 만든다. 이 때 새로운 jar 파일을 구성하는 클래스들은 기존 jar 파일에 정의된 추상 클래스들을 확장해서 만들어진다.

횡단 관심사

- 지금까지 배운 것은 아키텍처 경계가 서비스 사이에 있지 않다는 사실이다. 오히려 서비스를 관통하며, 서비스를 컴포넌트 단위로 분할한다.
- 모든 주요 시스템이 직면하는 횡단 관심사를 처리하려면, 위 다이어그램에서 보듯이, 서비스 내부는 의존성 규칙도 준수하는 컴포넌트 아키텍처로 설계해야 한다.
- 이 서비스들은 시스템의 아키텍처 경계를 정의하지 않는다. 아키텍처 경계를 정의하는 것은 서비스 내에 위치한 컴포넌트다.