코딩스토리

Clean Architecture 스터디[14장] 컴포넌트 결합 본문

Clean Architecture

Clean Architecture 스터디[14장] 컴포넌트 결합

라크라꾸 2022. 2. 6. 13:32

컴포넌트 결합

컴포넌트 결합은 컴포넌트 사이의 관계를 설명한다.

 

ADP: 의존성 비순환 원칙

컴포넌트 의존성 그래프에 순환이 있어서는 안 된다.

 

숙취 증후군

많은 개발자가 동일한 소스 파일을 수정하는 환경에서 발생한다. 보통 프로젝트가 커지면서 누군가 마지막으로 수정한 코드 때문에 망가진 부분을 수정하고, 또 수정하는 작업을 반복하면서 발생된다.

 

해결법

  • 주 단위 빌드
  • 의존선 비순환 원칙

 

주 단위 빌드(Weekly Build)

  • 중간규모 프로젝트에서 흔하게 사용한다.
  • 정해진 기간동안 각자 신경쓰지 않고 개발을 하다가, 날 잡고 통합하여 시스템 빌드하는 방법이다.
  • 프로젝트가 커지며 통합하는 시간이 길어지며, 개발하는 시간을 침범하게 되어 효율성이 점점 떨어지게됨.

 

순환 의존성 제거

 

개발자가 개발 -> 릴리스 -> 다른 개발자가 사용

 

  • 이 문제의 해결책은 개발 환경을 릴리스 가능한 컴포넌트 단위로 분리하는 것이다.
  • 이렇게 되면 컴포넌트는 개별 개발자 또는 단일 개발팀이 책일을 질 수 있는 닥업 단위가 된다.
  • 컴포넌트가 변경되어도 다른 팀에 영향을 주지 않음
  • 이 방법은 단순하고 합리적이어서 널리 사용되지만 성공적으로 동작하려면 컴포넌트 사이의 의존성구조를 반드시 관리해야 한다. 의존성 구조에 순환이 생기면 '숙취 증후군'을 피해 갈 수 없다.

 

전형적인 컴포넌트 다이어그램

 

위의 컴포넌트들을 가각의 팀이라고 생각하고, Presenters를 담당하는 팀이 새로 릴리즈를 하게 됬다고 가정을 해보면, Presenters를 의존하는 컴포넌트는 View와 Main이 해당하게 됩니다. View와 Main팀의 작업중인 개발자라면, Presenters의 새로운 릴리스와 자신의 작업물을 언제 통합할지만 결정하면 된다. 또, Main이 새로 릴리스가 되더라도, 이를 의존하는 컴포넌트가 없기 때문에 영향을 받는 컴포넌트가 없다. 시스템 전체를 릴리스 해야 할 시기에는 Entities -> Database,Interactors -> Presenter, View, Controllers, Authorizer ->Main 순서로 절차는 상향식으로 진행된다.

 

순환이 컴포넌트 의존성 그래프에 미치는 영향

 

순환 의존성

 

이 순환은 즉각적인 문제를 일으킨다. 

Database 컴포넌트 릴리스 -> Entities 의존 -> Authorizer의존 -> Interactors 의존 -> Entities 의존 -> 순환... 

단순히 Database를 릴리스 했을 뿐인데, Entities에 호환되어야하고, Authorizer에 호환되어야하고, Interactors에도 호환되어야하는게 계속 순환되기 때문에 Database는 릴리스 하기가 어려워진다. 이렇게 되면 거대한 Entities, Authorizer, Interactors는 거대한 컴포넌트가 되버린다. 

 

순환 끊기

순환 끊기의 방법

  • 의존성 역전 활용

 

위와 같이 User가 필요하는 메서드를 제공하는 인터페이스를 생성한다. 이 인텊페이스는 Entities에 위치시키고, Authorizer에서는 이 인터페이스를 상속받는다. 이렇게 하면 Entities와 Authorizer 사이의 의존성을 역전시킬 수 있고, 이를 통해 순환을 끊을 수 있다.

 

  • 두 컴포넌트가 의존하는 새로운 컴포넌트를 만든다

Entities와 Authorizer가 모두 의존하는 새로운 컴포넌트를 만든다. 그리고 두 컴포넌트가 모두 의존하는 클래스들을 새로운 컴포넌트로 이동시킨다.

 

흐트러짐

요구사항이 변경되면 컴포넌트 구조도 변경될 수 있다. 애플리케이션이 성장함에 따라 컴포넌트 의존성 구조는 서서히 흐트러지며 또 성장한다. 따라서 의존성 구조에 순환이 발생하는지를 항상 관찰해야 한다.

 

하향식(top-down) 설계

컴포넌트구조는 하향식으로 설계될 수 없다. 

컴포넌트는 시스템에서 가장 먼저 설계할 수 있는 대상이 아니며, 오히려 시스템이 성장하고 변경될 때 함께 진행한다.

 

