본문 바로가기
프로그래밍 놀이터/디자인 패턴, 리펙토링

[책 정리] 16장. 독립성 - Clean Architecture

by 돼지왕 왕돼지 2022. 10. 22.
반응형

 

-

좋은 아키텍처는 다음을 지원해야 한다.

    유즈케이스

    운영

    개발

    배포

 

 

 

유즈케이스

 

-

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

아키텍처는 반드시 유스케이스를 지원해야 한다.

 

좋은 아키텍처가 행위를 지원하기 위해 할 수 있는 일 중에서 가장 중요한 사항은

행위를 명확히 하고 외부로 드러내며, 이를 통해 시스템이 지닌 의도를 아키텍처 수준에서 알아볼 수 있게 만드는 것이다.

 

이들 요소는 클래스이거나 함수 또는 모듈로서 아키텍처 내에서 핵심적인 자리를 차지할 뿐만 아니라, 자신의 기능을 분명하게 설명하는 이름을 가질 것이다.

 

 

 

운영

 

-

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

 

 

 

개발

 

-

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

 

 

 

배포

 

-

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

목표는 '즉각적인 배포(immediate deployment)'다.

좋은 아키텍처는 수십 개의 작은 설정 스크립트나 속성 파일을 약간씩 수정하는 방식을 사용하지 않는다.

좋은 아키텍처는 꼭 필요한 디렉터리나 파일을 수작업으로 생성하게 내버려 두지 않는다.

시스템이 빌드된 후 즉각 배포할 수 있도록 지원해야 한다.

 

 

 

선택사항 열어놓기

 

-

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

그러나 현실에서는 이러한 균형을 잡기가 매우 얿다.

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

더 심각한 문제는 이러한 사항들을 알고 있더라도, 시스템이 생명주기의 단계를 하나씩 거쳐감에 따라 이 사항들도 반드시 변해간다는 사실이다.

 

 

 

계층 결합 분리

 

-

모든 유스케이스를 알지 못하지만 아키텍트는 시스템의 기본적인 의도는 분명히 알고 있다.

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

 

 

-

우리는 시스템을 서로 결합되지 않은 수평적인 계층으로 분리할 수 있다.

이러한 계층의 예로는 UI, 앱에 특화된 업무 규칙, 앱과는 독립적인 업무 규칙, DB 등을 들 수 있다.

 

 

 

유스케이스 결합 분리

 

-

유스케이스도 서로 다른 이유로 변경된다.

주문 입력 시스템에서 주문을 추가하는 유스케이스는 주문을 삭제하는 유스케이스와는 틀림없이 다른 속도로, 그리고 다른 이유로 변경된다.

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

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

각 유스케이스는 UI 의 일부, 앱 특화 업무 규칙의 일부, 앱 독립적 업무 규칙의 일부, 그리고 DB 기능의 일부를 사용한다.

따라서 우리는 시스템을 수평적 계층으로 분할하면서 동시에 해당 계층을 가로지르는, 얇은 수직적인 유스케이스로 시스템을 분할할 수 있다.

 

 

 

결합 분리 모드

 

-

유스케이스에서 서로 다른 관점(aspect)이 분리되었다면, 높은 처리량을 보장해야 하는 유스케이스와 낮은 처리량으로도 충분한 유스케이스는 이미 분리되어 있을 가능성이 높다.

UI 와 DB 가 업무 규칙과 분리되어 있다면, UI 와 DB 는 업무 규칙과는 다른 서버에서 실행될 수 있다.

높은 대역폭을 요구하는 유스케이스는 여러 서버로 복제하여 실행할 수 있다.

 

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

 

 

 

개발 독립성

 

-

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

업무 규칙이 UI 를 알지 못하면 UI 에 중점을 둔 팀은 업무 규칙에 중점을 둔 팀에 그다지 영향을 줄 수 없다.

유스케이스 자체도 서로 결합이 분리되면 addOrder 유스케이스에 중점을 둔 팀이 deleteOrder 유스케이스에 중점을 둔 팀에 개입할 가능성은 거의 없다.

 

 

 

배포 독립성

 

-

결합을 제대로 분리했다면 운영 중인 시스템에서도 계층과 유스케이스를 교체(hot-swap) 할 수 있다.

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

 

 

 

중복

 

-

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

소프트웨어에서 중복은 일반적으로 나쁜 것이다.

하지만 중복에도 여러 종류가 있다.

그 중 하나는 진짜 중복이다.

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

