이론/설계

클린 아키텍쳐

tsyang 2021. 4. 25. 23:26

로버트 C. 마틴의 클린 아키텍쳐를 읽고... 12장부터 '대충' 요약

 

컴포넌트


컴포넌트는 배포할 수 있는 가장 작은 단위를 의미한다. (jar, dll 같은 파일) 컴파일형 언어에서는 바이너리 파일의 결합체, 인터프리터형 언어의 경우 소스파일의 결합체이다. 

 

잘 설계된 컴포넌트라면 반드시 독립적으로 배포가능(=독립적으로 개발가능)해야한다.

 

어떤 클래스를 어느 컴포넌트에 포함시켜야 할까? 세 가지 원칙을 고려할 수 있다.

 

  1. REP (재사용/릴리스 등가 원칙) : 재사용 단위는 릴리스 단위와 같다. 
  2. CCP (공통 폐쇄 원칙) : SRP의 컴포넌트 버전. 서로 다른 이유로 변경되는 클래스는 서로 다른 컴포넌트로 분리해라!
  3. CRP (공통 재사용 원칙) : 컴포넌트 사용자들을 필요없는 것에 의존하도록 강요하지 마라.

REP와 CCP는 컴포넌트를 크게 만드는 요소이고, CCP는 컴포넌트를 작게 만든다. 이 세 가지 원칙의 밸런스를 잘 맞춰야 하는데. 각각의 원칙이 지켜지지 않으면 아래와 같은 단점이 생긴다.

 

  1. CRP가 없다 : 불필요한 릴리스 너무 자주 일어남
  2. REP가 없다 : 재활용이 어렵다.
  3. CCP가 없다 : 컴포넌트 변경이 빈번하다. 

프로젝트 초기에는 CCP가 REP보다 중요하다. 일단 굴러가야 하니... 그러다 나중에는 점차 REP가 중요해진다. 

 

이 외에 컴포넌트를 설계하며 고려해야 할 것은

 

  1. ADP(의존성 비순환 법칙) : 컴포넌트 의존성 그래프에 순환이 있으면 안 된다.
    => 이건 DIP로 끊어주면 됨 (추상클래스, 인터페이스 이용해서) 
  2. SDP(안정된 의존성 원칙) : 더 안정된 쪽에 의존해라. 모듈버전 OCP
  3. SAP (안정된 추상화 원칙) : 컴포넌트는 안정된 만큼만 추상화되어야 한다. 추상화의 정도는 컴포넌트의 모든 클래스 중 추상 클래스나 인터페이스가 몇 개인지로 알 수 있다. 안정성은 컴포넌트로 들어오고 나가는 의존성에 비해서 나가는 의존성이 몇 개인지로 알 수 있다.
    완전히 안정된 컴포넌트라면 (나가는 의존성이 없다.) 그럼 완전 추상적이여야 함. 반대로 완전히 불안정한 컴포넌트라면 (나가는 의존성만 있다.) 구체여야 한다. 컴포넌트가 이 둘을 잇는 선분에 위치하도록 해야 한다.  만약 완전 구체인데 안정된 컴포넌트라면 노답이다. 완전 추상인데 불안정한 컴포넌트라면 아무도 구현 안 한 인터페이스 같은 것이다. (책 134p)

 

엔티티, 유스케이스


(책 200p)

업무규칙 : 대출에 N% 이자 부과하는 이런 규칙.

업무데이터 : 이자율, 대출 금액과 같은 데이터

 

엔티티 (Entity)

업무 데이터를 기반으로 동작하는 업무 규칙 <= 을 구체화하는 객체. 엔티티는 업무 데이터를 직접 포함하거나 매우 쉽게 접근할 수 있다. 엔티티 같은 경우 전사적인 업무 규칙이라고 볼 수 있다. 만약 전사적인게 아니라 단일 애플리케이션이라면? 엔티티는 가장 일반적이며 고수준인 규칙을 캡슐화한 업무 규칙이다. 엔티티는 변경될 가능성이 지극히 낮다. 

 

