티스토리 뷰

나의 공부방

[개발서적] 클린 아키텍처 6부 세부사항 - 내용 정리 및 요약

망나니개발자 2022. 11. 4. 11:00
반응형

이번에는 로버트 C 마틴의 클린 아키텍처를 읽은 내용을 정리해보도록 하겠습니다. 개인적인 설명은 기울임으로 표시해두었으니, 읽으면서 참고하시면 될 것 같습니다.

 

 

 

 

 

30장. 데이터베이스는 세부사항이다.


[ 서론 ]

  • 아키텍처 관점에서 데이터베이스는 엔티티가 아닌 세부사항이라서 아키텍처의 구성 요소 수준으로 끌어올릴 수 없음
  • 애플리케이션 내부 데이터의 구조는 시스템 아키텍처에서 대단히 중요하지만 데이터베이스는 데이터 모델이 아님
  • 데이터베이스는 소프트웨어이자 유틸리티인 저수준의 세부사항(메커니즘)일 뿐이라서 아키텍트와 관련이 없음
  • 뛰어난 아키텍트라면 저수준의 메커니즘이 시스템 아키텍처를 오염시키는 일을 용납하지 않음

 

[ 관계형 데이터베이스 ]

  • 관계형 데이터베이스는 데이터를 저장하고 접근하는데 탁월한 기술이지만 결국 그저 기술일 뿐이며, 세부사항임을 뜻함
  • 데이터를 테이블에 행 단위로 배치한다는 자체는 아키텍처적으로 전혀 중요하지 않음
  • 유스케이스는 이러한 방식을 알아서는 안되며 관여해서도 안됨
  • 데이터가 테이블 구조를 가진다는 사실은 오직 아키텍처의 외부 원에 위치한 최하위 수준의 유틸리티 함수만 알아야 함

예를 들어 JPA를 사용하는데, Fetch Type이 Lazy인 다른 연관된 엔티티가 필요해지는 상황이라고 하자. 만약 서비스 계층에서 forEach로 엔티티를 지연 로딩하는 코드를 작성한다면 이것은 저수준의 세부사항이 시스템 아키텍처를 오염시키는 행위가 된다.

 

 

[ 하지만 성능은? ]

  • 성능은 당연히 아키텍처적인 관심사이지만, 데이터 저장소의 측면에서는 완전히 캡슐화하여 업무 규칙과 분리할 수 있는 관심사임
  • 데이터 저장소에서 데이터를 빠르게 넣고 뺄 수 있어야 하는 것은 맞지만, 이는 저수준의 관심사임
  • 이는 저수준의 데이터 접근 메커니즘 단에서 다룰 수 있고, 성능은 전반적인 아키텍처와는 아무런 관련이 없음

 

[ 결론 ]

  • 체계화된 데이터 구조와 데이터 모델은 아키텍처적으로 중요함
  • 하지만 데이터를 회전식 자기 디스크 표면에서 이리저리 옮길 뿐인 기술과 시스템(데이터베이스)은 아키텍처적으로 중요하지 않음
  • 데이터를 테이블 구조로 만들고, SQL로만 접근하도록 하는 관계형 데이터베이스 시스템은 전자보다는 후자에 훨씬 가까움
  • 데이터는 중요하지만 데이터베이스는 세부사항임

 

 

 

31장. 웹은 세부사항이다.


[ 끝없이 반복하는 추 ]

  • IT 역사 전체로 시야를 넗히면 웹은 아무것도 바꾸지 않았음
  • 웹은 우리가 발버둥치면서 생기는 수 많은 진동(기술적인 변화) 중 하나에 불과함
  • 이러한 진동은 우리가 태어나기 전에도 있어 왔고, 우리가 은퇴한 뒤에도 지속될 것임
  • 아키텍트로서 이 진동은 그저 비즈니스 로직의 중심에서 밀어내고 싶은 단기적인 문제일 뿐임

지금이야 웹 기반의 API로 입력을 받고 출력을 보내는 것이 당연하지만, 입출력 장치는 계속해서 변해왔다. 현재 우리에게 익숙한 RESTful API와 같은 것들도 언제 변할지 모르는 세부사항일 뿐이다. 그러므로 핵심 비즈니스 로직에 결합되지 않도록 하자.

 

 

