Book Notes

[개발서적] Clean Architecture 5부(ch15~19) 요약 (1)

feel2 2025. 12. 10. 21:36

Ch15. 아키텍처란?


 

아키텍처라는 단어는 권력과 신비로움을 연상케 한다.

소프트웨어 아키텍처는 기술적 성취의 정점에 서 있다.

 

무엇보다도 소프트웨어 아키텍트는 프로그래머이며, 앞으로도 게속 프로그래머로 남는다.

소프트웨어 아키텍트라면 코드에서 탈피하여 고수준의 문제에 집중해야 한다는 거짓말에 속아넘어가서는 안 된다.

 

소프트웨어 아키텍트는 최고의 프로그래머이며, 앞으로도 계속 프로그래밍 작업을 맡을 뿐만 아니라 동시에 나머지 팀원들이 생산성을 극대화할 수 있는 설계를 하도록 방향을 이끌어 준다.

 

소프트웨어 시스템의 아키텍처란 시스템을 구축했던 사람들이 만들어내 시스템의 형태다. 그리고 그 형태는 아키텍처 안에 담긴 소프트웨어 시스템이 쉽게 개발, 배포, 운영, 유지보수되도록 만들어진다.

 

이러한 일을 용이하게 만들기 위해서는 가능한 한 많은 선택지를, 가능한 한 오래 남겨두는 전략을 따라야 한다.

 

아키텍처의 주된 목적은 시스템의 생명주기를 지원하는 것이다.

좋은 아키텍처는 시스템을 쉽게 이해하고, 쉽게 개발하며, 쉽게 유지보수하고, 또 쉽게 배포하게 해준다.

따라서 아키텍처의 궁극적인 목표는 시스템의 수명과 관련된 비용은 최소화하고, 프로그래머의 생산성은 최대화하는데 있다.

 

[개발]

개발하기 힘든 시스템이라면 수명이 길지도 않고 건강하지도 않을 것이다.

따라서 시스템 아키텍처는 개발팀(들)이 시스템을 쉽게 개발할 수 있도록 뒷받침해야만 한다.

 

팀 구조가 다르다면 아키텍처 관련 결정에서도 차이가 난다.

예를 들어 팀이 개발자가 다섯 명이라면 잘 정의된 컴포넌트나 인터페이스가 없더라도 서로 효율적으로 협력하여 모노리틱 시스템을 개발할 수도 있다.

다른 한편으로 일곱 명씩 구성된 총 다섯 팀이 시스템을 개발하고 있다면 시스템을 신뢰할 수 있고 안정된 인터페이스를 갖춘, 잘 설계된 컴포넌트 단위로 분리하지 않으면 개발이 진척되지 않는다.

 

이러한 ‘팀별 단일 컴포넌트’ 아키텍처가 시스템을 배포, 운영, 유지보수하는 데 최적일 가능성은 거의 없다.

그럼에도 여러 팀이 순전히 일정에만 쫓겨서 일한다면, 결국 이 아키텍처로 귀착될 것이다.

 

[배포]

소프트웨어 시스템이 사용될 수 있으려면 반드시 배포할 수 있어야 한다.

배포 비용이 높을수록 시스템의 유용성은 떨어진다.

따라서 소프트웨어 아키텍처는 시스템을 단 한 번에 쉽게 배포할 수 있도록 만드는 데 그 목표를 두어야 한다.

 

[운영]

아키텍처가 시스템 운영에 미치는 영향은 개발, 배포, 유지보수에 미치는 영향보다는 덜 극적이다.

좋은 소프트웨어 아키텍처는 시스템을 운영하는 데 필요한 요구도 알려준다.

시스템 아키텍처는 유스케이스, 기능, 시스템의 필수 행위를 일급 엔티티로 격상시키고, 이들 요소가 개발자에게 주요 목표로 인식되도록 해야 한다.

이를 통해 시스템을 이해하기 쉬워지며, 따라서 개발과 유지보수에 큰 도움이 된다.

 