컴포넌트 의존성 다이어그램은 애플리케이션의 빌드 가능성과 요주보수성을 보여주는 지도와 같다. 컴포넌트 구조는 프로젝트 초기에 설계할 수 없다. 구현과 설계가 이뤄지는 프로젝트 초기에 모듈들이 점차 쌓이기 시작하면, ‘숙취 증후군’을 겪지 않고 프로젝트를 개발하기 위해서 의존성 관리에 대한 요구가 점차 늘어나게 된다. 뿐만 아니라 변경되는 범위가 시스템의 가능한 한 작은 일부로 한정되기를 원한다.

의존성 구조와 관련된 최우선 관심사는 변동성을 격리하는 일이다. 우리는 변덕스러운 이유로 자주 변경되는 컴포넌트로 인해, 그렇지 않았다면 안정적이었을 컴포넌트가 영향받는 일을 원치 않는다. 결국 컴포넌트 의존성 그래프는 자주변경되는 컴포넌트로부터 안정적이며 가치가 높은 컴포넌트를 보호하려는 아키텍트가 만들고 가다듬게 된다.

 

애플리케이션이 계속 성장함에 따라 우리는 재사용 가능한 요소를 만드는 일에 관심을 기울이기 시작한다. 이 시점이 되면 컴포넌트를 조합하는 과정에 공통 재사용 원칙(CRP)이 영향을 미치기 시작한다.

 

SDP:안정된 의존성 원칙

안정성의 방향으로(더 안정된 쪽에) 의존하라.

설계를 유지하다 보면 변경은 불가피하다. CCP(공통 폐쇄 원칙)을 준수함으로써, 컴포넌트가 다른 유형의 변경에는 영향을 받지 않으면서도 특정 유형의 변경에만 민감하게 만들 수 있다. 이처럼 컴포넌트 중 일부는 변동성을 지니도록 설계된다.

변경이 쉽지 않은 컴포넌트가 변동성이 큰 컴포넌트를 의존하게 만들어서는 절대로 안된다. 한번 의존하게 되면 변동성이 큰 컴포넌트도 결국 변경하기 어려워지기 때문이다.

SDP(안정된 의존성 원칙)을 준수하면 변경하기 어려운 모듈이 변경하기 쉽게 만들어진 모듈에 의존하지 않도록 만들 수 있다.

 

안정성

소프트웨어 컴포넌트를 변경하기 어렵게 만드는 확실한 방법 하나는 수많은 다른 컴포넌트가 해당 컴포넌트에 의존하게 만드는 것이다. 컴포넌트 안쪽으로 들어오는 의존성이 많아지면 상당히 안정적으로 볼 수 있는데, 사소한 변경이라도 의존하는 모든 컴포넌트를 만족시키면서 변경하려면 상당한 노력이 들기 때문이다.

X는 안정된 컴포넌트다.

  • 위 그림은 안정된 컴포넌트다.
  • 세 컴포넌트가 X에 의존하며, 따라서 X컴포넌트는 변경하지 말아야 할 이유가 세가지나 된다.
  • 이 경우 'X는 세 컴포넌트를 책임진다' 라고 말한다.
  • 반대로 X는 어디에도 의존하지 않으므로 X가 변경되도록 만들 수 있는 외적인 형향이 없다. 이 경우 'X는 독립적이라' 라고 말한다.

 

Y는 상당히 불안정한 컴포넌트다

 

  • Y는 상당히 불안정한 컴포넌트다
  • 어떤 컴포넌트도 Y에 의존하지 않으므로 Y는 책임성이 없다고 말할 수 있다.
  • 또한 Y는 세 개의 컴포넌트에 의존하므로 변경이 발생할 수 있는 외부 요인이 세 가지이다.
  • 이 경우 Y는 의존적이라고 말한다.

 

안정성 지표

  • 의존성 개수 세는 방법
  • Fan-in : 안으로 들어오는 의존성
  • Fan-out : 바깥으로 나가는 의존성
  • I(불안정성) : I = Fan-out/(Fan-in + Fan-out)
  • 이 지표는 [0,1] 범위의 값을 갖는다. 0에 가까울 수록 안정된 컴포넌트라는 뜻이다.

 

예제

 

Cc의 컴포넌트의 안정성 계산

  • Cc내부의 클래스에 의존하며 Cc외부에 있는 클래스는 세 개다. (Fan-in = 3)
  • Cc내부의 클래스가 의존하는 Cc외부에 위치한 클래스는 한 개다. (Fan-out = 1)
  • I = 1/4 = 0.25

SDP에서 컴포넌트의 I지표는 그 컴포넌트가 의존하는 다른 컴포넌트들의 I보다 커야 한다고 말한다. 즉, 의존성 방향으로 갈수록 I지표가 값이 감소 해야 한다.

 

모든 컴포넌트가 안정적이어야 하는 것은 아니다