[ 요약 ]

  • 웹은 입출력장치이자 GUI이며 GUI는 세부사항이므로, 이를 핵심 업무 로직에서 분리된 경계 바깥에 두어야 함
  • UI와 애플리케이션 사이에는 추상화가 가능한 또 다른 경계가 존재함
  • 비즈니스 로직은 다수의 유스케이스로 구성되며, 각 유스케이스는 사용자를 대신해서 일부 함수를 수행하는 것으로 볼 수 있음
  • 각 유스케이스는 입력 데이터, 수행할 처리 과정, 출력 데이터를 기반으로 기술할 수 있음

 

[ 결론 ]

  • 추상화는 만들기 쉽지 않고, 제대로 만들려면 수차례의 반복 과정을 거쳐야 하지만 가능함
  • 그리고 세상은 마케팅 귀재로 가득하기 때문에 이러한 추상화가 꼭 필요할 때가 많다고 주장하기는 어렵지 않음

여기서 "마케팅 귀재"란 마케팅을 위해 신규 기술의 도입을 주장하는 사람들이다. 실제로 어떤 신규 기술을 적용해서 마케팅을 하는 경우가 적지 않다. 만약 우리가 세부사항을 잘 분리해뒀다면, 이러한 신규 기술을 필요한 도입하는 것은 어렵지 않을 것이다.

 

 

 

32장. 프레임워크는 세부사항이다.


[ 혼인 관계의 비대칭성 ]

  • 우리는 프레임워크를 위해 대단히 큰 헌신을 해야 하지만, 프레임워크 제작자는 우리를 위해 아무런 헌신도 하지 않음
    • 프레임워크를 사용할 경우 우리는 프레임워크 제작자가 제공하는 문서를 꼼꼼히 읽음
    • 이 문서에서는 우리가 만들 소프트웨어와 프레임워크를 어떻게 통합할 수 있을지 조언함
    • 대개의 경우 이들은 프레임워크를 중심에 두고, 우리의 아키텍처는 그 바깥을 감싸야 한다고 말함
  • 프레임워크 제작자는 당신의 애플리케이션이 가능하면 프레임워크에 공고하게 결합될 것을 강하게 역설함
  • 프레임워크 제작자 입장에서는 프레임워크에 대해 절대적인 제어권을 쥐고 있으므로 오히려 결합되기를 바람
  • 한번 결합하면 그 관계를 깨기가 매우 어렵기 때문이며, 이러한 관계는 일방적임

스프링은 강한 결합을 요구하는 EJB에 반하여 나온 프레임워크다. 그래서 POJO 중심의 개발을 지향하고, 나름대로 프레임워크에 독립적일 수 있는 방향으로 발전을 한다고 생각한다. 대표적으로 AOP를 위한 과정은 스프링이 어떤 노력을 해왔는지 보여주는 결과물이 아닌가 싶다. 한 프레임워크만 알고 있으니 견해가 조금 치우칠 수 있는 것 같다는 생각이 자주 드는데, 나중에 다른 프레임워크도 살펴봐야겠다.

 

 

[ 해결책 ]

  • 프레임워크를 사용할 수 있지만 프레임워크와 결합해서는 안되고, 적당히 거리를 둬야 함
  • 프레임워크가 자신의 클래스로부터 파생을 요구한다면 프록시를 만들고, 업무 규칙에 플러그인할 수 있는 컴포넌트에 위치시켜야 함
  • 프레임워크를 아키텍처의 바깥쪽 원에 속하는 세부사항으로 취급하고 아키텍처의 안쪽 원으로 들어오지 못하게 해야 함
  • 핵심 코드에 플러그인 할 수 있는 컴포넌트에 프레임워크를 통합하고, 의존성 규칙을 준수해야 함
  • 스프링은 훌륭한 의존성 주입 프레임워크이지만 ,업무 객체는 @Autowired 등을 포함해 절대로 스프링에 대해 알아서는 안됨

