본문 바로가기
IT 서적/Clean Architecture

[클린 아키텍처] 14장. 컴포넌트 결합 (4부. 컴포넌트 원칙)

by Crystal.k 2022. 8. 15.

14장. 컴포넌트 결합

컴포넌트 사이의 관계를 설명하는 3가지 원칙, 개발 가능성과 논리적 설계 사이의 균형을 다룬다.

컴포넌트 구조와 관련된 아키텍처를 침범하는 힘은 기술적이며, 정치적이고, 가변적이다.

ADP: 의존성 비순환 원칙

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

많은 개발자가 동일한 소스파일을 수정하는 경우 (큰 프로젝트, 개발팀 규모가 클수록) 악몽이 시작된다

누군가가 마지막으로 수정한 코드 때문에 망가진 부분이 동작하도록 코드를 또 수정하고 또 수정하는 작업이 계속되어 안정적인 빌드를 하지 못한 채 몇 주가 지나갈 수 있다.

문제를 해결하기 위해 주 단위 빌드와 의존성 비순환 원칙(ADP)이 만들어졌다.

주 단위 빌드(Weekly Build)

중간 규모 프로젝트에서 흔히 사용

일주일의 4일 동안은 서로 신경 쓰지 않고 개인적으로 작업, 금요일이 되면 변경된 코드를 모두 통합하여 시스템을 빌드한다.

장점 : 5일 중 4일은 개인의 세계에서 작업할 수 있다.

단점: 금요일에 통합과 관련된 막대한 업보를 치러야 함.

개발보다 통합에 드는 시간이 늘어나면서 팀의 효율성이 나빠지고, 불만이 쌓이고, 격주로 빌드하자는 의견이 나오고, 빌드 주기는 점점 늦어지고, 갈수록 프로젝트가 감수할 리스크는 커진다.

빠른 피드백을 받을 수 없으며, 통합과 테스트를 수행하기 점점 어려워진다.

순환 의존성 제거하기

개발환경을 릴리즈 가능한 컴포넌트 단위로 분리하여 해결할 수 있다.

컴포넌트는 개별 개발자나 팀이 책임지는 단위가 된다.

개발자가 해당 컴포넌트가 동작하도록 만든 후 해당 컴포넌트를 릴리스하여 다른 개발자가 사용할 수 있도록 만든다.

컴포넌트에 릴리즈 번호를 부여하고, 다른 팀에서 사용할 수 있는 디렉터리로 이동시킨다. 개발자는 자신의 공간에서 해당 컴포넌트를 지속적으로 수정한다. 나머지 개발자는 릴리즈 된 버전을 사용한다.

컴포넌트가 새로 릴리즈 되어 사용할 수 있게 되면, 타 팀에선 새 릴리즈를 당장 적용할지 말지 결정한다.

특정 컴포넌트가 변경되더라도 다른 팀에 즉각 영향을 주지 않기 때문에 각 팀은 자신의 컴포넌트를 새로운 컴포넌트에 맞게 수정할 시기를 스스로 결정할 수 있다.

통합은 작고 점진적으로 이뤄지며, 특정 시점에서 모든 개발자가 한데 모여 통합할 일은 사라진다.

단순하며 합리적인 방법으로 널리 사용되는 방식이다.

이 절차가 성공적으로 동작하려면 컴포넌트 사이의 의존성 구조를 반드시 관리해야 한다. 의존성 구조에 순환이 존재하면 안 된다.

 

어느 컴포넌트에서 시작하더라도 의존성 관계를 따라가면서 최초 컴포넌트로 돌아갈 수 없어야 한다. 비순환 방향 그래프(Directed Acyclic Graph: DAG)여야 한다.

시스템 전체를 릴리즈 하게 되면 상향식으로 진행한다.

Entities → Database, interactors, → Presenters, View, Controllers, Authorizer → Main

 

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

순환이 생기는 컴포넌트들은 하나의 거대한 컴포넌트가 된다. 이 컴포넌트 중 어느 하나를 개발하더라도 서로 얽매이게 되고, 항상 정확하게 동일한 릴리즈를 사용해야만 한다.

여러 클래스 중 하나에 간단한 단위 테스트를 실행시키는데도 다양한 라이브러리와 여러 작업물을 포함해야 한다면 의존성 그래프에 순환이 있을 것이다…..

순환이 생기면 컴포넌트 분리가 어려워진다.

