좋은 아키텍처와 "컴포넌트"

JACKJACK·2023년 8월 3일
1
post-thumbnail

좋은 아키텍처란? & 컴포넌트란?

좋은 아키텍처는 변경에 유연하고, 이해하기 쉽고, 많은 소프트웨어에서 사용될 수 잇는 구조를 갖춰야한다.

컴포넌트는 시스템 구성요소로 "배포할 수 있는 가장 작은 단위"이다. 따라서 좋은 컴포넌트들로 시스템을 구성해야 좋은 아키텍처라고 할 수 있다.

컴포넌트>모듈>클래스 역순으로 큰 개념이며, JAVA에서 보면 JAR파일이 컴포넌트이며 이를 묶어 WAR파일과 같은 단일 아카이브로 만드는것이 가능하다. 이 때 중요한 것은 컴포넌트는 반드시 독립적으로 배포 가능한, 개발 가능한 능력을 갖춰야 한다는 것이다.

컴포넌트의 역사

초창기에는 메모리에서 프로그램의 위치와 범위까지 레이아웃을 개발자가 직접 제어하고 라이브러리를 써도 소스코드 형태로 어플리케이션에 포함시켰다.

이후 하나를 변경해도 전체를 컴파일 해야하는 일이 발생해 어플리케이션과 라이브러리 코드를 분리하고, 메모리 공간을 효율적으로 사용하기 위해 링킹로더로 메모리에 프로그램을 재배치시켰다. 하지만 프로그램의 크기는 점점 커지고 해결 방법이 없었다.

이 때 무어의 법칙이 나오면서 프로그램의 성장 속도보다 하드웨어의 성장속도가 빨라짐으로 인해 프로그램의 엄청난 발전이 이루어지게 되었다.(ActiveX와 공유 라이러리, .jar파일이 그 예시) 이렇게 컴포넌트 플러그인 아키텍처가 탄생하게 된다.



그렇다면 어떤 클래스를 컴포넌트에 포함시켜야할까? - 컴포넌트 응집도

REP, CCP, CRP를 통해 컴포넌트의 응집도를 높이자. (재사용/등가 원칙, 공통 폐쇄 원칙, 공통 재사용 원칙)

솔직히 이름만 들으면 감이 안오니 각각의 목적을 살펴보며 이해하면 좋다.
REP - 재사용성을 위함, CCP - 유지보수를위함, CRP - 불필요한 릴리스를 피하기 위함

- REP 재사용/등가 원칙[PPTS]

REP를 쉽게 정의하면 재사용 단위는 릴리스 단위와 같게 해야 한다는 원칙이다.

  • 어떠한 컴포넌트나 라이브러리를 재사용하려 할 때 릴리즈 번호가 없다면, 각 컴포넌트들이 서로 호환되는 것인지 보증할 방법이 없다.
  • 따라서 릴리스 번호에 따라 컴포넌트를 사용할 것인지, 새로운 버전으로 통합할 것인지를 결정할 수 있다는 얘기이다.
  • 이 때 추가로 얘기하자면 아키텍처 관점에서 컴포넌트를 구성하는 모든 모듈은 서로 공유하는 중요한 테마나 목적을 띄어야 하며, 동일한 릴리스로 추적 관리가 되게끔 응집성이 높은 클래스와 모듈들로 구성되어 있어야 한다고 한다.

- CCP 공통 폐쇄 원칙

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

이 원칙은 SRP를 컴포넌트 관점에서 쓴 것이다. SRP에서는 서로 다른 이유로 변경되는 메서드는 서로 다른 클래스로 분리하라고 하듯이, CCP에서도 변경을 단일 컴포넌트로 제한한다면 다른 컴포넌트는 다시 검증하거나 배포할 필요가 없기 때문에 응집도가 높아지기 때문에 CCP를 준수해야 한다.

- CRP 공통 재사용 원칙

CRP는 함께 재사용되는 경향이 있는 클래스와 모듈들은 같은 컴포넌트에 묶어야 한다는 원칙인데, 클린 아키텍처 책에서는 컴포넌트를 사용자들이 필요로 하지 않는 것에 의존하게 강요하지 않아야하는것을 강조한다.

위 그림에서 C1,C2 각각의 컴포넌트에서 공통컴포넌트의 모듈 M1,M2 각각을 사용할 때 M2의 변동사항이 생기면 M1도 함께 배포해줘야하는 상황이 생긴다. CRP는 ISP의 포괄적인 버전이다. ISP에서는 사용하지 않은 메서드가 있는 클래스에 의존하지 말라고 조언하고, CRP는 사용하지 않는 클래스를 가진 컴포넌트에 의존하지 말라고 조언한다.