"만들면서 배우는 클린 아키텍처"에서 @WebAdapter@PersistenceAdapter를 만들어 사용하는 것을 보고 처음에는 불필요한 정보로 인지 부담을 가중시킨다는 생각이 들었다. 그런데 이 책을 읽다보니 프레임워크로부터 독립적인 코드를 만들기 위한 노력의 일환이였다고 생각이 든다. @PrersistenceAdapter는 프레임워크로부터 독립적으로 만드는데 성공했지만 @WebAdapter는 여전히 스프링 프레임워크가 침투하는데, 그렇다면 @WebAdapter는 굳이 만들 필요가 없을 것 같다는 생각이다.

그리고 추가적으로 이 책을 읽으면서 @Autowired의 사용을 지양해야 하는 이유를 하나 더 늘릴 수 있다.

 

 

 

 

33장. 사례 연구: 비디오 판매


[ 제품 ]

  • 예시로 살펴볼 제품은 웹사이트에 비디오를 판매하는 소프트웨어로, 비디오들을 개인과 기업에게 웹으로 판매함
    • 개인은 단품 가격을 지불해 스트리밍으로 보거나, 더 높은 가격을 내고 비디오를 다운로드해서 영구소장할 수 있음
    • 기업용 라이센스는 스트리밍 전용이며, 대량 구매를 하면 할인을 받을 수 있음
    • 일반적으로 개인은 시청자인 동시에 구매자이지만, 기업은 비디오를 구매하는 사람이 따로 있음
    • 비디오 제작자는 비디오 파일과 비디오 해법에 대한 설명서, 부속 파일(시험, 문제, 해법, 소스 코드 등)을 제공해야 함
    • 관리자는 신규비디오 시리즈물을 추가하거나 기존 시리즈물에 비디오를 추가/ 삭제하며, 다양한 라이센스에 맞춰 가격을 책정함
  • 시스템의 초기 아키텍처를 결정하는 첫 단계는 액터와 유스케이스를 식별하는 일임

이번 챕터에서는 실제 사례를 기반으로 아키텍처를 결정하는 흐름을 보여주므로, 쭉 따라가보면 좋다.

 

 

 

[ 유스케이스 분석 ]

 

 

  • 4개의 주요 액터는 시스템이 변경되어야 할 네 가지 주요 근원임
    • 신규 기능을 추가하거나 기존 기능을 변경해야 한다면, 그 이유는 반드시 이들 액터 중 하나에게 기능을 제공하기 위함임
    • 따라서 시스템을 분할하여, 특정 액터를 위한 변경이 나머지 액터에게는 전혀 영향을 미치지 않게 만들어야 함
  • 중앙의 점선으로 된 유스케이스는 추상 유스케이스임
    • 추상 유스케이스는 범용적인 정책을 담고 있으며 다른 유스케이스에서 이를 더 구체화함
    • 시청자 입장의 카탈로그 조회하기와 구매자 입장의 카탈로그 조회하기는 모두 추상 유스케이스를 상속받음
    • 는 꼭 생성해야만 했던 것은 아니며, 이를 다이어그램에서 없애더라도 전체 제품의 기능을 조금도 손상시키지 않음
    • 그러나 이들은 너무 비슷하기 때문에, 유사성을 식별해서 분석 초기에 통합하는 방법을 찾는 편이 더 현명하다고 판단했음

 

 

[ 컴포넌트 아키텍처 ]

 

  • 액터와 유스케이스를 식별했으므로, 예비 단계의 컴포넌트 아키텍처를 만들어볼 수 있음
    • 늘 그렇듯이 이중으로 된 선은 아키텍처 경계를 나타냄
    • 뷰, 프레젠터, 인터랙터, 컨트롤러로 분리된 전형적인 분할 방법을 확인할 수 있음
    • 또한 대응하는 액터에 따라 카테고리를 분리했다는 사실도 확인할 수 있음
    • 각 컴포넌트는 단일 jar 파일에 해당한며, 이들 컴포넌트는 자신에게 할당된 뷰, 프레젠터, 인터랙터, 컨트롤러를 포함함
  • CatalogView와 Catalog Presenter는 특수한 컴포넌트임
    • 이는 카탈로그 조회하기라는 추상 유스케이스를 처리하는 나만의(로버트 마틴 만의) 방식임
    • 이 뷰와 프레젠터는 해당 컴포넌트 내부에 추상 클래스로 코드화 될 것임
    • 그리고 상속받는 컴포넌트에서는 이들 추상 클래스로부터 상속받은 뷰와 프레젠터 클래스들을 포함함
  • 시스템을 여러 컴포넌트로 분할해서 여러 개의 .jar로 전달해야 할까?
    • 이는 그럴수도 있고 아닐수도 있음
    • 대신 컴파일과 빌드 환경은 분명히 이 형태로 나눌 것이며, 컴포넌트를 독립적으로 전달할 수 있게 빌드하는 것도 가능할 것임
    • 또한 전달해야 할 이 모든 단위를 더 적은 개수로 합칠 수 있는 권한도 가지고 있다.
    • 선택지를 열어두면, 후에 시스템이 변경되는 양상에 맞춰 배포 방식을 조정할 수 있음

 

 

