본문 바로가기

책/클린 아키텍쳐 (완)

5부 아키텍처

15장 아키텍처란?

  • 소프트웨어 아키텍트라면 코드에서 탈피하여 고수준의 문제에 집중해야 한다는 거짓말에 절대로 속아 넘어가서는 안 된다.
  • 소프트웨어 시스템의 아키텍처란 시스템을 구축했던 사람들이 만들어낸 시스템의 형태다. 그 모양은 시스템을 컴포넌트로 분할하는 방법, 분할된 컴포넌트를 배치하는 방법, 컴포넌트가 서로 의사소통하는 방식에 따라 정해진다.
  • 아키텍처의 주된 목적은 시스템의 생명주기를 지원하는 것이다.
  • 아키텍처의 궁극적인 목표는 시스템의 수명과 관련된 비용은 최소화하고, 프로그래머의 생산성은 최대화하는 데 있다.

개발

  • 시스템 아키텍처는 개발팀이 시스템을 쉽게 개발할 수 있도록 뒷받침해야만 한다.
  • 팀별 단일 컴포넌트 아키텍처가 시스템을 배포, 운영, 유지보수하는데 최적일 가능성은 거의 없다.

배포

  • 소프트웨어 시스템이 사용될 수 있으려면 반드시 배포할 수 있어야 한다. 배포 비용이 높을수록 시스템의 유용성은 떨어진다. 따라서 소프트웨어 아키텍처는 시스템을 단 한 번에 쉽게 배포할 수 있도록 만드는 데 그 목표를 두어야 한다.

개발

  • 운영에서 겪는 대다수의 어려움은 소프트웨어 아키텍처에는 극적인 영향을 주지 않고도 단순히 하드웨어를 더 투입해서 해결할 수 있다.
  • 그렇더라도 시스템을 운영할 때 아키텍처가 맡는 또 다른 역할이 있다. 좋은 소프트웨어 아키텍처는 시스템을 운영하는 데 필요한 요구도 알려준다.
  • 시스템 아키텍처가 개발자에게 시스템의 운영 방식을 잘 드러내 준다고 할 수 있다. 시스템 아키텍처는 유스케이스, 기능, 시스템의 필수 행위를 일급 엔티티로 격상시키고, 이들 요소가 개발자에게 주요 목표로 인식되도록 해야 한다.

유지보수

  • 유지보수의 가장 큰 비용은 탐사와 이로 인한 위험부담에 있다.
  • 주의를 기울여 신중하게 아키텍처를 만들면 이 비용을 크게 줄일 수 있다.

선택사항 열어 두기

  • 소프트웨어를 부드럽게 만드는 것은 바로 이구조적 가치이기 때문이다.
  • 소프트웨어를 부드럽게 유지하는 방법은 선택사항을 가능한 한 많이, 그리고 가능한 한 오랫동안 열어 두는 것이다. 그렇다면 열어 둬야 할 선택사항이란 무엇일까? 그것은 바로 중요치 않은 세부사항이다.
  • 모든 소프트웨어 시스템은 주요한 두 가지 구성요소로 분해할 수 있다. 바로 정책과 세부사항이다.
  • 아키텍트의 목표는 시스템에서 정책을 가장 핵심적인 요소로 식별하고, 동시에 세부사항은 정책에 무관하게 만들 수 있는 형태의 시스템을 구축하는데 있다.
  • 선택사항을 더 오랫동안 열어 둘 수 있다면 더 많은 실험을 해볼 수 있고 더 많은 것을 시도할 수 있다.
  • 좋은 아키텍트는 결정되지 않은 사항의 수를 최대화한다.

장치 독립성

  • 대표적인 실수 중 하나는 코드를 입출력 장치와 직접 결합해버린 일이었다.

결론 

  • 좋은 아키텍트는 세부사항을 정책으로부터 신중하게 가려내고, 정책이 세부사항과 결합되지 않도록 엄격하게 분리한다.

16장 독립성