즉 CRP의 요점은 "필요하지 않은 것에 의존하지 마라."이다. 꼭 공통으로 쓰이는 필요한 내용만 공통 컴포넌트로 만들도록하자!

- 좋은 컴포넌트를 구성하기 위한 응집도 다이어그램

다음은 컴포넌트 응집도에 대한 균형 다이어그램이다. 응집도에 대해 앞서말한 세가지 원칙이 어떻게 상호작용 하는지 보여준다. 각 변의 반대쪽 꼭지점은 원칙을 포기했을 때 감수해야하는 비용을 뜻하고, 뛰어난 아키텍트라면 어느 쪽으로 관심을 기울여야하는지 파악해야 한다고 합니다. 일반적으로는 프로젝트가 진행되고 확장될 수록 다이어그램의 오른쪽에서 왼쪽으로 중요도가 변한다고 한다.



그렇다면 컴포넌트 사이의 관계는 어떻게 해야할까?

컴포넌트 사이의 관계를 알아보기 전에 개발자들이 컴포넌트를 어떤식으로 개발/수정하는지 알아야 한다. 보통 개발을 진행할 때 다음과 같은 프로세스를 거치게 된다.

  1. 개발자들이 동일한 소스 파일을 수정
  2. 서로 마지막으로 수정한 코드들을 통합시키고 망가진 부분이 동작하도록 수정
  3. 프로젝트 규모는 증가

이 때 소프트웨어가 망가지지 않게 하기 위해서 두가지 방법이 있는데.

  • 주단위 빌드: 중간/소규모의 프로젝트에서 많이 사용하며 첫 4일간은 서로(통합)를 신경쓰지않고 개발을 하고, 금요일에 변경된 코드를 통합하여 시스템을 만든다.
    물론 규모가 커질수록 개발시간 대신 통합시간을 늘어나서, 효율성이 나빠지게 되고 프로젝트가 감수할 위험은 늘어나게 된다.

  • 의존성 비순환 원칙(ADP): 순환의존성을 제거하는 방법이 있는데, 개발 환경을 릴리스 가능한 컴포넌트 단위로 분리하는 것이다. 그리하면 컴포넌트에 릴리스 번호를 부여하고, 다른 팀 개발자는 릴리스 번호를 보고 해당 내용을 적용할지 말지 결정하며, 다른팀에 의해 좌지우지되지 않는다.

ADP를 좀더 깊게 살펴보자

왼쪽은 그림은 어느 컴포넌트에서 시작하더라도 의존성 관계를 따라가면 main으로 도달할수 없다는 것으로 보아 비순환 방향 그래프이고, 오른쪽은 의존에 순환이 생겨버린다. 이 때 Presenters 를 담당하는 팀이 새로운 릴리스를 만들면 Interector와 Entites를 이용해 테스트를 해봐야 하며, Presenter에 의존하는 View, Main은 자신의 작업물을 언제 통합할지 반드시 결정해야 한다.

만약 순환구조를 띄는 상황에서 Interector를 변경하는 상황이 오면 Interactors->Entities->Authorizer->Inetrector 의존이 반복되어 배포도 단위테스트도 모두 어려워진다. 그렇기 때문에 ADP를 통해 순환을 끊어줘야한다. 이 때 DIP로 순환을 끊어준다.

  • 의존성 역전은 다음과 같이 순환이 발생하는 곳에 인터페이스를 추가해 의존성을 역전시킨다.

컴포넌트 다이어그램의 하향식, 비순환 설계

앞서 말한듯이 개발을 하면 좋지만 초기부터 컴포넌트 다이어그램을 설계할 수는 없으며 모듈이 쌓이면서 컴포넌트를 진화시켜 나가야한다. 이 때 의존성을 줄이기 위해 REP, CCP에 주목하게 될것이고, 컴포넌트를 조합하는 과정에서 공통 재사용 원칙(CRP)를 사용하다 보면 컴포넌트의 의존성 그래프가 흐트러지고 커지게 된다.



컴포넌트를 안정적으로 결합하면서 설계하려면? SDP (안정성 의존 원칙)사용

설계를 유지하면 변경은 불가피하고 변동성을 지닌 컴포넌트는 언제나 존재하기 마련인데 이를 SDP를 통해 안정적으로 설계해야한다. 여기서 SDP란 안정성의 방향으로 의존하라는 원칙이다.