[ 의존성 관리 ]

  • 제어흐름은 오른쪽에서 왼쪽으로 이동함
    • 컨트롤러에서 입력이 발생하면 인터랙터에 의해 처리되어 결과가 만들어짐
    • 그리고 프레젠터가 결과의 포맷을 변경하고 뷰가 화면에 표시한다.
  • 모든 화살표가 오른쪽에서 왼쪽을 가리키지는 않으며, 사실 대다수의 화살표는 왼쪽에서 오른쪽으로 향함
    • 이는 아키텍처가 의존성 규칙을 준수하기 때문임
    • 모든 의존성은 경계를 한 방향으로만 가로지르는데, 항상 더 높은 수준의 정책을 포함하는 컴포넌트를 향함
    • 이를 통해 저수준의 세부사항에서 발생한 변경이 상위로 파급되어서 상위 수준의 정책에 영향을 미치지는 않음을 보장할 수 있음

 

 

[ 결론 ]

  • 그림의 아키텍처 다이어그램은 두 가지 서로 다른 차원의 분리 개념을 포함하고 있음
  • 하나는 단일 책임 원칙에 기반한 액터의 분리이며, 두 번째는 의존성 규칙임
  • 이 두 차원은 모두 서로 다른 이유로, 서로 다른 속도로 변경되는 컴포넌트를 분리하는데 그 목적이 있음
  • 서로 다른 이유라는 것은 액터와 관련이 있으며, 서로 다른 속도라는 것은 정책 수준과 관련이 있음

우리가 아키텍처를 혹은 작게는 패키지를, 더 작게는 클래스를 분할할 때 어떤 부분을 염두에 둬야하는지 명확하게 설명해주는 부분이다. 중요한 것은 "액터 기반의 분리"와 "의존셩 규칙의 준수"이다. 만들어지는 대부분의 코드들은 이 두 가지를 크게 염두하지 않는 것 같은데, 적어도 이 두가지가 핵심임은 머리에 담아두고 넘어가도록 하자.

 

 

 

 

34장. 빠져있는 장


[ 계층 기반 패키지 ]

  • 가장 단순한 첫 번째 설계 방식은 전통적인 수평 계층형 아키텍처임
  • 기술적인 관점에서 해당 코드가 하는 일에 기반해 코드를 분할하며, 이를 “계층 기반 패키지”라고 부름
  • 전형적인 계층형 아키텍처에는 웹, 업무 규칙, 영속성 코드를 위한 계층이 하나씩 존재함

 

 

  • 코드는 계층이라는 얇은 수평 조각으로 나뉘며, 각 계층은 유사한 종류의 것들을 묶는 도구로 사용됨
  •  “엄격한 계층형 아키텍처”의 경우 계층은 반드시 아래 계층에만 의존해야 하며, 자바의 경우 계층은 주로 “패키지”로 구현됨
  • 그림에서 계층 사이의 의존성은 모두 아래를 향하며, 다음의 자바 타입들이 존재함
    • OrdersController: 웹 컨트롤러이며, 웹 기반으로 처리한다. Spring MVC 컨트롤러 등이 여기 해당한다.
    • OrdersService: 주문 관련 업무 규칙을 정의하는 인터페이스
    • OrdersServiceImpl: OrdersService의 구현체
    • OrdersRepository: 영구 저장된 주문 정보에 접근하는 방법을 정의하는 인터페이스
    • JdbcOrdersRepository: OrdersRepository의 구현체
  • 처음에는 계층형 아키텍처가 적합한데, 이는 엄청난 복잡함을 겪지 않고도 무언가를 작동시켜주는 아주 빠른 방법임
  • 계층형 아키텍처의 문제는 소프트웨어가 커지고 복잡해지기 시작하면서 나타남
    • 3계층만으로는 모든 코드를 담기엔 부족하므로 더 잘게 모듈화해야 할지를 고민하게 됨
    • 또한 계층형 아키텍처는 업무 도메인에 대해 아무것도 말해주지 않으며 이 밖에도 큰 문제들이 있음

