본문 바로가기

SearchDeveloper/클린 아키텍처

[4] 12~14 컴포넌트

컴포넌트의 역사

: 내가 생각했을 땐 분리의 역사

소프트웨어 개발 초창기에는 어떤 메모리 주소에 프로그램을 로드할 건지도 일일히 정해줘야 했었다. 라이브러리 함수도 직접 애플리케이션 코드에 포함해서 단일 프로그램으로 컴파일해야 했다.

그러다가 프로그램 사이즈도 너무 커지고 컴파일에도 오래 걸리니 앺 코드와 라이브러리 코드를 메모리 상에서 분리했다. 메모리 주소 몇 이상은 라이브러리 같은 규칙을 정하고 말이다. 그러면 라이브러리 따로 개별적으로 컴파일할 수 있었지만, 앺 코드가 너무 커지면서 라이브러리 밑에 집어넣는 비효율이 발생했다.

메모리 주소를 고정이 아닌 재배치를 하는게 해결책이었다. 그 기능을 로더가 해준다. 앺과 라이브러리를 로드할 위치를 로더에게 전달해주면 그에 맞게 컴파일러를 수정한다. 로더가 한 가지 기능을 더 한다. 함수를 호출하는 외부참조를 함수 정의하는 프로그램인 외부 정의에 링크시키는 역할이다. 이렇게 링크와 로드를 동시에 하는 링킹 로더가 탄생했다.

프로그램 사이즈가 커지다보니 링킹 로더가 너무 느려져 링크와 로드를 분리했다. 링커로 한 번 실행 파일 만들어두면 로더로 언제든지 빠르게 로드할 수 있게 되었다. 하지만 컴파일-링크 구간이 여전히 병목구간 이었으나 디스크가 빨라지고 메모리가 저렴해지면서 링크 속도가 점점 빨라지기 시작했다.

오늘날에는 속도가 더 빨라져 링크와 로드를 동시에 하고 실행할 수 있다. 그래서 기존 애플리케이션에 라이브러리를 플러그인 형태로 배포하는 컴포넌트 플러그인 아키텍처가 탄생했다.

(컴포넌트의 역사 (내가 생각했을 땐 분리의 역사) 초기에는 라이브러리를 애플리케이션 코드에 넣어 단일 프로그램으로 컴파일함 → 오래 걸려서 라브와 앺 코드를 메모리 상에서 분리 → 앺 코드가 커지면서 메모리 주소를 재배치 해주는 로더의 등장. 이땐 함수 호출과 함수 정의를 매핑해주는 링크도 로더가 같이 했음 → 링크 너무 느려 로더랑 분리 → 디스크와 메모리가 여유로워지면서 앺에 라브를 플러그인 처럼 배포할 수 있는 컴포넌트 플러그인 아키텍처가 탄생 )


컴포넌트 응집도 (컴포넌트를 구성하는 기준)

3가지 원칙(REP, CCP, CRP)에 따라 어떤 클래스를 어떤 컴포넌트에 포함시킬지 결정한다.

REP(Reuse/Release Equivalence Principle, 재사용/릴리즈 등가 원칙)

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

릴리즈 번호가 없으면 재사용 컴포넌트들이 호환되는지 확인하기 어려우므로 꼭 필요하다.

같은 컴포넌트의 클래스와 모듈은 버전 번호가 같아야 한다. 그래야 동일한 릴리즈로 추적이 되고 문서에도 포함될테니깐.

CCP(Common Closure Principle, 공통 폐쇄 원칙)

SRP, OCP 를 기반으로 한 원칙.

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

CRP(Common Reuse Principle, 공통 재사용 원칙)

ISP 를 기반으로 한 원칙

ISP 는 필요하지 않는 기능에 의존하지 않기 위해 인터페이스를 분리하라는 것처럼,

CRP 는 한 컴포넌트에 속한 클래스들은 더 작게 그룹지을 수 없도록 의존하지 않는 애들을 같은 컴포넌트에 넣지 않는다.

세 원칙의 관계

  • REP(재사용/릴리즈 등가 원칙) 이 없으면: 릴리즈 번호가 없어 재사용이 어려움
  • CCP(공통 폐쇄 원칙) 이 없으면: 변경해야할 소스가 한 컴포넌트가 아닌 여러군데 있으므로 컴포넌트의 변경이 너무 빈번함 (변경이 많음)
  • CRP(공통 재사용 원칙) 이 없으면: 불필요한 의존성이 강해 불필요한 릴리즈가 빈번함 (배포가 많음)

일반적으로 개발 초기엔 CCP 에 중점을 두고, 유지보수하면서 REP 쪽으로 넘어간다.


컴포넌트 사이의 3가지 관계

ADP(Acyclic Dependencies Principle, 의존성 비순환 원칙)

어제 돌아가던 코드가 오늘 안 돌아가는 경우, 내가 의존하는 코드를 누군가가 수정해서 안 돌아가는 이유가 많다.

동시에 수정을 해서 안 돌아가는거라, 해결책은 릴리즈 가능한 컴포넌트 단위로 분리한다음 개인 공간에서 개발 할 거 하고 나중에 릴리즈를 적용한다. 다른 컴포넌트의 변경 적용을 내가 하고 싶을 때 할 수 있다는 장점이 있다.

근데 컴포넌트 간 의존성은

비순환 관계여야한다.

왜냐고? 그림처럼 EnitiesAuthorizer 의 관계가 추가돼서 순환 참조가 돼버리면 모든 컴포넌트가 동시에 호환되어야 하기 때문에 하나의 거대한 컴포넌트처럼 모두 정확한 동일한 릴리즈를 사용해야 한다.

순환관계 끊는 법

1.DIP 를 적용해 인터페이스를 참조하게 해서 의존성을 역전시킨다.

2.의존하는 원인인 Permission 컴포넌트를 새로 만든다.

SDP(Stable Dependencies Principle, 안정된 의존성 법칙)

컴포넌트 간 안정되게 설계하라

그렇다면 안정됨이란?

X 는 안정된 컴포넌트이다.

위 3개 컴포넌트를 책임지고 있고(변경이 최소화되어야 할 이유), 어디에도 의존하지 않기 때문에 외부 요인으로 인해 변경될 일은 없기 때문이다.

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

3개 컴포넌트에 의존하고 있기 때문에 외부 요인으로 인해 변경될 여지가 많기 때문이다.

안정성 지표
※ Fan-in: 들어오는 화살표
Fan-out: 나가는 화살표I: Instability: I = Fan-out , (Fan-in + Fan-out).
0이면 최고로 안정하다.

SAP(Stable Abstractions Principle, 안정된 추상화 원칙)

안정성과 추상화의 관계를 정의.

안정된 컴포넌트는 추상 컴포넌트여야 하고, 불안정한 컴포넌트는 구체 컴포넌트여야 한다.

고수준일 수록 모든 의존성의 화살표가 자기한테 들어올거기 때문에 변경은 적게 일어나야 하니깐.

구체 컴포넌트는 변경될 일이 많기 때문에 책임질 컴포넌트가 많이 없는 불안정 컴포넌트여야한다.

추상화 지표
※ Na: 추상 클래스, 인터페이스 수, Nc: 클래스 수
A: Abstractness. A = Na ÷ Nc

 

 

 

레퍼런스

 

클린 아키텍처