유스케이스

  • 시스템의 아키텍처는 시스템의 의도를 지원해야 한다는 뜻이다.
  • 아키텍처는 시스템의 행위에 그다지 큰 영향을 주지 않는다.
  • 행위를 명확히 하고 외부로 드러내며, 이를 통해 시스템이 지닌 의도를 아키텍처 수준에서 알아볼 수 있게 만드는 것이다.

운영

  • 아키텍처에서 각 컴포넌트를 적절히 격리하여 유지하고 컴포넌트 간 통신 방식을 특정 형태로 제한하지 않는다면, ... , 전환하는 일이 훨씬 쉬워질 것이다.

배포

  • 이러한 아키텍처를 만들려면 시스템을 컴포넌트 단위로 적절하게 분할하고 격리시켜야 한다. 여기에는 마스터 컴포넌트도 포함되는데, 마스터 컴포넌트는 시스템 전체를 하나로 묶고, 각 컴포넌트를 올바르게 구동하고 통합하고 관리해야 한다.

선택사항 열어놓기

  • 좋은 아키텍처는 선택사항을 열어둠으로써, 향후 시스템에 변경이 필요할 때 어떤 방향으로든 쉽게 변경할 수 있도록 한다.

계층 결합 분리

  • 결론적으로 아키텍트는 이들을 시스템의 나머지 부분으로부터 분리하여 독립적으로 변경할 수 있도록 해야만 한다

유스케이스 결합 분리

  • 우리는 시스템을 수평적 계층으로 분할하면서 동시에 계층을 가로지르는 얇은 수직적인 유스케이스로 시스템을 분할할 수 있다.

개발 독립성

  • 컴포넌트가 완전히 분리되면 팀 사이의 간섭은 줄어든다.

중복

  • 자동반사적으로 중복을 제거해버리는 잘못을 저지르는 유혹을 떨쳐내라. 중복이 진짜 중복인지 확인하라.

결론

  • 내가 말하려는 바는 시스템의 결합 모드는 시간이 지나면서 바뀌기 쉬우며, 뛰어난 아키텍트라면 이러한 변경을 예측하여 큰 무리 없이 반영할 수 있도록 만들어야 한다는 점이다.

17장 경계: 선 긋기

  • 소프트웨어 아키텍처는 선을 긋는 기술이며, 나는 이러한 선을 경계라고 부른다. 경계는 소프트웨어 요소를 서로 분리하고, 경계 한편에 있는 요소가 반대편에 있는 요소를 알지 못하도록 막는다.
  • 아키텍트의 목표는 필요한 시스템을 만들고 유지하는 데 드는 인적 자원을 최소화하는 것이라는 사실을 상기하자. 그렇다면 인적 자원의 효율을 떨어뜨리는 요인은 무엇일까? 바로 결합이다.
  • 어떤 종류의 결정이 이른 결정일까? 바로 시스테므이 업무 요구사항, 즉 유스케이스와 아무런 관련이 없는 결정이다.

두 가지 슬픈 이야기

  • 이 비극은 아키텍트가 너무 이르게 결정을 내림으로써 개발 비용을 엄청나게 가중시킨 사례다
  • 서비스를 중심으로 구조화된 소프트웨어 시스템이 본질적으로 잘못된 것은 아니다. w사의 실수는 SOA를 약속하는 일련의 도구들을 너무 일찍 채택하여 적용했다는 사실이다.

플러그인에 대한 논의

  • 경계는 변경의 축이 있는 지점에 그어진다. 경계의 한쪽에 위치한 컴포넌트는 경계 반대편의 컴포넌트와는 다른 속도로, 그리고 다른 이유로 변경된다.

결론

  • 소프트웨어 아키텍처에서 경계선을 그리려면 먼저 시스템을 컴포넌트 단위로 분할해야 한다. 일부 컴포넌트는 핵심 업무 규칙에 해당한다. 나머지 컴포넌트는 플러그인으로, 핵심업무와는 직접적인 관련이 없지만 필수 기능을 포함한다. 그런 다음 컴포넌트 사이의 화살표가 특정방향, 즉 핵심업무를 향하도록 이들 컴포넌트의 소스를 배치한다.