실제로 계층형 아키텍처로 구현을 하다 보면 "이게 좋은 아키텍처가 맞나?" 하는 순간이 온다. 그때면 보다 적합한 아키텍처는 없는지 혹은 어떻게 하면 계층형 아키텍처를 진화시켜 볼지 고민해보는 것도 좋다.

위에서 "계층형 아키텍처는 업무 도메인에 대해 아무것도 말해주지 않으며"라는 부분이 있는데, 그림의 패키지 구조로는 해당 아키텍처가 어떤 비즈니스를 하는지 말해주지 못한다는 것이다. 앞선 장에서 우리는 "소리치는 아키텍처"의 중요성에 대해 살펴보았다.

 

 

 

 

[ 기능 기반 패키지 ]

  • “기능 기반 패키지” 구조는 연관된 기능, 도메인 개념 또는 에그리거트 루트에 기반하여 수직의 얇은 조각으로 코드를 나누는 방식임
  • 전형적으로 모든 타입이 하나의 자바 패키지에 속하며, 패키지 이름은 그 안에 담긴 개념을 반영해서 지음

 

 

  • 동장하는 인터페이스와 클래스는 이전과 같지만, 모두가 단 하나의 패키지에 속함
  • 이는 “계층 기반 패키지”를 아주 간단히 리팩토링한 형태지만, 이제 코드의 상위 수준 구조가 업무 도메인에 대해 무언가를 알려줌
  • 드디어 우리는 이 코드 베이스가 주문과 관련한 무언가를 한다는 것을 볼 수 있음
  • 변경해야 할 코드가 한 패키지에 있으므로 “주문 조회하기” 유스케이스가 변경될 경우 변경할 코드를 찾는 작업이 쉬워질 수 있음
  • 하지만 이 방법 역시 차선책일 뿐임

 

 

 

[ 기능 기반 패키지 ]

 

 

  • “포트와 어댑터” 혹은 “육각형 아키텍처”, “경계, 컨트롤러, 엔티티(BCE)” 등의 방식으로 접근하기도 함
  • 그 이유는 도메인에 초점을 둔 코드가 데이터베이스 같은 세부 구현과 독립적이며 분리된 아키텍처를 만들기 위함임
  • 코드 베이스는 “내부”(도메인)와 “외부”(인프라)로 구성됨을 흔히 볼 수 있음
  • “내부”는 도메인 개념을 모두 포함하는 반면, “외부”는 외부 세계(UI, DB 등)와의 상호작용을 포함함
  • 여기서 핵심 규칙은 바로 외부가 내부에 의존하며, 절대로 그 반대로는 안된다는 점임

 

 

 

 

 

  • 위의 그림에서 domain 패키지가 내부이고 나머지는 모두 외부이며, 의존성이 내부를 향해 흐름
  • 이전의 OrdersRepository는 Orders라는 간단한 이름으로 바뀌었는데, 이는 도메인 주도 설계에서 비롯된 명명법임
    • 도메인 주도 설계에서는 내부에 존재하는 모든 것의 이름을 반드시 유비쿼터스 도메인 언어 관점에서 기술하라고 조언함
    • 즉, 도메인에 대해 논의할 때 우리는 “주문”에 대해 말하는 것이지, “주문 레포지토리”에 대해 말하는 것이 아님

 

 

 

 