안정성의 사전적인 정의는 "쉽게 움직이지 않는"인데 이를 코드로 바꾸면 변경하기 려운이다. 만약 변동성이 없어야하는 컴포넌트가 변동성이 큰 컴포넌트를 의존한다면 이는 아래 그림과 같이 SDP를 위반한 것이다.

SDP를 준수해 컴포넌트들을 안정적인 방향으로 의존시켜 변경의 요인을 줄이고 안정성을 늘릴 수 있다. 아래 그림과 같이 X컴포넌트는 여러 다른 컴포넌트에서 의존하기 때문에 변경할 수 없는 요인이 많으며 독립적이고 안정적이다. 하지만 Y컴포넌트는 여러 컴포넌트를 의존해 변경이 발생할 수 있는 요인이 세가지이며 의존적이며 불안정하게 된다.

그렇다면 안정성을 측정할 수 있을까? O

대답은 YES이고 I(불안정성) ,Fan-in(외부에서 사용), Fain-out(외부 클래스에 의존)이라고 한다면 다음과 같이 계산이 가능하다.
I = Fan-out / (Fan-in+Fain-out)
I가 1이면 어떠한 컴포넌트도 해당 컴포넌트에 의존하지 않지만 의존적이며, 0이면 다른 컴포넌트들이 의존하고 있어 변경하기는 어렵지만 독립적이고 안정적이게 된다. 위 그림을 예시로 들면 X컴포넌트는 0으로 안정적이고 Y는 1로 불안정하게된다.

모든 컴포넌트가 I(불안정성)이 0이면 좋을까? X

대답은 No이고 모든 컴포넌트의 I가 0이면 시스템 변경이 불가능한 상황이 온다. 따라서 SDP에 맞게 컴포넌트(A)가 의존할 때는 의존하는 다른 컴포넌트들(B,C)보다 컴포넌트(A)의 I가 커야 한다고 말한다.(안정적인 방향으로 의존하라)

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

아키텍처를 설계할 때 컴포넌트는 SAP 원칙을 적용해서 어느정도 범위까지 추상화화를 시켜야하는지 정책을 정해야 한다고 한다.

쉽게 풀어 얘기하면 I가 만약 0인 컴포넌트가 많을 수록 소스코드의 수정이 불가해지므로, 이를 시스템에 어디까지 적용시킬 것인지 정의해야 하고 그 원칙이 SAP이다.

SAP에서는 안정된 컴포넌트는 추상 컴포넌트어야하고, 불안정한 컴포넌트는 구체 컴포넌트이며 코드를 쉽게 변경할 수 있게 해야한다고 한다.


불안정성(I)와 추상화정도(A)의 관계

I와 A는 긴밀한 관계를 띄고 둘을 합치면 곧 DIP와 비슷해진다고 한다. 이 때 I와 A의 관계도를 알아보자.

그래프를 보면 둘다 0과 1사이의 값을 지니며 모든 컴포넌트가 (0,1),(1,0)에 위치하지는 않는다. 대체로 컴포넌트는 추상화와 안정화의 정도가 다양하며, 그 예로 추상클래스에서 파생한 추상클래스는 추상적이지만 안정적이지는 않기 때문에 추상클래스라고 무조건 안정적이지는 않을 수 있다. 따라서 오른쪽 그래프와 같이 합리적인 지점을 정의하는 점의 궤적을 정의할 수 있고(Z는 표준편차), 고통의 구역과 쓸모없는 구역을 피해서 컴포넌트를 개발해야한다.


바람직한 컴포넌트를 나타내는 지표

D(바람직한 컴포넌트의 지표이자 주계열과의 거리)

D = |A + I - 1|

결국은 그래프에서 추계열에 얼마나 가까운지가 중요하며 이것을 지표로 나타내 컴포넌트가 얼마나 안정되고 결합이 잘 되어있는지를 확인할 수 있다. 그리고 D지표를 다음과 같이 시계열에 따라 나타낼 수 있는데, 관리한계를 정해서 어느 값 이상의 지표가 나타나면 컴포넌트를 재정비 하는 방법을 사용할 수 있다.



결론

- 컴포넌트 원칙을 지키며 프로그램을 설계하면 변경에 유연하고, 이해하기 쉽고, 많은 소프트웨어에서 사용될 수 잇는 구조를 갖출 수 있다.

- 컴포넌트는 소프트웨어가 확장되며 변해가는데 이 때 지표를 통해 의존성, 추상화 정도를 관리해 좋은 컴포넌트를 만들고 유지시키자.

profile
러닝커브를 빠르게 높이자🎢

0개의 댓글