본문 바로가기

기타/클린 아키텍처

[클린 아키텍처] 컴포넌트 원칙

컴포넌트는 배포 단위다. 컴포넌트가 어떤 형태로 배포되든, 잘 설계된 컴포넌트라면 반드시 독립적으로 배포 가능한, 독립적으로 개발 가능한 능력을 갖춰야 한다.

컴포넌트의 간략한 역사

소프트웨어 개발 초창기에는 프로그램의 위치가 한번 결정되면 재배포가 불가능하였고, 메모리에서의 프로그램 위치와 레이아웃을 프로그래머가 직접 제어하였다.

이시대에는 프로그래머가 라이브러리 함수의 소스 코드를 애플리케이션 코드에 직접 포함시켜 단일 프로그램으로 컴파일했다.그러나 장치는 느리고 메모리는 너무 비쌌다. 메모리가 너무 작아서 소스 코드 전체를 메모리에 상주시킬 수가 없었기에, 컴파일러는 느린 장치를 이용해서 소스코드를 여러 차례 읽어야만 했다.

컴파일 시간을 단축시키기 위해 프로그래머는 함수 라이브러리의 소스 코드를 애플리케이션 코드로부터 분리했다. 함수 라이브러리를 개별적으로 컴파일하고, 컴파일된 바이너리를 메모리의 특정 위치에 로드했다. 함수 라이브러리에 대한 심벌 테이블을 생성한 후, 이를 이용해 애플리케이션 코드를 컴파일했다. 그리고 애플리케이션을 실행해야 한다면 바이너리 함수 라이브러리를 로드한 다음 애플리케이션을 로드했다.

애플리케이션이 메모리에서 0000과 1777 사이에 주소공간에 들어갈 수 없다면 애플리케이션을 두 개의 주소 세그먼트로 분리하여 배치하였다. 이러한 상황은 지속가능하지 않았다.

재배치성

해결책은 재배치가 가능한 바이너리였다.

지능적인 로더를 사용해서 메모리에 재배치할 수 있는 형태의 바이너리를 생성하도록 컴파일러를 수정하자는 것이었다. 이때 로더는 재배치 코드가 자리할 위치 정보를 전달받았다. 그리고 재배치 코드에는 로드한 데이터에서 어느 부분을 수정해야 정해진 주소에 로드할 수 있는지 알려주는 플래그가 삽입되었다.

링커

링킹 로더는 커지는 프로그램을 감당할 수 없어 로더에서부터 링커를 분리하였다. 링커라는 별도의 애플리플케으로 이 작업을 처리하도록 만들었다.

1980년대 소스 모듈은 .c 파일에서 .o 파일로 컴파일된 후, 링커로 전달되어 빠르게 로드될 수 있는 형태의 실행파일로 만들어졌다. 각 모듈을 컴파일하는 과정은 상대적으로 빨랐지만, 전체 모듈을 컴파일하는 일은 꽤 시간이 걸렸다.

머피의 법칙. 컴파일하고 링크하는 데 사용 가능한 시간을 모두 소모할 때까지 프로그램은 커진다.

무어의 법칙. 반도체 집적회로의 성능이 24개월마다 2배로 증가한다.

1990년대 후반. 프로그래머가 프로그램을 성장시키는 속도보다 링크 시간이 줄어드는 속도가 더 빨라지기 시작했다. 마침내 무어의 법칙이 이겼다.

액티브 X, 공유라이브러리, .jar 파일이 등장하기 시작했다 . 컴퓨터와 장치가 빨라져서 또다시 로드와 링크를 동시에 할 수 있게 되었다. 이렇게 컴포넌트 플러그인 아키텍처가 탄생했다. 런타임에 플러그인 형태로 결합할 수 있는 동적 링크 파일이 이 책에서 말하는 소프트웨어 컴포넌트에 해당된다.

 

 

컴포넌트 응집도와 관련된 세 가지 원칙


  • REP: 재사용/릴리스 등가 원칙
  • CCP: 공통 폐쇄 원칙
  • CRP: 공통 재사용 원칙

 

REP: 재사용/릴리스 등가 원칙

재사용 단위는 릴리스 단위와 같다.

 

단일 컴포넌트는 응집성 높은 클래스와 모듈들로 구성되어야 함을 뜻한다. 하나의 컴포넌트로 묶인 클래스와 모듈은 반드시 함께 릴리스할 수 있어야 한다.

 

CCP: 공통 폐쇄 원칙

동일한 이유로 동일한 시점에 변경되는 클래스를 같은 컴포넌트로 묶어라. 서로 다른 시점에 다른 이유로 변경되는 클래스는 다른 컴포넌트로 분리하라.

 

이 원칙은 컴포넌트 관점의 단일 책임 원칙(SRP)이다. 단일 컴포넌트는 변경의 이유가 여러 개 있어서는 안 된다.

대다수의 애플리케이션에서 유지보수성은 재사용성보다 훨씬 중요하다. 애플리케이션에서 코드가 반드시 변경되어야 한다면, 이러한 변경이 여러 컴포넌트 도처에 분산되어 발생하기보다는, 변경 모두가 단일 컴포넌트에서 발생하는 편이 낫다.

변경에 대해 닫혀 있는 클래스들을 하나의 컴포넌트로 묶음으로써 개방 폐쇄 원칙(OCP)에서 얻은 교훈을 확대 적용한다. 따라서 변경이 필요한 요구사항이 발생했을 때, 그 변경이 영향을 주는 컴포넌트들이 최소한으로 한정될 가능성이 높아진다.

 

CRP: 공통 재사용 원칙

컴포넌트 사용자들은 필요하지 않은 것에 의존하게 강요하지 말라.

 