18장 경계 해부학

시스템 아키텍처는 일련의 소프트웨어 컴포넌트와 그 컴포넌트들을 분리하는 경계에 의해 정의된다.

두려운 단일체

  • 아키텍처 경계 중에서 가장 단순하며 가장 흔한 형태는 물리적으로 엄격하게 구분되지 않는 형태다.
  • 이러한 아키텍처는 거의 모든 경우에 특정한 동적 다형성에 의존하여 내부 의존성을 관리한다.
  • 가장 단순한 형태의 경계 횡단은 저수준의 클라이언트에서 고수준 서비스로 향하는 함수 호출이다.
  • 결과적으로, 소스 수준에서 결합이 분리되면 경계를 가로지르는 통신은 상당히 빈번할 수 있다.

19장 정책과 수준

  • 대다수의 주요 시스템에서 하나의 정책은 이 정책을 서술하는 여러 개의 조그만 정책들로 쪼갤 수 있다.
  • 소프트웨어 아키텍처를 개발하는 기술에는 이러한 정책을 신중하게 분리하고, 정책이 변겨되는 양상에 따라 정책을 재편성하는 일도 포함된다.
  • 좋은 아키텍처라면 각 컴포넌트를 연결할 때 의존성의 방향이 컴포넌트의 수준을 기반으로 연결되도록 만들어야 한다. 즉, 저수준 컴포넌트가 고수준 컴포넌트에 의존하도록 설계되어야 한다.

수준

  • 주목할 점은 데이터 흐름과 소스 코드 의존성이 항상 같은 방향을 가리키지는 않는다는 사실이다.
  • 정책을 컴포넌트로 묶는 기준은 정책이 변경되는 방식에 달려있다는 사실을 상기하자. 단일 책임 원칙과 공통 폐쇄 원칙에 따르면 동일한 이유로 동일한 시점에 변경되는 정책은 함께 묶인다.

20장 업무 규칙

  • 업무 규칙은 사업적으로 수익을 얻거나 비용을 줄일수 있는 규칙 또는 절차다.
  • 이러한 규칙을 핵심 업무 규칙이라고 부를 것이다.
  • 핵심 업무 규칙은 보통 데이터를 요구한다.
  • 우리는 이러한 데이터를 핵심 업무 데이터라고 부르겠다.
  • 핵심 규칙과 핵심 데이터는 본질적으로 결합되어 있기 때문에 객체로 만들기 좋은 후보가 된다. 우리는 이러한 유형의 객체를 엔티티라고 하겠다.

엔티티

  • 엔티티는 컴퓨터 시스템 내부의 객체로서, 핵심 업무 데이터를 기반으로 동작하는 일련의 조그만 핵심 업무 규칙을 구체화한다.

유스케이스

  • 유스케이스는 자동화된 시스템이 사용되는 방법을 설명한다. 유스케이스는 사용자가 제공해야 하는 입력, 사용자에게 보여줄 출력, 그리고 해당 출력을 생성하기 위한 처리 단계를 기술한다. 엔티티 내의 핵심 업무 규칙과는 반대로, 유스케이스는 애플리케이션에 특화된 업무 규칙을 설명한다.
  • 유스케이스는 사용자 인터페이스를 기술하지 않는다는 점이다.
  • 엔티티는 자신을 제어하는 유스케이스에 대해 아무것도 알지 못한다.

결론

  • 업무 규칙은 소프트웨어 시스템이 존재하는 이유다. 업무 규칙은 핵심적인 기능이다.
  • 업무 규칙는 사용자 인터페이스나 데이터베이스와 같은 수준의 관심사로 인해 오염되어서는 안 되며, 원래 그대로의 모습으로 남아 있어야 한다.

21장 소리치는 아키텍처

아키텍처의 테마

  • 소프트웨어 애플리케이션의 아키텍처도 애플리케이션의 유스케이스에 대해 소리쳐야 한다.

아키텍처의 목적

소프트웨어 애플리케이션의 아키텍처도 애플리케이션의 유스케이스에 대해 소리쳐야 한다.

 

