[도서] 클린아키텍처 Clean Architecture 정리 12~14장
12장 컴포넌트
컴포넌트는 독립적으로 배포 가능한 가장 작은 단위
여러 컴포넌트를 묶어서 실행 가능한 단일 파일로 만들거나 동적으로 로드 할 수 있는 플러그인으로 만들수도 있음
컴포넌트의 간략한 역사
과거에는 프로그램이 로드될 메모리의 주소를 직접 코드에 작성해서 컴파일러에게 알려줬어야했음
메모리 주소의 여유공간이 없어지면 다른 빈 공간에 로드해서 두 주소를 오가며 작동하게 했는데 이는 지속 가능한 방법이 아니었다.
재배치성
이런 불편함을 해결하기위해 프로그램을 재배치 가능하게 만들었음
로더를 두고 로더에 재배치 코드전달한뒤 재배치가 가능한지 여부를 바이너리에서 참조할 수있도록 컴파일러를 수정
이 과정에서 링킹로더가 탄생했음. 링킹로더는 바이너리 코드에서 라이브러리 함수를 호출하는 외부참조와 라이브러리 함수를 정의하는 외부정의 함수이름을 서로 링크시켜서 메타데이터로 생성했다.
링커
프로그램이 커지면서 링킹로더의 링킹 시간이 너무 오래걸림. 대게 외부 라이브러리는 느린 장치에 저장되었어서..
프로그래머가 링커라는 별도 프로램으로 링킹을 직접 했음. 이 과정이 느리긴 해도 한 번 실행 파일로 만들어 두면 계속 쓸 수 있었어서 괜찮았다.
하지만 계속 프로그램의 규모가 커지면서 전체 소요시간이 다시 늘어남
메모리 용량이 늘어나고 처리 속도가 빨라지는 등 기술이 발전하면서 해결됐음
이제 다시 로드와 링크를 같이해도 오랜 시간이 소요되지않음
결론
덕분에 다수의 공유 라이브러리들을 런타임에 링크해서 동작하는 플러그인 아키텍처 구현이 가능해짐
13장 컴포넌트 응집도
어떤 클래스를 어느 컴포넌트에 포함시켜야할지. 컴포넌트의 응집과 관련된 원칙 세가지 소개하겠음
- REP : 재사용/릴리스 등가 원칙
- CCP : 공통 폐쇄 원칙
- CRP : 공통 재사용 원칙
REP : 재사용/릴리스 등가 원칙
재사용 단위 = 릴리스 단위
컴포넌트의 릴리스 단위와 재사용 단위가 같아야함은 당연한 이치 같은것. 컴포넌트가 릴리즈 절차에 의해 추적관리되지않으면 어떻게 재사용을 할까? 언제 출시됐는지 뭐가 바뀌었는지도 모르고 새 릴리즈를 통합 할 수는 없음
간단히 말해서 단일 컴포넌트는 응집성이 높은 클래스와 모듈로 구성되어야하고 동일한 릴리즈로 관리되어야 한다.
그렇다고 무작정 클래스와 모듈을 한데 모으라고 하는것은 설명이 부족하기때문에 다음 원칙들을 살펴보겠음
CCP : 공통 폐쇄 원칙
컴포넌트를 변경해야하는 어떤 하나의 이유로 변경되는 클래스들을 한데 묶어라
단일 책임 원칙(SRP)의 컴포넌트버전
어떤 이유로 컴포넌트를 변경해야할때 변경할 클래스들이 여러 컴포넌트에 분산되어있는것보다 하나의 컴포넌트에 있는게 낫다. 해당 컴포넌트만 재배포하면 되니까
공통적인 변경에는 닫혀있도록 해야함 → 공통적인 변경(여러 컴포넌트를 수정해야하는)때문에 클래스가 변경되는 일이 없도록 해야함
SRP와의 유사성
같은 이유로 동시에 변경되어야 하는 것들을 한데 묶고
다른 이유로 서로 다른 시점에 변경되는 것들을 분리해라.
CRP : 공통 재사용 원칙
대게 재사용되는 클래스는 클래스가 속해있는 재사용 모듈의 다른 클래스와 상호작용한다. 이런 클래스들은 동일한 컴포넌트에 한데 묶어야 한다.
만약 한 컴포넌트가 다른 컴포넌트의 클래스를 단 하나만 사용한다고해도 의존성이 생겨난다.
이 의존성 때문에 사용되는 컴포넌트가 변경될때마다, 심지어 이 변경이 사용하는 컴포넌트와 관련이 없더라도 사용하는 컴포넌트를 재컴파일, 재배포해야하는 가능성이 생기게됨
한 컴포넌트에 속해있는 모든 클래스들은 서로 확실히 의존하고 있어야함
만약 쌩뚱맞은 클래스가 컴포넌트에 속해있으면 이녀석때문에 불필요한 재배포에 노력을 허비해야함
다시말해 CRP는 공통으로 재사용되지않는 클래스들을 한데 묶지 말아야함에 주안점을 둔다.
ISP와의 관계
사용하지 않는 메서드가 있는 클래스에 의존하지 말아야 하듯이
사용하지 않는 클래스가 있는 컴포넌트에 의존하지 말아라.
컴포넌트 응집도에 대한 균형 다이어그램
위에서 언급한 세 원칙은 서로 상충한다.
REP와 CCP는 컴포넌트를 크게 모으는 포함원칙이고 CRP는 컴포넌트를 작게 나누는 배제원칙이기때문에
아키텍트는 세 원칙 중 어떤점에 중점을 둘지 균형을 이뤄야함
대게 개발을 시작하고 시간이 흐르면서 중점을 두는 부분이 변함
개발 초기에는 재사용성보다는 개발 가능성이 중요하기때문에 CCP에 중점을 둠
시간이 흘러 프로젝트가 커지고 파생 프로젝트가 생기면 재사용성에 중점을 두게됨
결론
REP, CCP, CRP 세 원칙은 컴포넌트 응집이 복잡 다양하게 이뤄질수 있음을 보여준다.
어떤 클래스들을 한 컴포넌트에 묶을지 결정할때 서로 상충하는 재사용성과 개발가능성을 잘 고려해야함
시간이 흐르면서 프로젝트의 성숙도가 변하면 세 원칙 사이의 균형점도 바뀐다.
14장 컴포넌트 결합
컴포넌트 사이의 관계를 설명하는 세 가지 원칙
ADP : 의존성 비순환 원칙
컴포넌트 의존성에 순환이 있어서는 안된다.
개발 완료 시점에는 정상 작동하지만 이후 누군가 의존하고있는 소스코드를 수정하면 작동을 안하는 경우 발생함
만약 의존성이 순환하면 규모가 큰 프로젝트일수록 안정화버전 빌드하는데 많은 시간을 소비하게됨
해결 책 두가지 주 단위 빌드, 순환 의존성 제거
주 단위 빌드
한 주에 일정 시간 예를 들면 4일은 각자 혼자 개발하고 남은 하루를 통합하여 안정화버전 빌드하는 방법
프로젝트가 커질수록 점점 통합하는 시간이 오래걸리게 되고 개발 시간이 줄어들게됨
결국 통합과 테스트 수행이 어려워지고 팀의 효율성이 저하됨
순환 의존성 제거하기
먼저 개발환경을 릴리즈 가능한 컴포넌트 단위로 분리. 개인 혹은 각 팀은 릴리즈 버전을 사용해서 개발한다.
의존하고있는 컴포넌트의 새 릴리즈는 안정성을 확보한뒤 적용한다.
통합이 작은 단위로 점진적으로 이뤄지기때문에 각 팀마다 서로 영향을 주지 않는다.
위 방식이 잘 작동하려면 의존성이 한쪽으로 흐르고있어야함. 순환이 있어서는 안된다.
의존성 구조를 파악하고있으면 어떤 컴포넌트의 릴리즈가 어떤 팀에 영향을 주는지 알 수 있음
순환이 컴포넌트 의존성 그래프에 미치는 영향
만약 컴포넌트 의존성 구조에 순환이 있으면 하나의 컴포넌트를 릴리즈하는데 순환 때문에 얽혀있는 거대한 컴포넌트 집합을 신경써야함
순환 끊기
두 가지 방법
- 의존성 역전 원칙(DIP) 적용
- 순환을 유발하는 컴포넌트들이 의존하는 새 컴포넌트를 만들어서 순환 끊기
흐트러짐
순환 끊기의 두번째 방법을 따르면 컴포넌트 구조가 변경됨
프로젝트가 성장하면서 컴포넌트 의존성 구조는 변경되거나 커질 수 있다.
하향식 설계
컴포넌트 의존성 구조는 애플리케이션의 기능을 서술하고있지는 않는것 같다.
오히려 빌드 가능성과 유지보수성을 나타낸다.
위 이유로 컴포넌트 구조는 하향식으로 설계할 수 없음
프로젝트 초기에는 유지보수할 시스템이 작기때문에 컴포넌트 설계하기가 어려움
개발이 진행되면서 모듈들이 늘어나면 의존성 관리할 필요가 생겨난다.
자주 변경되는 컴포넌트로부터 안정적인 컴포넌트가 보호받길 바라기때문
처음부터 컴포넌트 구조를 설계하는건 어렵다. 프로젝트가 성장하면서 변경되고 진화해야함
SDP: 안정된 의존성 원칙
더 안정된 쪽에 의존해야한다.
일부 컴포넌트는 변동성이 크다. 이런 컴포넌트에 변경이 쉽지않은 컴포넌트가 의존하면 둘다 변경하기 어려워진다
안정성
안정성이란 변경하기위해 얼마의 노력이 필요한지를 의미함
소프트웨어에도 컴포넌트의 크기, 복잡도, 간결함 등 변경을 어렵게 만드는 요인이 있다.
변경을 어렵게 만드는 확실한 방법은 수많은 컴포넌트가 의존하게 하는것
안정성 지표
컴포넌트에 들어오고 나가는 의존성의 개수를 가지고 지표로 나타낼 수 있음
불안정성 = 의존하고있는 개수 / 전체 의존성 개수
의존하고 있는 개수가 많아질수록 불안정성이 올라감
의존성 방향은 불안정성이 작은 방향으로 흘러야함
모든 컴포넌트가 안정적이어야 하는 것은 아니다
모든 컴포넌트가 최고로 안정적이라면 변경이 불가능한 시스템이 된다.
추상 컴포넌트
DIP를 적용할때 사용되는 인터페이스같은 추상 컴포넌트는 상당히 안정적이다.
덜 안정적인 컴포넌트가 의존하기에 적합한 대상이다.
SAP: 안정된 추상화 원칙
컴포넌트의 안정된 정도와 추상화 정도는 비례해야한다.
고수준 정책을 어디에 위치시켜야 하는가
업무로직같은 고수준의 정책은 변동성이 없길 기대한다. 그래서 변동성이 적은 안정된 컴포넌트에 위치시켜야한다.
하지만 안정된 컴포넌트는 변경이 어려워서 시스템을 유연하게 만들 수 없게됨
OCP를 적용하면 클래스를 변경하기 쉽게 만들 수있음(추상화 클래스)
안정된 추상화 원칙
안정된 컴포넌트는 한편으로는 추상 컴포넌트여야함. 변경에 유연하게 만들어야 하니까
안정된 추상화 원칙(SAP)과 안정된 의존성 원칙(SDP)의 결합은 컴포넌트에 대한 의존성 역전 원칙(DIP)와 같음
다른 점은 클래스는 추상적이거나 아니거나 둘 중 하나이지만 컴포넌트는 일부는 안정적이고 나머지는 추상적일 수 있음
추상화 정도 측정하기
추상화 정도(A) = 추상화 클래스와 인터페의스의 개수 / 전체 컴포넌트 개수
A가 1이면 컴포넌트에 추상 클래스 한 개도 없음
A가 0이면 컴포넌트에 추상클래스만 있음
주계열
안정성과 추상화 사이의 관계를 그래프로 나타낼 수 있다
가로 축은 불안정성, 세로축은 추상화 정도라고했을때 최고로 안정적이며 추상화된 컴포는 좌측 상단 (0, 1)에 위치한다.
최고로 불안정하며 구체화된 컴포넌트는 우측 하단(1, 0)에 위치한다.
앞서 언급했듯이 클래스는 추상적이거나 아니거나 둘 중 하나이지만 컴포넌트는 안정적인 클래스나 추상화된 클래스가 전체 중 몇개냐에 따라 정도의 차이가 있다.
여러가지 경우의 수가 있겠지만 컴포넌트가 절대 있어서는 안되는 구역(배제 구역)을 제외하면 컴포넌트가 주로 있어야 하는 위치(주계열)를 추론 할 수 있음
고통의 구역
그래프에서 (0, 0) 근처의 구역
이 구역의 컴포넌트는 많은 곳에서 의존하고 있기때문에 안정성이 높아서 변경하기 쉽지 않은데다가 추상화 정도도 낮기 때문에 확장변경하기도 어려움
데이터베이스 스키마 처럼 많은곳에서 의존하는데 변동성이 높고 구체적인 경우 있음 대체로 스키마가 변경되면 고통을 수반함
(0, 0) 근처의 컴포넌트는 변동성이 없을때는 괜찮지만 변동성이 있을때는 안정성이 높고 추상화 정도나 낮기때문에 많은 고통을 수반하게됨
쓸모없는 구역
그래프에서 (1, 1) 근처의 구역
최고로 추상적이지만 불안정성이 매우 높은, 아무곳에서도 해당 컴포넌트에 의존하지 않는 구역
추상화되어서 변동이 쉽지만 누구도 컴포넌트에 의존하질 않으니 쓸모가 없음
배제 구역 벗어나기
위에서 언급한 두 구역에서 가장 멀리 떨어진 점을 찍으면 (1, 0)의 지점과 (0, 1)의 지점을 잇는 선분(주계열)이 생김
주계열에 위치한 컴포넌트들은 너무 추상적이지도 않고 너무 불안정하지도 않음
(1, 0)과 (0, 1) 지점에 컴포넌트가 위치하는게 가장 바람직하지만 현실적으로 어렵기때문에 주계열에 가까워질 수록 이상적이다.
주계열과의 거리
컴포넌트가 주계열 선분과 얼마나 떨어져있는지 정도를 측정해서 설계를 통계적으로 분석할 수 있다.
주계열과의 거리가 표준편차보다 멀다면 면밀히 살펴볼만한 근거가 됨
대게 해당 컴포넌트에 의존하는 컴포넌트가 없는데 너무 추상적이거나 반대의 경우일 것임
주계열과의 거리를 시간에 따라 나타내서 미리 정해놓은 기준을 넘었을때 그 원인을 검토해볼 수도 있음
결론
컴포넌트 결합의 세 원칙에 따라 설계된 컴포넌트들의 의존성과 추상화 정도는 측정해서 그래프로 나타낼 수 있음
덕분에 지표에서 유용한 결과를 얻어낼 수 있지만 맹신해서는 안된다.