[유지보수]

유지보수는 모든 측면에서 봤을 때 소프트웨어 시스템에서 비용이 가장 많이 든다.

새로운 기능은 끝도 없이 발생하고, 뒤따라서 발생하는 결함은 피할 수 없으며, 결함을 수정하는 데도 엄청난 인적 자원이 소모된다.

 

유지보수의 가장 큰 비용은 탐사와 이로 인한 위험부담이 있다.

여기서 탐사란 기존 소프트웨어에 새로운 기능을 추가하거나 결함을 수정할 때, 소프트웨어를 파해쳐서 어디를 고치는게 최선인지, 그리고 어떤 전략을 쓰는게 최적일지를 결정할 때 드는 비용이다.

 

주의를 기울여 신중하게 아키텍처를 만들면 이 비용을 크게 줄일 수 있다.

시스템을 컴포넌트로 분리하고, 안정된 인터페이스를 두어 서로 격리한다.

이를 통해 미래에 추가될 기능에 대한 길을 밝혀 둘 수 있을 뿐만 아니라 의도치 않은 장애가 발생할 위험을 크게 줄일 수 있다.

 

[선택사항 열어두기]

이 책 초반에 설명했듯이, 소프트웨어는 두 종류의 가치, 즉 행위적 가치구조적 가치를 갖는다.

이 중에서 두번째 가치가 더 중요한데, 소프트웨어를 부드럽게 만드는 것은 바로 이 구조적 가치이기 때문이다.

 

소프트웨어를 부드럽게 유지하는 방법은 선택사항을 가능한 한 많이, 그리고 가능한 한 오랫동안 열어두는 것이다.

여기서 말하는 선택사항이란 바로 중요치 않은 세부사항이다.

 

모든 소프트웨어 시스템은 주요한 두 가지 구성요소로 분해할 수 있다. 바로 정책세부사항이다.

정책 요소는 모든 업무 규칙과 업무 절차를 구체화한다. 정책이란 시스템의 진정한 가치가 살아 있는 곳이다.

세부사항은 사람, 외부 시스템, 프로그래머가 정책과 소통할 때 필요한 요소이지만, 정책이 가진 행위에는 조금도 영향을 주지 않는다.

이러한 세부사항에는 입출력 장치, 데이터베이스, 웹 시스템, 서버, 프레임워크, 통신 프로토콜 등이 있다.

 

아키텍트의 목표는 시스템에서 정책을 가장 핵심적인 요소로 식별하고, 동시에 세부사항은 정책에 무관하게 만들 수 있는 형태의 시스템을 구축하는데 있다.

 

예를 들어보자.

  • 개발 초기에는 데이터베이스 시스템을 선택할 필요가 없다.
  • 개발 초기에는 웹 서버를 선택할 필요가 없다.
  • 개발 초기에는 REST를 적용할 필요가 없다.
  • 개발 초기에는 의존성 주입 프레임워크를 적용할 필요가 없다.

선택사항을 더 오랫동안 열어 둘 수 있다면 더 많은 실험을 해볼 수 있고, 더 많은 것을 시도해 볼 수 있다.

뛰어난 아키텍트라면 이러한 결정이 아직 내려지지 않은 것처럼 행동해야하며, 여전히 결정을 가능한 한 오랫동안 연기하거나 변경할 수 있는 형태로 시스템을 만든다.

 

좋은 아키텍트는 결정되지 않은 사항의 수를 최대화 한다.

 

[결론]

좋은 아키텍트는 세부사항을 정책으로부터 신중하고 가려내고, 정책이 세부사항과 결합되지 않도록 엄격하게 분리한다.

이를 통해 정책은 세부사항에 관한 어떠한 지식도 갖지 못하게 되며, 어떤 경우에도 세부사항에 의존하지 않게 된다.

좋은 아키텍트는 세부사항에 대한 결정을 가능한 한 오랫동안 미룰 수 있는 방향으로 정책을 설계한다.

 

Ch16. 독립성


 