에러도 쉽게 발생한다. 빌드 관련 이슈는 기하급수적으로 증가한다.

어떠한 순서로 컴포넌트를 빌드해야 올바른지 파악하기 매우 어렵다.

자바와 같이 컴파일된 바이너리 파일에서 선언문을 읽어 들이는 언어라면 끔찍한 문제가 발생할 수 있다.

순환 끊기

컴포넌트 사이의 순환을 끊고 의존성을 다시 DAG로 복구하는 일은 가능하다.

주요 메커니즘 2개

  1. 의존성 역전 원칙 (DIP)를 적용.
    1. User 가 필요로 하는 메서드를 제공하는 인터페이스 생성
    2. 이 인터페이스는 Entities에 위치시키기, Authorizer에서 이 인터페이스 상속
  2. Entities와 Authorizer가 모두 의존하는 새로운 컴포넌트를 만들고 새로운 컴포넌트로 두 컴포넌트가 의존하는 클래스들을 모두 이동시킨다.

흐트러짐(Jitters)

요구사항이 변경되면 컴포넌트 구조도 변경될 수 있다.

애플리케이션이 성장함에 따라 의존성 구조는 흐트러지고 성장한다. 의존성 구성에 순환이 발생하는지를 항상 잘 관찰하고, 순환이 발생하면 어떤 식으로든 끊어야 한다.

컴포넌트를 생성하거나 의존성 구조가 더 커지더라도 끊어야 한다.

하향식(top-down) 설계

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

컴포넌트 의존성 다이어그램은 애플리케이션 기능을 기술하는 일과 관련 없고, 빌드 가능성, 유지보수성을 보여주는 지도와 같아서 초기에 설계할 수 없다.

초기 모듈이 점차 쌓이면 숙취 증후군을 겪지 않고 프로젝트를 개발하기 위해서 의존성 관리에 대한 요구가 늘어나고, 변경되는 범위가 작은 일부로 한정되길 원한다.

따라서 단일 책임원칙 공통 폐회 원칙에 관심을 갖게 된다. 변경되는 클래스는 같은 위치에 배치되도록 만든다.

의존성 구조의 최우선 관심사는 변동성을 분리(격리)하는 것이다.

컴포넌트 의존성 그래프는 자주 변경되는 컴포넌트부터 안정적이며 가치가 높은 컴포넌트를 보호하려는 아키텍트가 만들고 가다듬게 된다.

아무런 클래스도 설계하지 않은 상태에서 컴포넌트 의존성 구조를 설계하려고 시도한다면 공통 폐쇄에 대해 파악하지 못하고, 재사용 가능한 요소도 알지 못한다. 컴포넌트를 생성할 때 순환 의존이 발생할 가능성이 높기 때문에 컴포넌트 의존성 구조는 시스템의 논리적 설계에 맞춰 성장하고 진화해야 한다.

 

SDP: 안정된 의존성 원칙

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

설계를 유지하다 보면 변경은 불가피하다.

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

안정성

컴포넌트를 변경하기 어렵게 만드는 확실한 방법은 수많은 다른 컴포넌트가 해당 컴포넌트에 의존하게 만드는 것이다. 컴포넌트 안족으로 들어오는 의존성이 많아지면 상당히 안정적으로 볼 수 있다.

x는 안정된 컴포넌트다. 세 컴포넌트가 X에 의존한다.

변경하지 말아야 할 이유가 세 가지나 된다. X는 세 컴포넌트를 책임진다.

X는 어디에도 의존하지 않고 X가 변경되도록 만드는 외적인 영향이 없다. 이 경우 X는 독립적이다.

안정성 지표

  • Fan-in : 안으로 들어오는 의존성, 컴포넌트 내부의 클래스의 의존하는 컴포넌트 외부의 클래스 개수.
  • Fan-out: 바깥으로 나가는 의존성, 컴포넌트 외부의 클래스에 의존하는 컴포넌트 내부의 클래스 개수.
  • I(불안정성) : I = Fan-out / (Fan-in + Fan-out) 이 지표는 [0,1] 범위의 값을 가진다. 0이면 최고로 안정적인 컴포넌트, 1이면 최고로 불안정적인 컴포넌트.
  • I값이 1
    • 최고로 불안정적인 컴포넌트
    • 어떤 컴포넌트도 해당 컴포넌트에 의존하지 않지만, 해당 컴포넌트는 다른 컴포넌트에 의존한다.
    • 이 컴포넌트는 책임성이 없으며 의존적이다.
    • 의존하는 컴포넌트가 없으므로 변경하지 말아야 할 이유가 없다.
  • I 값이 0
    • 해당 컴포넌트에 의존하는 다른 컴포넌트가 있지만, 해당 컴포넌트 자체는 다른 컴포넌트에 의존하지 않는다.
    • 다른 컴포넌트를 책임지며 또 독립적이다.
    • 최고로 안정된 상태다
    • 자신에게 의존하는 컴포넌트가 있으므로 해당 컴포넌트는 변경하기가 어렵다.
    • 하지만 해당 컴포넌트를 변경하도록 강제하는 의존성은 갖지 않는다.

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