[ 컴포넌트 기반 패키지 ]

  • 계층형 아키텍처의 목적은 기능이 같은 코드끼리 분리하는 것으로 웹은 업무 로직으로부터, 업무 로직은 데이터 접근으로부터 분리함
  • 엄격한 계층형 아키텍처에서는 의존성 화살표가 항상 아래로 향해야 하며, 각 계층은 반드시 아래 계층에만 의존해야 함
  • 하지만 의도치 않은 방식으로 의존성을 추가하더라도, 잘못되었지만 보기에는 여전히 좋은 비순환 의존성 그래프가 생성됨
  • 예를 들어 OrdersController에서 OrdersRepositry를 주입하여 코드가 동작하도록 만든 결과는 다음과 같음

 

 

  • 의존성 화살표는 여전히 아래를 향하지만, 이제 몇몇 유스케이스에서는 OrdersController가 OrdersService를 우회함
  • 이러한 조직화는 인접한 계층을 건너뛰는 일이 허용되기 때문에 흔히 완화된 계층형 아키텍처라고 부름
  • CQRS 패턴을 지키려고 하는 경우 등에 이는 의도된 결과이기도 한데, 이외에 업무 로직 계층을 우회하는 일은 바람직하지 못함
  • 필요한 것은 아키텍처 원칙으로, “웹 컨트롤러는 절대로 레포지토리에 직접 접근해서는 안된다” 등의 규칙이 강제성과 필요함
    • 많은 팀이 “우리는 훌륭한 규율, 코드 리뷰를 통해서 이 원칙을 강제합니다. 우리는 개발자를 믿습니다.”라고 얘기함
    • 하지만, 자금이 바닥나거나 납기가 다가온면 무슨 일이 벌어지는지를 우리는 이미 잘 알고 있음
    • 훨씬 적은 수의 팀만이 정적 분석 도구를 사용해서 아키텍처적인 위반 사항이 없는지를 검사하여 자동으로 강제한다고 답함
    • 이는 다소 조잡하지만 팀 차원에서 정의한 아키텍처 원칙을 위반하는 항목을 알려주고, 위반 시 빌드가 실패하므로 효과가 있음
    • 하지만 두 접근법 모두 오류가 있을 수 있으며, 그 결과를 알게 되는 주기가 필요 이상으로 길다는 문제가 있음
    • (시몬브라운) 개인적으로는 가능하면 컴파일러를 사용해서 아키텍처를 강제하는 방식을 선호함

 

 

 

  • “컴포넌트 기반 패키지”는 이것들을 혼합한 것으로, 큰 단위의 단일 컴포넌트와 관련된 모든 책임을 하나의 자바 패키지로 묶음
  • SOLID, REP, CCP, CRP 및 책의 조언에 대해 전적으로 동감하지만, 코드를 조직화하는 방법에 대한 다소 다른 결론임
  • 아래의 그림은 “주문 조회하기” 유스케이스가 어떤 그림인지 보여줌

 

 

 

 

  • 이 접근법에서는 “업무 로직”과 영속성 관련 코드를 하나로 묶고 이를 “컴포넌트”라고 부르며, 앞선 정의와는 다름
    • 앞부분: 컴포넌트는 배포 단위다. 컴포넌트는 시스템의 구성 요소로, 배포할 수 있는 가장 작은 단위다.
    • 여기: 컴포넌트는 멋지고 깔끔한 인터페이스로 감싸진 연관 기능들의 묶음으로, 애플리케이션과 같은 실행 환경 내부에 존재한다.
  • 컴포넌트 기반 패키지 접근법의 주된 이점은 무언가를 코딩해야 할 때 오직 한 곳만 둘러보면 된다는 점임
  • 이 컴포넌트 내부에서 관심사의 분리는 여전히 유효하며, 업무 로직은 데이터 영속성과 분리되어 있음
  • 하지만 이는 컴포넌트 구현과 관련된 세부사항으로 사용자는 알 필요가 없음

위의 그림과 살짝 다르지만 "만들면서 배우는 클린 아키텍처"의 저자인 Tom Hombergs는 2022년에 Spring IO에서 이와 관련된 발표를 한적이 있다. 인사이트를 얻을만한 부분이 꽤 많으니 봐보는 것도 좋을 것 같다.

 

 

 