앞서 서술한 바와 같이 좋은 아키텍처는 다음을 지원해야 한다.

  • 시스템의 유스케이스
  • 시스템의 운영
  • 시스템의 개발
  • 시스템의 배포

 

[유스케이스]

유스케이스의 경우 시스템의 아키텍처는 시스템의 의도를 지원해야 한다는 뜻이다.

만약 시스템이 장바구니 애플리케이션이라면, 이 아키텍처는 장바구니와 관련된 유스케이스를 지원해야 한다.

실제로 아키텍트의 최우선 관심사는 유스케이스며, 아키텍처에서도 유스케이스가 최우선이다.

 

좋은 아키텍처가 행위를 지원하기 위해 할 수 있는 가장 중요한 사항은 행위를 명확히 하고 외부로 드러내며, 이를 통해 시스템이 지닌 의도를 아키텍처 수준에서 알아볼 수 있게 만드는 것이다.

이들 행위는 일급 요소이며, 시스템의 최상위 수준에서 알아볼 수 있으므로, 개발자가 일일이 찾아 헤매지 않아도 된다.

 

[운영]

시스템의 운영 지원 관점에서 볼 때 아키텍처는 더 실질적이며 덜 피상적인 역할을 맡는다.

시스템이 초당 100,000명의 고객을 처리해야 한다면, 아키텍처는 이 요구와 관련된 각 유스케이스에 걸맞는 처리량과 응답시간을 보장해야 한다.

 

이러한 형태를 지원한다는 말은 시스템에 따라 다양한 의미를 지닌다.

어떤 시스템에서는 시스템의 처리 요소를 일련의 작은 서비스들로 배열하여, 서로 다른 많은 서버에서 병렬로 실행할 수 있게 만들어야 함을 의미한다.

 

이러한 결정은 뛰어난 아키텍트라면 열어 두어야 하는 선택사항 중 하나다.

만약 아키텍처에서 각 컴포넌트를 적절히 격리하여 유지하고, 컴포넌트 간 통신 방식을 특정 형태로 제한하지 않는다면, 시간이 지나 요구사항이 바뀌더라도 스레드, 프로세스, 서비스로 구성된 기술 스팩트럼 사이를 전환하는 일이 훨씬 쉬워질 것이다.

 

[개발]

아키텍처는 개발환경을 지원하는 데 있어 핵심적인 역할을 수행한다.

콘웨이의 법칙을 보면 다음과 같다.

 

시스템을 설계하는 조직이라면 어디든지 그 조직의 의사소통 구조와 동일한 구조의 설계를 만들어 낼 것이다.

 

많은 팀으로 구성되며 관심사가 다양한 조직에서 어떤 시스템을 개발해야 한다면, 각 팀이 독립적으로 행동하기 편한 아키텍처를 반드시 확보하여 개발하는 동안 팀들이 서로를 방해하지 않도록 해야 한다.

 

[배포]

아키텍처는 배포 용이성을 결정하는 데 중요한 역할을 한다.

이때 목표는 즉각적인 배포다.

좋은 아키텍처라면 시스템이 빌드된 후 즉각 배포할 수 있도록 지원해야 한다.

다시 말하지만, 이러한 아키텍처를 만들려면 시스템을 컴포넌트 단위로 적절하게 분할하고 격리시켜야 한다.

 

[선택사항 열어두기]

좋은 아키텍처라면 컴포넌트 구조와 관련된 이 관심사들 사이에서 균형을 맞추고, 각 관심사 모두를 만족시킨다.

하지만 현실에서 이러한 균형을잡기가 매우 어렵다.

대부분의 경우 우리는 모든 유스케이스를 알 수 없으며, 운영하는 데 따르는 제약사항, 팀 구조, 배포 요구사항도 알지 못하기 때문이다.

그렇기 때문에 좋은 아키텍처는 선택사항을 열어 둠으로써, 향후 시스템에 변경이 필요할 때 어떤 방향으로든 쉽게 변경할 수 있도록 한다.

 