클래스와 모듈을 어느 컴포넌트에 위치시킬지 결정할 때 도움되는 원칙이다. 같이 재사용되는 경향이 있는 클래스의 모듈들은 같은 컴포넌트에 포함해야 한다고 말한다.

동일한 컴포넌트로 묶어서는 안되는 클래스가 무엇인지도 말해준다. 어떤 컴포넌트가 다른 컴포넌트를 사용하면, 두 컴포넌트 사이에는 의존성이 생겨난다. 사용하는 컴포넌트가 사용되는 컴포넌트에서 단 하나의 클래스만 사용할 수도 있다. 그렇다고 해서 의존성은 조금도 약해지지 않는다. 사용하는 컴포넌트는 사용되는 컴포넌트에 여전히 의존한다.

인터페이스 분리 원칙(ISP)의 포괄적인 버전이다. ISP는 사용하지 않는 메서드가 있는 클래스에 의존하지 말라고 조언한다. CRP는 사용하지 않는 클래스를 가진 컴포넌트에 의존하지 말라고 조언한다.

필요하지 않은 것에 의존하지 말라.

 

컴포넌트 응집도에 대한 균형 다이어그램

다음 그림은 균형 다이어그램으로, 응집도에 관한 세 원칙이 서로 어떻게 상호작용하는지 보여준다. 다이어그램의 각 변은 반대쪽 꼭지점에 있는 원칙을 포기했을 때 감수해야 할 비용을 나타낸다.

프로젝트가 발전되고 사용되는 방법에 따라 어느 원칙에 집중해야 하는지 달라진다.

프로젝트 초기에는 CCP가 REP보다 훨씬 더 중요하다. 개발 가능성이 재사용성보다 중요하기 때문이다.

일반적으로 프로젝트는 삼각형의 오른쪽에서 시작하는 편이며, 이때는 오직 재사용성만 희생하면 된다. 프로젝트가 성숙하고, 그 프로젝트로부터 파생된 또 다른 프로젝트가 시작되면, 프로젝트는 삼각형에서 점차 왼쪽으로 이동해 간다. 즉, 프로젝트의 컴포넌트 구조는 시간과 성숙도에 따라 변한다는 뜻이다.

 

 

컴포넌트 사이의 관계를 설명하는 원칙


  • ADP: 의존성 비순환 원칙
  • SDP: 안정된 의존성 원칙
  • SAP: 안정된 추상화 원칙

 

ADP: 의존성 비순환 원칙

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

 

하루종일 일해서 무언가를 작동하게 만들어 놓고 퇴근했는데, 이튿날 출근해 보면 전혀 돌아가지 않는 경험을 해본 적이 있지 않은가? 누군가가 당신보다 더 늦게까지 일하면서 당신이 의존하고 있던 무언가를 수정했기 때문이다.

숙취 증후군의 해결책으로 두 가지 방법이 있다.

  1. 주 단위 빌드
    • 4일동안 개발하고 1일동안 통합하고 빌드하기
  2. 의존성 비순환 원칙
    • 개발 환경을 릴리스 가능한 컴포넌트 단위로 분리하기
    • 이 절차가 성공적으로 동작하려면 컴포넌트 사이의 의존성 구조를 반드시 관리해야 한다. 의존성 구조에 순환이 있어서는 안 된다.
    • 시스템 전체를 릴리스해야 할 때가 오면 릴리스 절차는 상향식으로 진행된다.

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

순환 끊기

Entities에 포함된 클래스 하나가 Authorizer에 포함된 클래스 하나를 사용하도록 변경해야 한다고 가정하자. 이 때 순환 의존성이 발생한다.

  1. 의존성 역전 원칙을 적용한다.
  2. Entities와 Authorizer가 모두 의존하는 새로운 컴포넌트를 만든다. 그리고 두 컴포넌트가 모두 의존하는 클래스들을 새로운 컴포넌트로 이동시킨다.

 

SDP: 안정된 의존성 원칙

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

 

X는 안정된 컴포넌트, Y는 불안정한 컴포넌트이다.

 

안정성 지표

I = Fan-out / (Fan-in + Fan-out)

Fan-in: 안으로 들어오는 의존성
Fan-out: 바깥으로 나가는 의존성

0일 때 안정된 컴포넌트, 1일 때 불안정한 컴포넌트

 

SDP의 위배 → DIP로 해결

 

 

SAP: 안정된 추상화 원칙

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

 

컴포넌트가 안정된 상태이면서 변경에 충분히 대응할 수 있도록 하려면? 답은 개방 폐쇄 원칙(OCP)

어떤 클래스가 이 원칙을 준수하는가? 답은 추상(abstract) 클래스

 

의존성은 추상화의 방향으로 향하게 된다.

 

추상화 정도 측정하기

A=Na/Nc

Nc: 컴포넌트의 클래스 개수
Na: 컴포넌트의 추상 클래스와 인터페이스 개수
A: 추상화 정도

0일 때 추상클래스가 없다. 1일 때 추상클래스만 있다.

  • 주계열
  • 고통의 구역: 안정적이며 구체적이다.
  • 쓸모 없는 구역: 안정적이지 않고 추상적이다.

D = |A+I-1|

D: 거리

 

D가 0이면 컴포넌트가 주계열 바로 위치, D가 1이면 주계열러부터 가장 멀리 위치한다. 주계열에 위치할수록 평균과 분산은 0에 가까워진다.

각 컴포넌트의 D값을 시간에 따라 그려볼 수 있다.

'기타 > 클린 아키텍처' 카테고리의 다른 글

[클린 아키텍처] 정책과 수준  (0) 2022.08.20