아키텍처의 목적

  • 좋은 아키텍처는 유스케이스를 그 중심에 두기 때문에, 프레임 워크나 도구 환경에 전형 구애받지 않고 유스케이스를 지원하는 구조를 아무런 문제없이 기술할 수 있다.
  • 뿐만 아니라 이러한 결정을 쉽게 번복할 수 있도록 한다.

프레임워크는 도구일 뿐, 삶의 방식은 아니다.

프레임워크가 아키텍처의 중심을 차지하는 일을 막을 수 있는 전략을 개발하라

 

테스트하기 쉬운 아키텍처

프레임워크를 전혀준비하지 않더라도 필요한 유스케이스 전부에 대해 단위 테스트를 할 수 있어야 한다.

 

22장 클린 아키텍처

  • 헥사고날 아키텍처, DCI, BCE, 이들의 목표는 모두 같은데, 바로 관심사의 분리다. 이들은 모두 소프트웨어를 계층으로 분리함으로써 관심사의 분리라는 목표를 달성할 수 있었다.
  • 프레임워크 톡립성
  • 테스트 용이성
  • UI 독립성
  • 데이터베이스 독립성
  • 모든 외부 에이전시에 대한 독립성

의존성 규칙

  • 이러한 아키텍처가 동작하도록 하는 가장 중요한 규칙은 의존성 규칙이다.
  • 소스코드 의존성은 반드시 안쪽으로, 고수준의 정책을 향해야 한다.

엔티티

  • 엔티티는 전사적인 핵심 업무 규칙을 캡슐화한다. 엔티티는 메서드를 가지는 객체이거나 일련의 데이터 구조와 함수의 집합일 수도 있다.
  • 운영 관점에서 특정 애플리케이션에 무언가 변경이 필요하더라도 엔티티 계층에는 절대로 영향을 주어서는 안 된다.

유스케이스

  • 유스케이스 계층의 소프트웨어는 애플리케이션에 특화된 업무 규칙을 포함한다.
  • 유스케이스는 엔티티로 들어오고 나가는 데이터 흐름을 조정하며, 엔티티가 자신의 핵심 업무 규칙을 사용해서 유스케이스의 목적을 달성하도록 이끈다.

인터페이스 어댑터

  • 인터페이스 어댑터 계층은 일련의 어댑터들로 구성된다.
  • 프레젠터, 뷰, 컨트롤러는 모두 인터페이스 어댑터 계층에 속한다. 모델은 그저 데이터 구조 정도에 지나지 않으며, 컨트롤러에서 유스케이스로 전달되고, 다시 유스케이스에서 프레젠터와 뷰로 되돌아 간다.

프레임워크와 드라이버

  • 프레임워크와 드라이버 계층은 모든 세부사항이 위치하는 곳이다. 웹은 세부사항이다. 데이터 베이스는 세부사항이다.

원은 네 개여야만 하나?

  • 항상 네 개만 사용해야 한다는 규칙은 없다. 하지만 어떤 경우에도 의존성 규칙은 적용된다. 소스 코드 의존성은 항상 안쪽을 향한다.

경계 횡단하기

  • 이처럼 제어흐름과 의존성의 방향이 명백히 반대여야 하는 경우, 대체로 의존성 역전 원칙을 사용하여 해결한다.
  • 우리는 동적 다형성을 이용하여 소스코드 의존성을 제어흐름과는 반대로 만들수 있고, 이를 통해 제어흐름이 어느 방향으로 흐르더라도 의존성 규칙을 준수할 수 있다.

경계를 횡단하는 데이터는 어떤 모습인가

  • 경계를 가로지르는 데이터는 흔히 간단한 데이터 구조로 이루어져 있다.
  • 중요한 점은 격리되어 있는 간단한 데이터 구조가 경계를 가로질러 전달된다는 사실이다.
  • 별다른 조치 없이 제어흐름을 따라 구현하면 안쪽 원의 코드가 바깥쪽 원의 코드를 호출하게 된다.
  • 따라서 경계를 가로질러 데이터를 전달할 때, 데이터는 항상 내부의 원에서 사용하기에 가장 편리한 형태를 가져야만 한다.