[계층 결합 분리]

아키텍트는 시스템의 기본적인 의도는 분명히 알고 있다.

그 시스템이 장바구니 시스템인지, 자재 명세서 시스템인지, 또는 주문 처리 시스템인지 안다는 뜻이다.

따라서 아키텍트는 단일 책임 원칙공통 폐쇄 원칙을 적용하여, 그 의도의 맥락에 따라 다른 이유로 변경되는 것들을 분리하고(SRP), 동일한 이유로 변경되는 것들을 묶는다(CCP).

 

예를 들어 입력 필드 유효성 검사는 애플리케이션 자체와 밀접하게 관련된 업무 규칙이다.

반대로 계좌의 이자 계산이나 재고품 집계는 업무 도메인에 더 밀접하게 연관된 업무 규칙이다.

따라서 이들 규칙은 서로 분리하고, 독립적으로 변경할 수 있도록 만들어야 한다.

 

[유스케이스 결합 분리]

유스케이스는 시스템을 분할하는 매우 자연스러운 방법이다.

예를 들어 주문 입력 시스템에서 주문을 추가하는 유스케이스와 주문을 삭제하는 유스케이스는 틀림없이 다른 속도로 변경될 것이다.

이와 동시에 유스케이스는 시스템의 수평적인 계층을 가로지르도록 자른, 수직으로 좁다란 조각이기도 하다.

이렇게 요소들을 분리하면 서로 영향을 주지 않으며 확장이 가능하다.

 

 

[결합 분리 법칙]

이렇게 결합을 분리하면 두 번째 항목인 운영 관점에서 어떤 의미가 있는지 살펴보기로 하자.

유스케이스를 위해 수행하는 그 작업들(결합 분리)은 운영에도 도움이 된다.

분리된 컴포넌트는 반드시 독립된 서비스여야 하고, 일종의 네트워크를 통해 서로 통신해야 한다.

 

많은 아키텍트가 이러한 컴포넌트를 ‘마이크로서비스’라고 하는데, 실제로 서비스에 기반한 아키텍처를 흔히들 서비스 지향 아키텍처(service-oriented-architecture)라고 한다.

 

여기서 이야기하고자 한 핵심은 우리는 때때로 컴포넌트를 서비스 수준까지도 분리해야 한다는 것이다.

 

[개발 독립성]

세 번째 항목은 개발이었다.

컴포넌트가 완전 분리되면 팀 사이의 간섭은 줄어든다.

기능 팀, 컴포넌트 팀, 계층 팀, 혹은 또 다른 형태의 팀이라도, 계층과 유스케이스의 결합이 분리되는 한 시스템의 아키텍처는 그 팀 구조를 뒷받침해 줄 것이다.

 

[배포 독립성]

유스케이스와 계층의 결합이 분리되면 배포 측면에서도 고도의 유연성이 생긴다.

새로운 유스케이스를 추가하는 일은 시스템의 나머지는 그대로 두고 새로운 jar 파일이나 서비스 몇 개를 추가하는 정도로 단순한 일이 된다.

 

[중복]

아키텍트는 종종 함정에 빠지곤 한다. 전적으로 중복에 대한 공포로부터 발생하는 함정이다.

중복에는 진짜 중복거짓된(혹은 우발적인) 중복이 있다.

진짜 중복은 한 인스턴스가 변경되면, 동일한 변경을 그 인스턴스의 모든 복사본에 반드시 적용해야 한다.

거짓된 중복은 중복으로 보이는 두 코드 영역이 각자의 경로로 발전한다면, 이 두 코드는 진짜 중복이 아니다.

 

[결합 분리 모드(다시)]

다시 결합 분리 모드로 돌아가자.

계층과 유스케이스를 분리하는 방법은 다양하다.