세 컴포넌트로 구성된 시스템이 가질 수 있는 이상적인 구조의 다이어그램

 

세 컴포넌트로 구성된 시스템의 이상적인 구성

 

  • 위쪽에 변경 가능한 컴포넌트가 있고, 아래의 안정된 컴포넌트에 의존한다.
  • 다이어그램에서 불안정한 컴포넌트를 관례적으로 위쪽에 둔다.(따르면 유용)
  • 위로 향하는 화살표가 있으면 SDP를 위배하는(ADP도 위반하는) 상태가 되기 때문이다.

 

SDP가 어떻게 위배되는 구조의 다이어그램

SDP 위배

Flexible은 변경하기 쉽도록 설계한 컴포넌트다. 하지만 Stable 컴포넌트에서 작업하던 개발자가 Flexible에 의존성을 걸게 되었다. 이로 인해 SDP를 위배하는데, Stable Flexible을 의존하기 이전에 Stable I(불안정성)지표는 Flexible I(불안정성)지표보다는 더 작기 때문이다. 결국 Flexible은 변경하기가 어렵게 되었다.

 

해결책

DIP를 도입하면 이 문제를 해결할 수 있다.

 

C는 US 인터페이스를 구현한다.

 

먼저 US라는 인터페이스를 생성한 후 UServer 컴포넌트에 넣는다.

C가 해당 인터페이스를 구현하도록 만든다. 이를 통해 Stable의 Flexible에 대한 의존성을 끊을 수 있고, 두 컴포넌트는 모두 UServer에 의존하도록 강제한다. UServer는 매우 안정된 상태이며(I=0), Flexible은 자신에게 맞는 불안정성(I=1)을 그대로 유지할 수 있다. 이제 모든 의존성은 I가 감소하는 방향으로 향하게 된다.

 

SAP: 안정된 추상화 원칙

컴포넌트는 안정된 정도만큼만 추상화되어야 한다.

 

고수준 정책을 어디에 위치시켜야 하는가?

시스템에는 자주 변경해서는 절대로 안 되는 소프트웨어도 있다. 고수준 아키텍처나 정책 결정과 관련된 소프트웨어가 그 예다.

업무 로직이나 아키텍처와 관련된 결정에는 변동성이 없기를 기대한다. 따라서 시스템에서 고수준 정책을 캡슐화하는 소프트웨어는 반드시 안정된 컴포넌트(I=0)에 위치해야 한다. 불안정한 컴포넌트(I=1)는 반드시 변동성이 큰 소프트웨어, 즉 빠르게 변경할 수 있는 소프트웨어만을 포함해야 한다.

 

하지만 고수준 정책을 안정된 컴포넌트에 위치시키면, 그 정책을 포함하는 소스 코드는 수정하기가 어려워진다. 이로 인해 시스템 전체 아키텍처가 유연성을 잃는다. 컴포넌트가 최고로 안정된 상태이면서도(I=0) 동시에 변경에 충분히 대응할 수 있을 정도로 유연하게 만들 수 있다. 개방 폐쇄 원칙(OCP)를 활용하는 것이다. OCP 에서는 클래스를 수정하지 않고도 확장이 충분히 가능할 정도로 클래스를 유연하게 만들 수 있다. 추상(abstract) 클래스가 이 원칙을 준수한다.

 

안정된 추상화 원칙

안정된 추상화 원칙(Stable Abstractions Principle, SAP)은 안정성(stability)과 추상화 정도(abstractness) 사이의 관계를 정의한다.

  • 안정된 컴포넌트는 추상 컴포넌트여야 하며, 안정성이 컴포넌트를 확장하는 일을 방해해서는 안 된다고 말한다.
  • 불안정한 컴포넌트는 반드시 구체 컴포넌트여야 하며, 불안정하므로 컴포넌트 내부의 구체적인 코드를 쉽게 변경할 수 있어야 하기 때문이다.
  • SAP와 SDP를 결합하면 컴포넌트에 대한 DIP나 마찬가지가 된다.
    • 실제로 SDP에서는 의존성이 반드시 안정성의 방향으로 향해야 한다고 말하고
    • SDP에서는 안정성이 결국 추상화를 의미한다고 말하기 때문이다.
    • 따라서 의존성은 추상화의 방향으로 향하게 된다.

핵심은, 안정적인 컴포넌트라면 반드시 인터페이스와 추상 클래스로 구성되어 쉽게 확장할 수 있어야 한다.

 

추상화 정도 측정하기

다음은 컴포넌트의 클래스 총 수 대비 인터페이스와 추상 클래스의 개수를 단순히 계산한 값이다.

  • NC: 컴포넌트의 클래스 개수
  • Na: 컴포넌트의 추상 클래스와 인터페이스의 개수
  • A: 추상화 정도 
    • A = Na / Nc
    • 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이면 주계열로부터 가장 멀리 위치함을 뜻한다.

Comments