들어가며
- ADP (의존성 비순환 원칙)
- SDP (안정된 의존성 원칙)
- SAP (안정된 추상화 원칙)
이 세가지 원칙은 컴포넌트 사이의 관계를 설명한다.
ADP 의존성 비순환 원칙
"컴포넌트 의존성 그래프에 순환이 있어서는 안된다."
무언가를 의존하고 테스트 완료 후 작업을 종료했지만 다음날 다시 보았을 때 오류가 난다면 당신이 작업을 종료한 후 누군가가 의존하고 있던 무언가를 수정했기 때문이다. 이를 '숙취 증후군'이라고 부른다.
의존성 비순환 원칙으로 해당 문제를 해결 할 수 있다.
순환 의존성 제거하기
해결책은 개발 환경을 릴리즈가 가능한 컴포넌트 단위로 분리하는 것이다. 개발자가 해당 컴포넌트가 동작하도록 만든 후 해당 컴포넌트를 릴리즈하여 다른 개발자가 사용할 수 있도록 만든다.
다른 팀에서는 새 릴리즈를 당장 적용 할지를 결정만 하면 된다.
따라서 특정 컴포넌트가 변경 되더라도 다른 팀에 즉각 영향을 주지는 않는다.
Presenters를 담당하는 팀이 해당 컴포넌트의 새로운 릴리즈를 만든다고 하면 이를 의존하는 컴포넌트는 View, Main이 해당된다. 새로운 릴리즈가 나왔다면 언제 통합 할 지만 결정하면 된다.
추가로 Main은 새로 릴리즈 되더라도 이로 인해 영향을 받는 컴포넌트는 없다. 또, 시스템 전체를 릴리즈해야 할 때가 오면 릴리즈 절차는 상향식으로 진행된다.
순환이 컴포넌트 의존성 그래프에 미치는 영향
Database 컴포넌트를 새로 릴리즈한다 하면 DB 컴포넌트가 의존하는 Entities컴포넌트와는 호환이 되어야 하지만 Entities역시 Authorizer 컴포넌트를 의존하고 있어서 DB는 Autorizer와도 호환이 되어야 한다. 그러나 Authorizer는 Interactors 또한 의존하고 있어 DB는 릴리즈하기 훨씬 어려워진다. Entities, Authorizer, Interactors는 하나의 거대한 컴포넌트가 되어버린다. 개발자는 3개중 어느것을 개발하더라도 '숙취 증후군'을 경험하게 된다.
단위 테스트 또한 의존하고있는 컴포넌트를 빌드하고 통합 해야해서 릴리즈하는 일도 굉장히 어려워지며 에러도 쉽게 발생하고 모듈의 개수가 많아짐에 따라 빌드 관련 이슈는 기하급수적으로 증가하게 된다.
이전 내용에서는 전체 시스템을 릴리즈할 때 상향식으로 진행하면 됐지만 순환 구조는 어떤 순서로 빌드하면 되는지 파악하기 상당히 힘들어지기도 한다.
순환끊기
컴포넌트 사이의 순환을 끊는 메커니즘 두가지
- 의존성 역전 원칙(DIP)을 적용한다.
(User가 필요로하는 메서드를 제공하는 인터페이스를 Entities에 위치시키고 Autorizer는 이 인터페이스를 상속한다.)
- Entities와 Autorizer가 모두가 의존하는 컴포넌트를 만들며 이 컴포넌트안에 두 컴포넌트가 모두 의존하는 클래스들을 포함시킨다.
흐트러짐
요구사항이 변경되면 컴포넌트 구조도 흐트러지며 변경되기 때문에 순환구조가 발생하는지 항상 관찰해야 한다.
하향식 설계
컴포넌트구조는 하향식으로 설계될 수 없다. 컴포넌트는 시스템에서 가장 먼저 설계할 수 있는 대상이 아니며, 오히려 시스템이 성장하고 변경될 때 함께 진행한다.
결론
애플리케이션이 계속 성장함에 따라 우리는 재사용 가능한 요소를 만드는 일에 관심을 기울이기 시작한다. 이 시점이 되면 컴포넌트를 조합하는 과정에 CRP(공통 재사용 원칙)이 영향을 미치기 시작하면 결국 순환이 발생되어 ADP(의존성 비순환 원칙)이 적용되고, 컴포넌트 의존성 그래프는 조금씩 흐트리지며 또 성장한다.
SDP 안정된 의존성 원칙
"안정성의 방향으로(더 안정된 쪽에) 의존하라."
설계를 유지하다 보면 변경은 불가피하다. CCP(공통 폐쇄 원칙)을 준수함으로써, 컴포넌트가 다른 유형의 변경에는 영향을 받지 않으면서도 특정 유형의 변경에만 민감하게 만들 수 있다. 이처럼 컴포넌트 중 일부는 변동성을 지니도록 설계된다.
변경이 쉽지 않은 컴포넌트가 변동성이 큰 컴포넌트를 의존하게 만들어서는 절대로 안된다. 한번 의존하게 되면 변동성이 큰 컴포넌트도 결국 변경하기 어려워지기 때문이다.
SDP(안정된 의존성 원칙)을 준수하면 변경하기 어려운 모듈이 변경하기 쉽게 만들어진 모듈에 의존하지 않도록 만들 수 있다.
안정성
안정적이란 말은 '쉽게 움직이지 않는'이라고 정의할 수 있다.
컴포넌트 안쪽으로 들어오는 의존성이 많아지면 상당히 안정적으로 볼 수 있다. 많은 컴포넌트들이 의존하고있는 컴포넌트는 사소한 변경이 생기더라도 의존하는 모든 컴포넌트를 만족 시키면서 변경 하려면 상당한 노력이 들기 때문이다.
이 경우 X는 세 컴포넌트를 책임진다라고 말할 수 있고 X는어디에도 의존하지 않으므로 독립적이다라고 말할 수 있다.
Y는 상당히 불안정한 컴포넌트이다. 또한 어떤 컴포넌트도 Y에 의존하지 않으므로 Y는 책임성이 없다고 말할 수 있다. 이 경우 Y는 의존적이라고 말한다.
안정성지표
Fan-in: 안으로 들어오는 의존성
Fan-out: 바깥으로 나가는 의존성.
I(불안정성): I = Fan-out / (Fan-in + Fan-out)
0 ≤ I ≤ 1
I=0이면 최고로 안정된 컴포넌트이며 반대로 I=1이면 최고로 불안정한 컴포넌트라는 뜻이다.
따라서, CC컴포넌트의 불안정성은 1/4인 0.25다.
SDP에서 컴포넌트의 불안정성(I 지표)은 그 컴포넌트가 의존하는 다른 컴포넌트들의 불안정성보다 커야 한다고말한다. 즉, 의존성 방향으로 갈수속 불안정성 값은 감소해야 한다.
모든 컴포넌트가 안정적이어야 하는 것은 아니다.
그림은 SDP가 위배되는 사례이다.
Flexible은 변경하기 쉽도록 설계한 컴포넌트다. 하지만 Stable 컴포넌트에서 작업하던 개발자가 Flexible에 의존성을 걸게 되었다. 이로 인해 SDP를 위배하는데, Stable이 Flexible을 의존하기 이전에 Stable의 I지표는 Flexible의 I지표보다는 더 작기 때문이다. 결국 Flexible은 변경하기가 어렵게 되었다.
어떤 조취를 취할 수 있을까?
DIP를 도입하면 이 문제를 해결할 수 있다.
먼저 US라는 인터페이스를 생성한 후 UServer 컴포넌트에 넣는다.
C가 해당 인터페이스를 구현하도록 만든다. 이를 통해 Stable의 Flexible에 대한 의존성을 끊을 수 있고, 두 컴포넌트는 모두 UServer에 의존하도록 강제한다. UServer는 매우 안정된 상태이며(I=0), Flexible은 자신에게 맞는 불안정성(I=1)을 그대로 유지할 수 있다. 이제 모든 의존성은 I가 감소하는 방향으로 향하게 된다.
SAP 안정된 추상화 원칙
고수준 정책을 어디에 위치시켜야 하는가?
시스템에서는 자주 변경해서는 절대 안되는 소프트웨어도 있다. 고수준 아키텍처나 정책 결정과 관련된 소프트웨어가 그 예다.
그렇다고 해서 고수준 정책을 안정된 컴포넌트에 위치시키면, 그 정책을 포함하는 소스코드는 수정 하기가 어려워진다. 컴포넌트가 최고로 안정된 상태이면서도(I=0) 동시에 변경에 충분히 대응할 수 있을 정도로 유연하게 만들 수 있는 방법은 OCP(개방폐쇄원칙)에서 찾을 수 있다.
→ 수정이아닌 확장이 가능하게 클래스를 유연하게 만든다.
안정된 추상화 원칙
안정된 추상화 원칙은 안정성과 추상화 정도사이의 관계를 정의한다.
변동성이 적은 안정적인 컴포넌트라면 반드시 인터페이스와 추상 클래스로 구성되어 쉽게 확장할 수 있어야한다. 안정된 컴포넌트가 확장이 가능해지면 유연성을 얻게 되고 "아키텍처를 과도하게 제약하지 않게 된다." ←이해안감.
SDP(안정된 의존성 원칙)와 SAP(안정된 추상화 원칙)를 결합하면 컴포넌트에 대한 DIP(의존성 역전 원칙)나 마찬가지가 된다. SDP에서는 의존성이 반드시 안정성 방향으로 향하게 되고 SAP는 안정성이 결국 추상화를 의미한다고 말하기 때문이다.
따라서, 의존성은 추상화 방향으로 향하게 된다.
단, DIP는 클래스에 대한 원칙이며, 클래스는 추상적이거나 아니거나 둘중 하나이다. 반면 SDP와 SAP의 조합은 컴포넌트에 대한 원칙으로 컴포넌트는 어떤 부분은 추상적이며 다른부분은 안정적일 수 있다.
추상화 정도 측정하기
Nc: 컴포넌트의 클래스 개수
Na: 컴포넌트의 추상 클래스와 인터페이스 개수
A: 추상화 정도, A = Na / Nc
0 ≤ A ≤ 1
A가 0이면 컴포넌트에는 추상 클래스가 하나도 없다는 뜻이며 1이면 컴포넌트는 오로지 추상 클래스만을 포함한다는 뜻이다.
주계열
A(추상화 정도) / I(안정성) 그래프
최고로 안정적이며 추상화된 컴포넌트는 좌측상단 (0,1)에 해당
최고로 불안정적이며 구체화된 컴포넌트는 우측하단 (1,0)에 해당
그러나 모든 컴포넌트가 이 두 지점에 위치하지는 않는다.
예를들어 추상클래스는 흔히 또 다른 추상클래스를 파생한다. 이 파생된 클래스는 추상적이면서도 최고로 안정적인것은 아니다. 의존성으로 인해 안정성이 감소했기 때문이다.
그러므로 모든 컴포넌트가 두 지점에 위치한다는 규칙을 강요할 수 없으므로 A/I 그래프상 합리적인 지점을 정의하는 점의 궤적이 존재한다.
고통의 구역
(0,0) 주변 구역에 위치한 컴포넌트는 매우 안정적이며 구체적이다. 추상적이지 않으므로 확장할 수 없고, 안정적이므로 변경하기도 상당히 어렵다. (0,0) 주변 영역은 배제해야 할 구역이며, 고통의 구역이라고 부른다.
(0,0) 근처에 위치한 또 다른 소프트웨어로는 구체적인 유틸리티 라이브러리중 String컴포넌트를 예로 들 수 있다. 이 컴포넌트는 광범위하게 사용되므로 수정해버리면 혼란을 초래한다. 따라서 변동성이 거의 없다.
변동성이 없는 컴포넌트는 (0,0) 구역에 위치했더라도 해롭지 않다. 변동될 가능성이 없기 때문에 변동성이 있는 컴포넌트만 고통의 구역에서 문제가 된다고 정의할 수 있다.
쓸모없는 구역
(1,1) 주변의 컴포넌트는 최고로 추상적이지만, 누구도 이 컴포넌트에 의존하지 않기 때문에 쓸모없는 구역이라고 불린다.
배제 구역 벗어나기
두 배제 구역으로부터 가능한 멀리 떨어진 궤적은 (1,0)과 (0,1)을 잇는 선분인 주계열이다.
주계열에 위치한 컴포넌트는 자신의 안정성에 비해 '너무 추상적'이지도 않고, 추상화 정도에 비해 '너무 불안정'하지도 않다.
컴포넌트가 위치할 수 있는 가장 바람직한 지점은 주계열의 두 종점이지만 저자는 경험을 통해 대규모 시스템에서 소수의 일부 컴포넌트는 완벽히 추상적이거나 완전하게 안정적일 수 없다고 말한다.
주계열과의 거리
D : 주계열로부터의 거리
D = | A + I - 1 |
0 ≤ D ≤ 1
D가 0이면 컴포넌트가 주계열 바로위에 위치하며, 1이면 주계열로부터 가장 멀리 위치함을 뜻한다.
'📚 Book > Clean Architecture' 카테고리의 다른 글
16장 독립성 (0) | 2020.02.22 |
---|---|
15장 아키텍처란? (0) | 2020.02.22 |
13장 컴포넌트 응집도 (0) | 2020.02.19 |
11장 DIP: 의존성 역전 원칙 (0) | 2020.02.19 |
10장 ISP: 인터페이스 분리 원칙 (0) | 2020.02.19 |