소스 코드 수준에서 분리할 수도 있으며, 바이너리 코드(배포) 수준에서도, 실행 단위(서비스) 수준에서도 분리할 수 있다.

  • 소스 수준 분리 모드: 소스 코드 모듈 사이의 의존성을 제어할 수 있다.(예, 루비 Gem) 이 모드에서는 모든 컴포넌트가 같은 주소 공간에서 실행되고, 서로 통신할 때 간단한 함수 호출을 사용한다. 이러한 구조를 흔히 모노리틱 구조라고 한다.
  • 배포 수준 분리 모드: jar, DLL, 공유 라이브러리와 같이 배포 가능한 단위들 사이의 의존성을 제어할 수 있다. 이를 통해 한 모듈의 소스 코드가 변하더라도 다른 모듈을 재빌드하거나 재배포하지 않도록 만들 수 있다. 이 모드의 중요한 특징은 결합이 분리된 컴포넌트가 jar 파일, Gem 파일, DLL과 같이 독립적으로 배포할 수 있는 단위로 분할되어 있다는 점이다.
  • 서비스 수준 분리 모드: 의존하는 수준을 데이터 구조 단위까지 낮출 수 있고, 순전히 네트워크 패킷을 통해서만 통신하도록 만들 수 있다. 이를 통해 모든 실행 가능한 단위는 소스와 바이너리 변경에 대해 서로 완전히 독립적이게 된다.(예, 마이크로서비스)

만약 시스템이 한 서버에서 실행되는 동안은 결합을 소스 수준에서 분리하는 것만으로 충분하다.

(현 시점에서 가장 인기 있어 보이는) 한 가지 해결책은 단순히 서비스 수준에서의 분리를 기본 정책으로 삼는 것이다.

다만 이 방식은 비용이 많이 들고, 결합이 큰 단위에서 분리된다는 문제가 있다.

 

좋은 아키텍처는 시스템이 모노리틱 구조로 태어나서 단일 파일로 배포되더라도, 이후에는 독립적으로 배포 가능한 단위들의 집합으로 성장하고, 또 독립적인 서비스나 마이크로 서비스 수준까지 성장할 수 있도록 만들어져야 한다.

또한 좋은 아키텍처라면 진행 방향을 거꾸로 돌려 원래 형태인 모노리틱 구조로 되돌릴 수 있어야 한다.

좋은 아키텍처는 결합 분리 모드를 선택사항으로 남겨두어서 배포 규모에 따라 가장 적합한 모드를 선택해 사용할 수 있게 만들어 준다.

 

[결론]

시스템의 결합 분리 모드는 시간이 지나면서 바뀌기 쉬우며, 뛰어난 아키텍트라면 이러한 변경을 예측하여 큰 무리 없이 반영할 수 있도록 만들어야 한다.

 

Ch17. 경계: 선 긋기


소프트웨어 아키텍처는 선을 긋는 기술이며, 나는 이러한 선을 경계라고 부른다.

경계는 소프트웨어 요소를 서로 분리하고, 경계 한편에 있는 요소가 반대편에 있는 요소를 알지 못하도록 막는다.

 

아키텍처의 목표는 필요한시스템을 만들고 유지하는 데 드는 인적 자원을 최소화하는 것이라는 사실을 상기하자.

그렇다면 인적 자원의 효율을 떨어뜨리는 요인은 무엇일까? 바로 결합이다.

특히 너무 일찍 내려진 결정에 따른 결합이다.

 

어떤 종류의 결정이 이른 결정일까?

바로 시스템의 업무 요구사항, 즉 유스케이스와 아무런 관련이 없는 결정이다.

여기에는 프레임워크, 데이터베이스, 웹 서버, 유틸리티 라이브러리, 의존성 주입에 대한 결정 등이 포함된다.

좋은 시스템 아키텍처란 이러한 결정이 부수적이며, 결정을 연기할 수 있는 아키텍처다.

 

[어떻게 선을 그을까? 그리고 언제 그을까?]

관련이 있는 것과 없는 것 사이에 선을 긋는다.

GUI는 업무 규칙과는 관련 없기 때문에, 이 둘 사이에는 반드시 선이 있어야 한다.