추상 컴포넌트

오로지 인터페이스만 포함하는 컴포넌트를 생성하는 방식

실행 가능한 코드가 전혀 없어서 이상하게 보일 수 있다. 꼭 필요한 전략으로 알려져 있으며, 추상 컴포넌트는 상당히 안정적이며, 따라서 덜 안정적인 컴포넌트가 의존할 수 있는 이상적인 대상이다.

동적 타입 언어(루이, 파이썬) 언어를 사용할 때는 추상 컴포넌트가 전혀 존재하지 않음

SAP: 안정된 추상화 원칙

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

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

고수준 정책은 자주 변경하면 안 된다.

고수준 정책을 캡슐화하는 소프트웨어는 반드시 안정된 컴포넌트(I =0)에 위치해야 한다. 이렇게 되면 그 정책을 포함 한 코드는 수정하기 어려워진다. 유연성을 잃는다.

즉, 컴포넌트가 최고로 안정된 상태면서 동시에 충분히 변경 가능할 정도로 유연해야 한다.

해결방법 : 개방 폐쇄 원칙(OCP)에서는 클래스를 수정하지 않고도 확장이 가능하다. 추상 클래스가 이 원칙을 따른다.

안정된 추상화 원칙

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

SAP와 SDP를 결합 합하면 DIP처럼 된다.

SDP에서는 의존성이 반드시 안정성의 방향으로 향해야 한다고 말하며 SAP는 안정성이 결국 추상화를 의미한다고 말하기 때문에 의존성은 추상화의 방향으로 향하게 된다.

컴포넌트는 어떤 부분은 추상적이면서 다른 부분은 안정적일 수 있다.

추상화 정도 측정하기

  • Nc: 컴포넌트의 클래스 개수
  • Na: 컴포넌트의 추상 클래스와 인터페이스의 개수
  • A: 추상화 정도 A=Na/Nc, A는 0~1 사이 값을 가진다. 0이면 추상 클래스가 하나도 없다. 1이면 오로지 추상 클래스만 포함한다는 뜻

주계열

안정성(I)과 추상화 정도(A) 관계를 그래프로 나타낸다.

 

배제 구역

  • 고통의 구역
    • (0,0) 주변구역에 위치한 컴포넌트
    • 매우 안정적이며 구체적이다.
    • 바람직한 상태는 아니다.
    • 뻣뻣한 상태
    • 변경하기 매우 어렵다.
    • 제대로 설계된 컴포넌트는 여기에 존재하면 안 된다.
    • 코드의 변동성이 크면 클수록 고통스럽다.
  • 쓸모없는 구역
    • (1,1) 주변의 컴포넌트
    • 최고로 추상적이지만 쓸모가 없다.
    • 폐기물
    • 전혀 사용되지 않으면서 시스템의 기반 코드에 남아 있음

배제 구역 벗어나기

주계 열과의 거리

  • D^4 : 거리 D=|A+I-1| , 유효 범위 : [0,1] 0 이면 컴포넌트가 주계열 바로 위에 위치, 1이면 주계 열로부터 가장 멀리 위치한다.
  • 주계열 이상한 컴포넌트는 검토해볼 가치가 있다. 이는 자신에게 의존하는 컴포넌트가 거의 없는데도 너무 추상적이거나, 자신에게 의존하는 컴포넌트가 많은데도 너무 구체적인 것이다.

결론

이 장에서 설명한 의존성 관리 지표는 설계의 의존성과 추상화 정도가 내가 ‘훌륭한’ 패턴이라고 생각하는 수준에 부합되는지를 측정한다.

좋은 의존성도 있지만 좋지 않은 의존성도 있다.

하지만, 지표는 신이 아니다. 지표로부터 유용한 것을 찾아 사용하자

반응형

댓글