또 다른 중복은 거짓된 또는 우발적인 중복이다. 중복으로 보이는 두 코드 영역이 각자의 경로로 발전한다면, 즉 서로 다른 속도와 다른 이유로 변경된다면 이 두 코드는 진짜 중복이 아니다.

몇 년이 지나 다시 보면 두 코드가 매우 다르다는 사실을 알게 될 것이다.

 

 

-

자동반사적으로 중복을 제거해버리는 잘못을 저지르는 유혹을 떨쳐내라.

중복이 진짜 중복인지 확인하라.

 

 

 

결합 분리 모드 (다시)

 

-

계층과 유스케이스의 결합을 분리하는 방법은 다양하다.

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

 

 

-

소스 수준 분리 모드

    소스 코드 모듈 사이의 의존성을 제어할 수 있다.

    이를 통해 하나의 모듈이 변해도 다른 모듈을 변경하거나 재컴파일하지 않도록 만들 수 있다.

    이 모드에서는 모든 컴포넌트가 같은 주소 공간에서 실행되고, 서로 통신할 떄는 간단한 함수 호출을 사용한다.

    컴퓨터 메모리에는 하나의 실행 파일만이 로드된다.

    이러한 구조를 흔히 모노리틱 구조라고 부른다.

 

 

-

배포 수준 분리 모드

    jar 파일, DLL, 공유 라이브러리와 같이 배포 가능한 단위들 사이의 의존성을 제어할 수 있다.

    이를 통해 한 모듈의 소스 코드가 변하더라도 다른 모듈을 재빌드하거나 재배포하지 않도록 만들 수 있다.

    많은 컴포넌트가 여전히 같은 주소 공간에 상주하며, 단순한 함수 호출을 통해 통신할 수 있다.

    어떤 컴포넌트는 동일한 프로세서의 다른 프로세스에 상주하고, 프로세스 간 통신, 소켓, 또는 공유 메모리를 통해 통신할 수 있다.

    이 모드의 중요한 특징은 결합이 분리된 컴포넌트가 jar 파일, Gem 파일, DLL 과 같이 독립적으로 배포할 수 있는 단위로 분할되어 있다는 점이다.

 

 

-

서비스 수준 분리 모드

    의존하는 수준을 데이터 구조 단위까지 낮출 수 있고, 순전히 네트워크 패킷을 통해서만 통신하도록 만들 수 있다.

    이를 통해 모든 실행 가능한 단위는 소스와 바이너리 변경에 대해 서로 완전히 독립적이게 된다.

 

 

-

프로젝트 초기 단계는 어떤 모드가 최선인지 알기 어렵다.

사실 프로젝트가 성숙해갈수록 최적인 모드가 달라질 수 있다.

 

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

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

마이크로서비스가 아무리 작다(micro) 하더라도, 충분히 작은 단위(fine-grained)에서 분리될 가능성은 거의 없다.

 

서비스 수준의 결합 분리가 지닌 또 다른 문제점은 개발 시간 측면뿐 아니라 시스템 자원 측면에서도 비용이 많이 든다.

필요치 않은 서비스 경계를 처리하는 데 드는 작업은 노력, 메모리, 계산량 측면에서 모두 낭비다.

 

컴포넌트가 서비스화될 가능성이 있다면 "나는" 컴포넌트 결합을 분리하되 서비스가 되기 직전에 멈추는 방식을 선호한다.

그리고는 컴포넌트들을 가능한 한 오랫동안 동일한 주소 공간에 남겨둔다.

이를 통해 서비스에 대한 선택권을 열어 둘 수 있다.

 

개발, 배포, 운영적인 문제가 증가하면 서비스 수준으로 전환할 배포 단위들을 신중하게 선택한 후, 점차적으로 이 방향으로 (즉, 서비스화 하는 방향으로) 시스템을 변경해 나간다.

 

 

-

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

또한 좋은 아키텍처라면 나중에 상황이 바뀌었을 때 이 진행 방향을 거꾸로 돌려 원래 형태인 모노리틱 구조로 되돌릴 수도 있어야 한다.

좋은 아키텍처는 이러한 변경으로부터 소스 코드 대부분을 보호한다. 좋은 아키텍처는 결합 분리 모드를 선택사항으로 남겨두어서 배포 규모에 따라 가장 적합한 모드를 선택해 사용할 수 있게 만들어준다.

 

 

 

결론

 

-

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

 

 

반응형

댓글