데이터베이스는 GUI와는 관련이 없으므로, 이 둘 사이에도 반드시 선이 있어야 한다.

 

데이터베이스는 업무 규칙이 간적접으로 사용할 수 있는 도구다.

업무 규칙은 스키마, 쿼리 언어, 또는 데이터베이스와 관련된 나머지 세부사항에 대해 어떤 것도 알아서는 안 된다.

 

그림 17.1 인터페이스 뒤로 숨은 데이터베이스

 

그림 17.1을 보면 BusinessRules는 Database Interface를 사용하여 데이터를 로드하고 저장한다.

DataAccess는 DatabaseInterface를 구현하며, Database를 실제로 조작하는 일을 맡는다.

 

경계선은 어디에 있는가? 경계선은 상속 관계를 횡단하면서 Database Interface 바로 아래에 그어진다.

 

그림 17.2 경계선

 

DatabaseAccess에서 출발하는 두 화살표에 주목하자.

 

이제 조금 물러나서 보자.

 

그림 17.3 업무 규칙과 데이터베이스 컴포넌트

 

 

위 그림을 보면, 두 컴포넌트 사이에 경계선을 그렸고, 화살표의 방향이 BusinessRules를 향하도록 만들었다.

이 의미는 BusinessRules에서는 어떤 종류의 데이터베이스도 사용할 수 있음을 알 수 있다.

 

[플러그인 아키텍처]

데이터베이스와 GUI에 대해 내린 두 가지 결정은 하나로 합쳐서 보면 컴포넌트 추가와 관련한 일종의 패턴이 만들어진다.

이 패턴은 시스템에서 서드파티 플러그인을 사용할 수 있게 한 바로 그 패턴과 동일하다.

 

사실 소프트웨어 개발 기술의 역사는 플러그인을 손쉽게 생성하여, 확장 가능하며 유지보수가 쉬운 시스템 아키텍처를 확립할 수 있게 만드는 방법에 대한 이야기다.

 

그림 17.5 업무 규칙에 플러그인 형태로 연결하기

 

 

이 설계에서 사용자 인터페이스는 플러그인 형태로 고려되었기에, 수많은 종류의 사용자 인터페이스를 플러그인 형태로 연결할 수 있게 된다.

웹 기반일 수도, 클라이언트/서버 기반일 수도, SOA나 콘솔 기반, 어떤 인터페이스도 상관없다.

데이터베이스도 SQL 데이터베이스, NoSQL 데이터베이스, 파일 시스템 기반 등 어떤 것도 상관없다.

 

[플러그인에 대한 논의]

경계는 변경의 축(axios of change)이 있는 지점에 그어진다.

업무 규칙은 의존성 주입 프레임워크와는 다른 시점에 그리고 다른 이유로 변경되므로, 둘 사이에도 반드시 경계가 필요하다.

이 역시도 단일 책임 원칙에 해당한다.

단일 책임 원칙은 어디에 선을 그어야 할지 알려준다.

 

[결론]

소프트웨어 아키텍처에서 경계선을 그리려면 먼저 시스템을 컴포넌트 단위로 분리해야 한다.

일부 컴포넌트는 업무 규칙에, 나머지 컴포넌트는 플러그인으로, 핵심 업무와는 직접적인 관련은 없지만 필수 기능을 포함한다.

그런 다음 컴포넌트 사이의 화살표가 특정 방향, 즉 핵심 업무를 향하도록 이들 컴포넌트의 소스를 배치한다.

즉, 의존성 화살표는 저수준 세부사항에서 고수준의 추상화를 향하도록 배치된다.

 

Ch18. 경계 해부학


시스템 아키텍처는 일련의 소프트웨어 컴포넌트와 그 컴포넌트들을 분리하는 경계에 의해 정의된다.

이러한 경계는 다양한 형태로 나타난다.

 

[경계 횡단하기]

‘런타임에 경계를 횡단한다.’ 함은 그저 경계 한쪽에 있는 기능에서 반대편 기능을 호출하여 데이터를 전달하는 일에 불과하다.