예 : 증권회사에서 수익률을 계산하는 공식. 증권회사 앱 전체에서 사용할 수 있는 그런 업무규칙들

 

유스케이스(Use Case)

어플리케이션에 특화된 업무 규칙을 구현하는 하나 이상의 함수를 제공하는 객체. 

 

예 : 수익률을 모바일에서는 어떻게 표현하고, 시스템에서는 어떻게 표현할지.

 

비고

엔티티는 유스케이스보다 고수준이다. 업무 규칙은 시스템에서 가장 독립적이며 가장 많이 재사용 할 수 있는 코드여야 한다.

 

 

아키텍쳐 설계


  • 수준(Level) : 계속 저수준 고수준 하는데... 이 수준은 입출력으로부터 거리라고 보면 이해하기가 쉽다. 당연히 입출력으로부터 거리가 멀 수록 정책의 수준이 높아진다. 의존성은 항상 저수준에서 고수준으로 가야한다. 
  • 아키텍처를 짤때 가능한 많은 선택지를 오래 남겨둬야 한다. 구체적으로는 세부사항에 대한 결정은 미루는게 좋다. 여기서 세부사항이란건 입출력 장치, DB, 서버, 프레임워크 등등인데... 이런걸 고려해서 아키텍쳐를 짜면 안 된다는 뜻이다. 어떤 세부사항이든 플러그인 방식으로 자유롭게 이용할 수 있어야 한다. 
  • 세부사항은 아키텍쳐의 가장 밖에 위치해야 한다. (= 매우 저수준의 컴포넌트이다.)

 

중복

중복도 종류가 있는데 정말 중복인경우와, 거짓된 중복인 경우가 있다. 거짓된 중복같은경우에 지금은 중복같아 보이지만 프로젝트가 진행되면서 다르게 바뀌어 나가는 코드들이다. 당연히 전자는 없애야겠지만 후자는 그냥 둬야한다.

 

소리치는 아키텍쳐

아키텍쳐만 딱 봤을때 (이건 헬스케어 시스템이야) , (이건 제어 시스템이야) 이렇게 소리쳐야 함. (이건 스프링이야, 이건 ASP야) 이런식이면 안댐. 애플리케이션의 아키텍처는 유스케이스에 대해 소리쳐야 함.
=> 아키텍쳐만 봤을 때 프로그램의 목표가 뭔지를 알 수 있어야 한다는 그런 의미같음 ..

 

관심사 분리

아키텍쳐에 여러 아이디어가 있는데.. 목표는 다 관심사의 분리임. 소프트웨어를 계층으로 분리해서 관심사의 분리를 달성함.

 

아키텍쳐들의 공통된 특징 (215p)

  • 프레임워크 독립성 : 프리엠워크의 존재여부에 의존하지 않음
  • 테스트 용이성 : 업무규칙은 UI ,DB, 서버 등등이 없어도 테스트 할 수 있어야 함.
  • UI 독립성 : 시스템의 나머지 부분을 변경하지 않고도 UI를 변경할 수 있다. 
  • DB독립성 : DB변경해도 업무규칙은 변경 ㄴㄴ
  • 외부 에이전시에 대한 독립성 : 업무규칙은 외부세계와 인터페이스에 대해 알지 못함.

고수준의 정책부터 나열하자면

 

엔티티) 유스케이스) 컨트롤러, 프리젠터, 게이트웨이 ) 장치, DB, UI, 외부 인터페이스

 

*시나리오) 책 220p 참고

 

험블 객체 패턴

테스트하기 어려운 것과 쉬운 것을 두 개의 모듈 또는 클래스로 나눈다. 이 때 테스트 어려운 애들을 모두 험블 객체로 옮긴다.  예를들어서 GUI 자체는 테스트가 어렵다. 그러나 GUI에서 하는 행동은 테스트가 쉽다. 이렇게 험블 객체 패턴으로 두 부류의 행위를 나누면 프레젠터와 뷰로 나눌 수 있다.

 