[ 구현 세부사항엔 항상 문제가 있다. ]

  • 자바 언어에서 public 접근 지시자를 지나칠 정도로 방만하게 사용하는 모습을 자주 봄
  • 모든 타입에서 public을 사용한다는 건 사용하는 프로그래밍 언어가 제공하는 캡슐화 관련 이점을 활용하지 않겠다는 뜻임
  • 이로 인해 누군가가 구체적인 구현 클래스의 인스턴스를 직접 생성하는 코드를 작성하는 일을 절대 막을 수 없음
  • 따라서 지향하는 아키텍처 스타일을 위반하게 될 것임

나의 IntelliJ 기본 접근 제어자는 package-private이다. 나 역시도 이전에는 public으로 되어 있었지만, package-private의 장점을 알고 나서는 적극 활용하고 있다. 외부에서 함부로 접근하지 못하게 막아줄 뿐만 아니라 인텔리제이의 자동 완성 추천 목록도 줄어드어 추천이 최적화되는 등의 장점도 있다. 그러므로 적극적으로 사용하면 좋다.

 

 

 

 

[ 조직화 vs 캡슐화 ]

  • public 타입으로 어디에서도 사용할 수 있다면 패키지를 사용하는 이점이 거의 없으므로 사실상 패키지를 사용하지 않는 것임
  • public을 과용하면 이 장의 앞에서 제시한 네 가지 아키텍처 접근법은 본질적으로 완전히 같아짐

 

 

  • 접근 지시자를 적절하게 사용하면, 타입을 패키지로 배치하는 방식에 따라서 각 타입에 접근할 수 있는 정도가 크게 달라질 수 있음
  • 만약 다이어그램에서 패키지 구조를 다시 살려서 더 제한적인 접근 지시자를 사용할 수 있는 타입을 흐리게 표시하면 인상적임
  • 여기에서 설명한 내용은 모노리틱 애플리케이션에 대한 것으로, 모든 코드가 단 하나의 소스 코드 트리에 존재하는 경우임
  • 모노리틱에서 아키텍처 원칙을 강제할 때 자기 규율이나 컴파일 후처리 도구 말고, 반드시 컴파일러에 의지할 것을 권장함

 

 

  • “계층 기반 패키지” 접근법
    • OrdersService와 OrdersRepository 인터페이스는 외부 패키지의 클래스로부터 들어오는 의존성이 존재함
    • 그러므로 반드시 public으로 선언되어야 함
    • 반면 구현체 클래스(OrdersServiceImpl과 JdbcOrdersRepository)는 package protected으로 선언할 수 있음
    • 이들 클래스는 누구도 알 필요가 없는 구현 세부사항임
  • “기능 기반 패키지”
    • OrdersController가 패키지로 들어올 수 있는 유일한 통로이므로 나머지는 모두 package protected로 지정할 수 있음
    • 여기서 가장 주의할 점은, 이 패키지 밖의 코드는 컨트롤러를 통하지 않으면 주문 관련 정보에 접근할 수 없음
    • 이는 바람직할 대도 있고, 아닐 때도 있다.
  • “포트와 어댑터”
    • OrdersService와 Orders 인터페이스는 외부로부터 들어오는 의존성을 가지므로 public으로 지정해야 함
    • 이 경우에도 구현 클래스는 package protected로 지정하며, 런타임에 의존성을 주입시킬 수 있음
  • “컴포넌트 기반 패키지”
    • 컨트롤러는 OrdersCompont로 향하는 의존성을 가지며, 그 외의 모든 것은 package protected로 지정할 수 있음
    • 이제 이 패키지 외부의 코드에서는 OrdersRepository 인터페이스나 구현체를 직접 사용할 수 있는 방법이 전혀 없음
    • 따라서 컴파일러의 도움을 받아 “컴포넌트 기반 패키지” 아키텍처의 접근법을 강제할 수 있음

 

 

 