적절한 위치에서 경계를 횡단하게 하는 비결은 소스 코드 의존성 관리에 있다.

 

왜 소스 코드일까? 소스 코드 모듈 하나가 변경되면, 의존하는 다른 소스 코드 모듈도 변경되기 때문이다.

경계는 이러한 변경이 전파되는 것을 막는 방화벽을 구축하고 관리하는 수단으로써 존재한다.

 

[두려운 단일체]

아키텍처 경계 중에서 가장 단순하며 가장 흔한 형태는 물리적으로 엄격하게 구분되지 않는 형태다.

이전 장에서 우리는 이걸 소스 수준 분리 모드라고 불렀다.

 

배포 관점에서 보면 이는 소위 단일체라고 불리는 단일 실행 파일에 지나지 않는다.

단일체는 배포 관점에서 볼 때 경계가 드러나지 않는다.

이러한 아키텍처는 거의 모든 경우에 특정한 동적 다형성에 의존하여 내부 의존성을 관리한다.

 

가장 단순한 형태의 경계 횡단은 저수준 클라이언트에서 고수준 서비스로 향하는 함수 호출이다.

이 경우 런타임 의존성과 컴파일타임 의존성은 모두 같은 방향, 즉 저수준 컴포넌트에서 고수준 컴포넌트로 향한다.

 

그림 18.1 제어흐름은 경계를 횡단할 때 저수준에서 고수준으로 흐른다.

 

그림 18.1을 보면, 제어흐름이 왼쪽에서 오른쪽으로 경계를 횡단하는 것을 볼 수 있다.

 

만약 고수준 클라이언트가 저수준 서비스를 호출해야 한다면 동적 다형성을 사용하여 제어흐름과는 반대 방향으로 의존성을 역전시킬 수 있다.

 

그림 18.2 제어흐름과는 반대로 경계를 횡단한다.

 

 

여기서 중요한 것은 의존성의 방향이 고수준 컴포넌트로 향한다는 점이다.

 

단일체를 배포하는 일은 일반적으로 컴파일과 정적 링크 작업을 수반하므로, 대체로 이러한 시스템에서 컴포넌트는 소스 코드 형태로 전달된다.

 

[배포형 컴포넌트]

아키텍처의 경계가 물리적으로 드러날 수 있는데, 그중 가장 단순한 형태는 동적 링크 라이브러리다.

.NET DLL, 자바 jar 파일, 루비 젬(Gem), 유닉스 공유 라이브러리 등이 그 예다.

컴포넌트를 이 형태로 배포하면 따로 컴파일하지 않고 바로 사용할 수 있다.

대신 컴포넌트는 바이너리와 같이 배포 가능한 형태로 전달된다.

이는 배포 수준 결합 분리 모드에 해당한다.

 

단일체와 마찬가지로 배포형 컴포넌트의 경계를 가로지르는 통신은 순전히 함수 호출에 지나지 않으므로 매우 값싸다.

 

[스레드]

단일체와 배포형 컴포넌트는 모두 스레드를 활용할 수 있다.

스레드는 아키텍처 경계도 아니며 배포 단위도 아니다.

모든 스레드가 단 하나의 컴포넌트에 포함될 수도 있고, 많은 컴포넌트에 걸쳐 분산될 수도 있다.

 

[로컬 프로세스]

훨씬 강한 물리적 형태를 띠는 아키텍처 경계로는 로컬 프로세스가 있다.

로컬 프로세스는 주러 명령행이나 그와 유사한 시스템 호출을 통해 생성된다.

 

대개의 경우 로컬 프로세스는 소켓이나 메일박스, 메시지 큐와 같이 운영체제에서 제공하는 통신 기능을 이용하여 서로 통신한다.

 

로컬 프로세스를 일종의 최상위 컴포넌트라고 생각하자. 즉, 로컬 프로세스는 컴포넌트 간 의존성을 동적 다형성을 통해 관리하는 저수준 컴포넌트로 구성된다.