뷰는 험블 객체이고 테스트하기 어렵다. 프리젠터는 테스트하기 쉬운애들이며 프리젠터의 역할은 애플리케이션으로부터 데이터를 받아 화면에 표현할 수 있는 포맷으로 만드는 거다. 

 

이런식으로 하면 프리젠터 - 뷰모델 - 뷰 이렇게 나뉜다.  이 때 뷰 모델은 간단한 데이터 구조이다.

 

예를 들어 주식 차트를 보는데 수익은 빨간색, 손해는 파란색이라면 프리젠터가 숫자를 읽어 이를 적절한 포맷으로 뷰 모델에 저장한 뒤에 알맞은 색상도 알아내서 뷰 모델에 넣어준다. 이 외에도 버튼의 활성화 여부 체크박스 등등도 프레젠터가 뷰 모델에 설정해준다. 뷰는 단순히 뷰 모델의 데이터를 화면으로 로드하는 역할이다. 뷰 모델은 humble하다.

 

메인 컴포넌트

메인은 시스템의 초기 진입점이다. 메인 컴포넌트는 궁국적인 세부사항이며 가장 저수준의 정책이다. OS를 제외한 어떤것도 메인에 의존하지 않는다. 메인은 여러 잡다한 일을 한 뒤에 고수준 정책에 제어권을 넘긴다. 그러니까 메인은 지저분할 수 밖에 없다. 

 

메인도 플러그인이라고 볼 수 있다. 개발용, 테스트용, 국가별 메인 플러그인.. 이런식으로 나누면 설정 관련 문제를 쉽게 해결할 수 있다. (메인은 최고 저수준이랬으니까 플러그인처럼 바꿔낄 수 있는것이다..!)

 

테스트 

테스트는 중요하다. 테스트의 의존성은 테스트 대상이 되는 코드를 향한다. 이때 주의해야 하는게 대상이 되는 코드가 변경된다면 애써 만들어놓은 수백개의 테스트가 제대로 동작하지 않을 수 있다. (예를 들어 GUI를 통해 주식 매수/매도가 잘 되는지 테스트를 만들었는데 매수/매도 버튼 위치가 바뀌어버리면? 큰일난다.)

 

그러면 어떡하나. 소프트웨어의 첫 번째 규칙은 언제나 같다. 변동성이 있는 것에 의존하지 마라. GUI는 변동성이 크다 그니까 테스트도 GUI에 의존하지 않고 할 수 있어야 한다. .. 

 

기타

데이터베이스 웹, 프레임워크는 다 세부사항이다. 특히 프레임워크가 그렇다. 프레임워크를 깃허브같은데서 다운받아 쓰려고 보면 소스코드와 매우 강하게 결합하는 방향으로 흘러간다... 이러면 안된다. 프레임워크는 언제든 변할 수 있자나? 그니까 프록시를 만들어서 플러그인처럼 써라. 즉 세부사항으로 여겨라 (저수준 정책 , 플러그인)

 

직접 다른 클래스를 참조하지 않고 문자열로 클래스 이름을 넘기는 등... 이런식으로 하면 참조를 끊을 수 있다. 그리고 실제로 끊었다고 간주하나보다. 예) 팩토리패턴에서 피자이름을 문자열로 넘김..

 

 

 

체크리스트 


앞으로 일을 하면서 책의 모든 내용을 상기할 수 없으니 아래의 체크리스트는 계속 염두에 두고 있으면 좋을 것 같다.

 

  • 동일한 시점에 동일한 이유로 변경되는 것끼리 묶어라.
  • 필요하지 않은 것에 (혹은 필요하지 않은 것을 포함하는 것에) 의존하지 마라. 
  • 변동성이 있는 것에 의존하지 마라.
  • 세부사항은 고려하지 않고 아키텍처를 짜야 한다. (세부사항에 대한 결정은 최대한 미룬다.)
  • DB, 웹, 프레임워크 등등은 다 세부사항이다.
  • 의존성은 항상 저수준에서 고수준으로 향해야 한다. (당연히 사이클도 있으면 안 됨)

써놓고보니 SOLID 원칙이랑 비슷하네