결론

  • 소프트웨어를 계층으로 분리하고 의존성 규칙을 준수한다면 본질적으로 테스트하기 쉬운 시스템을 만들게 될 것이며, 그에 따른 이점을 누릴 수 있다.

23장 프레젠터와 험블 객체

험블 객체 패턴

  • 험블 객체 패턴은 디자인 패턴으로 ,테스트하기 어려운 행위와 테스트하기 쉬운 행위를 단위 테스트 작성자가 분리하기 쉽게 하는 방법으로 고안되었다.
  • 행위들을 두 개의 모듈 또는 클래스로 나눈다. 이들 모듈중 하나가 험블이다. 가장 기본적인 본질은 남기고, 테스트하기 어려운 행위를 모두 험블 객체로 옮긴다.

테스트와 아키텍처

  • 테스트 용이성은 좋은 아키텍처가 지녀야할 속성으로 오랫동안 알려져 왔다. 험블 객체 패턴이 좋은 예인데, 행위를 테스트하기 위운부분과 테스트하기 어려운 부분으로 분리하면 아키텍처 경계가 정의되기 때문이다.

데이터 매퍼

  • ORM보다는 차라리 데이터 매퍼라고 부르는 편이 나아 보이는데, 관계형 데이터 베이스 테이블로부터 가져온 데이터를 데이터 구조에 맞게 담아주기 때문이다.
  • 이러한 ORM 시스템은 어디에 위치해야 하는가? 물론 데이터베이스 계층이다.

결론

  • 각 아키텍처 경꼐마다 경계 가까이 숨어 있는 험블 객체 패턴을 발견할 수 있을 것이다. 경계를 넘나드는 통신은 거의 모두 간단한 데이터 구조를 수반할 떄가 많고, 대개 그 경계는 테스트하기 어려운 무언가와 테스트하기 쉬운 무언가로 분리될 것이다.

24장 부분적 경계

마지막 단계를 건너뛰기

  • 부분적 경계를 생성하는 방법 하나는 독립적으로 컴파일하고 배포할 수 있는 컴포넌트를 만들기 위한 작업은 모두 수행한 후,  단일 컴포넌트에 그대로 모아만 두는 것이다.

일차원 경계

  • 완벽한 형태의 아키텍처 경계는 양방향으로 격리된 상태를 유지해야 하므로 쌍방향 Boundary 인터페이스를 사용한다.

파사드

  • 이보다 훨씬 더 단순한 경계는 파사드 패턴으로 그림 24.2와 같다. 이 경우에는 심지어 의존성 역전까지도 희생한다.

25장 계층과 경계

  • 시스템이 세 가지 컴포넌트로(UI, 업무 규칙, 데이터베이스)로만 구성된다고 생각하기 쉽다. 몇몇 단순한 시스템에서는 이 정도로 충분하다. 하지만 대다수의 시스템에서 컴포넌트의 개수는 이보다 훨씬 많다.

결론

  • 이 예제를 가져온 이유는 아키텍처 경계가 어디에나 존재한다는 사실을 보여주기 위함이다. 아키텍트로서 우리는 아키텍처 경계가 언제 필요한지를 신중하게 파악해내야 한다.
  • 한편으로는 매우 똑똑한 일부 사람들이 우리에게 수년 동안 말해왔듯이 추상화가 필요하리라고 미리 예측해서는 안 된다. 이것이 바로 YAGNI가 말하는 철학이다.
  • 첫 조짐이 보이는 시점이 되면, 해당 경계를 구현하는 비용과 무시할 때 감수할 비용을 가늠해 본다. 그리고 결정된 사항을 자주 검토한다.

26장 메인 컴포넌트

궁극적인 세부사항

  • 메인은 모든 팩토리와 전략, 그리고 시스템 전반을 담당하는 나머지 기반 설비를 생성한 후, 시스템에서 더 높은 수준을 담당하는 부분으로 제어권을 넘기는 역할을 맡는다.
  • 메인은 클린 아키텍처에서 가장 바깥 원에 위치하는, 지저분한 저수준 모듈이라는 점이다. 메인은 고수준의 시스템을 위한 모든 것을 로드한 후, 제어권을 고수준의 시스템에게 넘긴다.