저수준 프로세스가 고수준 프로세스의 플러그인이 되도록 만드는 것이 아키텍처 관점의 목표라는 사실을 기억하자.

 

로컬 프로세스 경계를 지나는 통신에는 운영체제 호출, 데이터 마샬링 및 언마샬링, 프로세스 간 문맥 교환 등이 있으며, 이들은 제법 비싼 작업에 속한다.

 

[서비스]

물리적인 형태를 띠는 가장 강력한 경계는 바로 서비스다.

서비스는 프로세스로, 일반적으로 명령행 또는 그와 동등한 시스템 호출을 통해 구동된다.

서비스들은 모든 통신이 네트워크를 통해 이뤄진다고 가정하자.

 

서비스 경계를 지나는 통신은 함수 호출에 비해 매우 느리기 때문에 가능하다면 빈번하게 통신하는 일은 피해야 한다.

항상 저수준 서비스는 반드시 고수준 서비스에 ‘플러그인’되어야 한다.

 

[결론]

단일체를 제외한 대다수의 시스템은 한 가지 이상의 경계 전략을 사용한다.

대체로 한 시스템 안에서도 통신이 빈번한 로컬 경계지연을 중요하게 고려해야 하는 경계가 혼합되어 있음을 의미한다.

 

Ch19. 정책과 수준


 

소프트웨어 시스템이란 정책을 기술한 것이다. 실제로 컴퓨터 프로그램의 핵심부는 이게 전부다.

컴퓨터 프로그램은 각 입력을 출력으로 변환하는 정책을 상세하게 기술한 설명서다.

 

소프트웨어 아키텍처를 개발하는 기술에는 이러한 정책을 신중하게 분리하고, 정책이 변경되는 양상에 따라 정책을 재편성하는 일도 포함된다.

 

흔히 아키텍처 개발은 재편성된 컴포넌트들을 비순환 방향 그래프(DAG)로 구성하는 기술을 포함한다.

그래프에서 정점은 동일한 수준의 정책을 포함하는 컴포넌트에 해당한다.

간선은 컴포넌트 사이의 의존성을 나타낸다.

 

이러한 의존성은 소스 코드, 컴파일타임의 의존성이다.

자바의 경우 import, node의 경우 require이다.

 

여기서 중요한 것은 저수준 컴포넌트가 고수준 컴포넌트에 의존하도록 설계되어야 한다.

 

[수준]

수준(level)을 엄밀하게 정의하자면 ‘입력과 출력까지의 거리’이다.

시스템의 입력과 출력 모두로부터 멀리 위치할수록 정책의 수준은 높아진다.

 

그림 19.1 간단한 암호화 프로그램

 

 

번역 컴포넌트는 이 시스템에서 최고 수준의 컴포넌트인데, 입력과 출력에서부터 가장 멀리 떨어져 있기 때문이다.

 

정책을 컴포넌트로 묶는 기준은 정책이 변경되는 방식에 달려있다는 사실을 상기하자.

단일책임원칙(SRP)공통 폐쇄 원칙(CCP)에 따르면 동일한 이유동일한 시점에 변경되는 정책은 함께 묶인다.

고수준 정책, 즉 입력과 출력에서부터 멀리 떨어진 정책은 저수준 정책에 비해 덜 빈번하게 변경되고, 보다 중요한 이유로 변경되는 경향이 있다.

 

결국 저수준 컴포넌트가 고수준 컴포넌트에 플러그인되어야 한다는 관점으로 귀결된다.

 

그림 19.3 저수준 컴포넌트는 고수준 컴포넌트에 플러그인되어야 한다.

 

[결론]

이 장에서 설명한 정책에 대한 논의는 단일 책임 원칙, 개방 폐쇄 원칙, 공통 폐쇄 원칙, 의존성 역전 원칙, 안정된 의존성 원칙, 안정된 추상화 원칙을 모두 포함한다.

각 원칙이 어디에서 무슨 이유로 사용되었는지를 꼭 찾아보자.