[ 다른 결합 분리 모드 ]

  • 다른 선택지로는 소스 코드 수준에서 의존성을 분리하는 방법도 있는데, 정확히는 서로 다른 소스 코드 트리로 분리하는 방법임
  • 포트와 어댑터를 예로 들자면 다음과 같은 소스 코드 트리를 만들 수 있음
    • 업무와 도메인용 소스 코드(기술이나 프레임워크와 독립적인 모든 것): OrdersService, OrdersServiceImpl, Orders
    • 웹용 소스코드: OrdersController
    • 데이터 영속성용 소스 코드: OrdersRepository
  • 마지막 두 소스 코드 트리는 업무와 도메인 코드에 대해 컴파일 시점에 의존성을 가짐
  • 반대로 업무와 도메인 코드 자체는 웹이나 데이터 영속성 코드에 대해서는 아무 것도 알지 못함
  • 구현 관점에서 이렇게 분리하려면 빌드 도구를 사용해서 모듈이나 프로젝트가 서로 분리되도록 구성해야 함
  • 하지만 이는 너무 이상적인 해결책인데, 현실에서 소스 코드를 이처럼 나누다 보면 성능, 복잡성, 유지보수 문제가 생김
  • 포트와 어댑터 접근법을 적용할 때는 이보다 간단한 방법을 사용하기도 하는데, 단순히 소스 코드 트리를 두 개만 만드는 것임
    • 도메인 코드(내부)
    • 인프라 코드(외부)

 

  • 이 다이어그램에서 인프라는 도메인에 대해 컴파일 시점의 의존성을 가짐
  • 이 접근법은 소스 코드를 조직화할 때 효과가 있겠지만, 잠재적으로 절충해야 할 부분이 있음
  • 인프라를 모아두는 것은 특정 인프라 코드가 도메인을 통하지 않고 애플리케이션의 다른 영역을 직접 호출할 수 있다는 뜻임
  • 예를 들어 웹 컨트롤러가 데이터베이스 레포지토리에 있는 코드를 직접 호출할 수 있음

개인적으로 업무에서 헥사고날 아키텍처를 적용해본 경험으로는 패키지 구조가 꽤나 복잡하다는 것이다. 결국 클래스는 도메인과 인프라로 나뉘어지는데 패키지도 실제로 이렇게 나누면 괜찮을 것 같다는 생각이 든다. 추후에는 이렇게 도메인 영역(orders)에 domain과 infra만 갖는 패키지로 구성을 해볼 게획이다.

 

 

[ 결론: 빠져 있는 조언 ]

  • 이 장은 최적의 설계를 꾀했더라도, 구현 전략에 얽힌 복잡함을 고려하지 않으면 설계가 순식간에 망가질 수도 있음을 강조함
  • 설계를 어떻게 해야만 원하는 코드 구조로 매핑할 수 있을지, 그 코드를 어떻게 조직화할지 고민해야 함
  • 또한 런타임과 컴파일타임에 어떤 결합분리 모드를 적용할지를 고민해야 함
  • 가능한 선택사항을 열어두되 실용주의적으로 행하고, 팀의 규모와 기술 수준, 해결책의 복잡성을 제약(일정과 예산)과 고려해야 함
  • 또한 선택된 아키텍처 스타일을 강제하는데 컴파일러의 도움을 받을 수 있을지를 고민해야 함
  • 그리고 데이터 모델과 같은 다른 영역에 결합되지 않도록 주의해야 하는데, 구현 세부사항에는 항상 문제가 있는 법임

 

 

위의 내용은 로버트 C 마틴의 클린 아키텍처 책을 읽고 정리한 내용입니다. 개인적인 설명은 기울임으로 표시해두었으니, 읽으면서 참고하시면 될 것 같습니다! 혹시 추가적인 의견 있으면 편하게 댓글 남겨주세요ㅎㅎ

 

 

 

관련 포스팅

  1. 클린 아키텍처 1부 소개 - 내용 정리 및 요약
  2. 클린 아키텍처 2부 벽돌부터 시작하기: 프로그래밍 패러다임 - 내용 정리 및 요약
  3. 클린 아키텍처 3부 설계 원칙 - 내용 정리 및 요약
  4. 클린 아키텍처 4부 컴포넌트 원칙 - 내용 정리 및 요약
  5. 클린 아키텍처 5부 아키텍처 - 내용 정리 및 요약
  6. 클린 아키텍처 6부 세부사항 - 내용 정리 및 요약

 

 

 

 

 

반응형
댓글
댓글쓰기 폼
반응형
공지사항
Total
3,532,677
Today
5,520
Yesterday
2,744
링크
TAG
more
«   2023/01   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함