27장 '크고 작은 모든' 서비스들

서비스 아키텍처?

  • 단순히 애플리케이션의 행위를 분리할 뿐인 서비스라면 값비싼 함수 호출에 불과하며, 아키텍처 관점에서 꼭 중요하다고 볼 수는 없다.
  • 그러나 서비스 그 자체로는 아키텍처를 잘 정의하지 않는다.

서비스의 이점?

결합 분리의 오류

  • 큰 이점 하나는 서비스 사이의 결합이 확실히 분리된다는 점이다.
  • 이 말에는 어느정도 일리가 있지만, 꼭 그런 것만은 아니다. 물론 서비스 개별 변수 수준에서는 각각 결합이 분리된다. 하지만 프로세서 내의 데이터 또는 네트워크 상의 공유 자원 때문에 결합될 가능성이 여전히 존재한다.

개발 및 배포 독립성의 오류

  • 시스템의 개발, 유지보수, 운영또한 비슷한 수의 독립적인 팀 단위로 분할할 수 있다고 여긴다. ... 극히 일부일 뿐이다.
  • 모노리틱 시스템이나 컴포넌트 기반 시스템으로도 구축할 수 있다는 사실
  • 데이터나 행위에서 어느 정도 결합되어 있다면 결합된 정도에 맞게 개발, 배포, 운영을 조정해야만 한다.

야옹이 문제

  • 횡단 관심사가 지닌 문제다. ... 새로운 기능이 기능적 행위를 횡단하는 상황에 상황에 매우 취약하다.

횡단 관심사

  • 지금까지 배운 것은 아키텍처 경계가 서비스 사이에 있지 않다는 사실이다. 오히려 서비스를 관통하며, 서비스를 컴포넌트 단위로 분할한다.

결론

  • 시스템의 아키텍처는 시스템 내부에 그러진 경계와 경계를 넘나드는 의존성에 의해 정의된다.

28장 테스트 경계

테스트는 시스템의 일부이며, 아키텍처에도 관여한다.

 

테스트를 고려한 설계

  • 개발자는 종종 테스트가 시스템의 설계 범위밖에 있다고 여긴다. 이 관점은 치명적이다.
  • 시스템의 공통 컴포넌트가 변경되면 수백 심지어 수천 개의 테스트가 망가진다.

결론

  • 테스트는 시스템 외부에 있지 않다. 오히려 시스템의 일부다.

29장 클린 임베디드 아키텍처

앱-티튜드 테스트

  • 켄트 백은 소프트웨어를 구축하는 세 가지 활동을 다음과 같이 기술했다.
    • "먼저 동작하게 만들어라." 소프트웨어가 동작하지 않는다면 사업은 망한거다.
    • "그리고 올바르게 만들어라." 코드를 리팩터링해서 당신을 포함한 나머지 사람들이 이해할 수 있게 만들고, 요구가 변경되거나 요구를 더 잘 이해하게 되었을 때 코드를 개선할 수 있게 만들어라.
    • "그리고 빠르게 만들어라." 코드를 리팩터링해서 '요구되는' 성능을 만족시켜라

계층

  • 소프트웨어와 펌웨어가 서로 섞이는 일은 안티 패턴이다.

하드웨어는 세부사항이다.

  • 소프트웨어와 펌웨어 사이의 경계는 코드와 하드웨어 사이의 경계와는 달리 잘 정의하기가 대체로 힘들다.

 

' > 클린 아키텍쳐 (완)' 카테고리의 다른 글

6부 세부사항  (0) 2024.05.31
4부 컴포넌트 원칙  (0) 2024.04.30
3부 설계원칙  (1) 2024.04.27
2부 벽돌부터 시작하기: 프로그래밍 패러다임  (0) 2024.04.01
1부 소개  (0) 2024.04.01