[Clean Architecture] 5부 아키텍처

okstring·2022년 11월 18일
0
post-thumbnail

5부 아키텍처

15장 아키텍처란

  • 소프트웨어 아키텍트라면 코드에서 탈피하여 고수준의 문제에 집중해야 한다는 거짓말에 절대로 속아 넘어가서는 안된다. 코드와 동떨어져서는 안된다.

  • 프로그래밍 작업을 맡을 뿐만 아니라 동시에 나머지 팀원들이 생산성을 극대화 할 수 있는 설계를 하도록 방향을 이끌어준다

  • 다른 프로그래머 만큼 코드를 많이 작성하지 않을 수도 있지만 프로그래밍 작업에는 지속적으로 참여한다.

  • 소프트웨어 시스템의 아키텍처란 시스템을 구축했던 사람들이 만들어낸 시스템의 형태다. 그 모양은 시스템을 컴포넌트로 분할하는 방법, 분할된 컴포넌트를 배치하는 방법, 컴포넌트가 서로 의사소통하는 방식에 따라 정해진다. 그리고 그 형태는 아키텍처 안에 담긴 소프트웨어 시스템이 쉽게 개발, 배포, 운영, 유지보수 되도록 만들어진다.

    이러한 일을 용이하게 만들기 위해서는 가능한 한 많은 선택지를, 가능한 한 오래 남겨두는 전략을 따라야 한다.

  • 시스템 아키텍처는 시스템이 제대로 동작하기를 최우선 목표 중 하나로 지원해야 한다. (수동적, 피상적 의미로)

  • 아키텍처의 주된 목적

    • 시스템의 생명주기를 지원하는 것
    • 시스템을 쉽게 이해하고
    • 쉽게 개발하며
    • 쉽게 유지보수하고
    • 쉽게 배포하게 해준다.
    • 궁극적인 목표는 시스템의 수명과 관련된 비용은 최소화하고 프로그래머의 생산성은 최대화 하는데 있다.

개발

  • 총 다섯팀이 잘 설계된 컴포넌트 단위, 안정된 인터페이스가 없다면 개발이 진척되지 않고 각자 다섯개의 컴포넌트로 발전될 가능성이 높다.

배포

  • 소프트웨어 아키텍처는 시스템을 단 한 번에 쉽게 배포할 수 있도록 만드는 데 그 목표를 두어야 한다.

운영

  • 아키텍처가 시스템 운영에 미치는 영향은 미미하다. 단순히 하드웨어를 더 투입해서 해결할 수 있다.
  • 또다른 역할은 시스템을 운영하는데 필요한 요구도 알려준다.
    • 유스케이스, 기능, 시스템의 필수 행위를 일급(first-class)엔티티로 격상시키고, 이들 요소가 개발자에게 주요 목표로 인식되도록 해야 한다. 이는 개발과 유지보수에 큰 도움이 된다.

유지보수

  • 유지보수는 소프트웨어 시스템에서 비용이 가장 많이 든다. 특히 탐사(spelunking)와 이로 인한 위험부담에 있다.
    • 탐사란 새로운 기능 추가, 결함 수정 시 소프트웨어를 파헤쳐서 어디를 고치는게 최선인지 결정할 때 드는 비용

선택사항 열어두기

  • 말 그래도 소프트웨어를 부드럽게 유지하는 방법은 선택사항을 가능한 한 많이, 그리고 가능한 한 오랫동안 열어두는 것이다.
    • 열어둬야 할 선택사항이란? 중요치 않은 세부사항이다.
    • 모든 소프트웨어 시스템은 주요한 두 가지 구성요소로 분해 할 수 있다. 정책(polisy)과 세부사항(detail)이다.
      • 정책이란 시스템의 진정한 가치가 살아있는 곳이다.
      • 세부사항은 사람, 외부시스템, 프로그래머가 정책과 소통할 때 필요한 요소지만 정책이 가진 행위에는 조금도 영향을 미치지 않는다.
        • 이러한 세부사항에는 입출력 장치, 데이터베이스, 웹시스템, 서버, 프레임워크 등이 있다.
    • 아키텍트의 목표는 시스템에서 정책을 가장 핵심적인 요소로 식별하고 동시에 세부사항은 정책에 무관하게 만들 수 있는 형태의 시스템을 구축하는데 있다.
    • 예를 들어
      • 개발 초기에 고수준의 정책에서는 어떤 종류의 데이터베이스를 사용하는지 신경써서는 안 된다.
      • 개발 초기에 웹서버를 선택할 필요가 없다. 고수준의 정책은 자신이 웹을 통해 전달된다는 사실을 알아서는 안된다.
      • 개발 초기에는 의존성 주입 프레임워크를 적용할 필요가 없다. 고수준의 정책은 의존성을 해석하는 방식에 대해 신경써서는 안 된다.
    • 세부사항에 몰두하지 않은 채 고수준의 정책을 만들 수 있다면 이러한 세부사항에 대한 결정을 오랫동안 미루거나 연기할 수 있다.

좋은 아키텍트는 결정되지 않은 사항의 수를 최대화 한다

장치 독립성

  • 예전 사례 중 대표적인 실수 중 하나는 코드를 입출력 장치와 직접 결합해버린 일이다.
    • 카드 판독기에서 카드를 읽어야 하는데 큰 규모의 천공카드는 관리하기가 어려웠고 잃어버리면 데이터 무결성이 깨질수도 있다.
    • 오늘날의 운영체제는 입출력 장치를 소프트웨어 함수로 추상화했고 해당 함수는 천공카드와 같은 단위 레코드를 처리한다.

결론

좋은 아키텍트는 세부사항을 정책으로부터 신중하게 가려내고, 정책이 세부사항과 결합되지 않도록 엄격하게 분리한다. 이를 통해 정책은 세부사항에 관한 어떠한 지식도 갖지 못하게 되며, 어떤 경우에도 세부사항에 의존하지 않게 된다. 좋은 아키텍트는 세부사항에 대한 결정을 가능한 한 오랫동안 미룰 수 있는 방향으로 정책을 설계한다.

16장 독립성

유스케이스

  • 유스케이스의 경우 시스템의 아키텍처는 시스템의 의도를 지원해야 한다는 뜻이다.
    • 만약 시스템이 장바구니 애플리케이션이라면, 이 아키텍처는 장바구니와 관련된 유스케이스를 지원해야 한다.
    • 행위를 명확히 하고 외부로 드러내며, 이를 통해 시스템이 지닌 의도를 아키텍처 수준에서 알아볼 수 있게 만드는 것이다

운영

  • 만약 시스템에서 수밀리초 안에 3차원의 빅데이터 테이블에 질의해야 한다면, 반드시 이러한 운영 작업을 허용할 수 있는 형태로 아키텍처를 구조화해야 한다.

개발

  • 콘웨이(Conway)의 법칙이 작용하는 지점이 바로 여기다.
    • 콘웨이의 법칙이란: 시스템을 설계하는 조직이라면 어디든지 그 조직의 의사소통 구조와 동일한 구조의 설계를 만들어 낼 것이다.
  • 각 팀이 독립적으로 행동하기 편한 아키텍처를 확보하여 서로를 방해하지 않도록 해야 한다.

배포

  • 목표는 즉각적인 배포(immediate deployment)다. 시스템이 빌드된 후 즉각 배포할 수 있도록 지원해야 한다.
  • 이러한 아키텍처를 만들려면 시스템을 컴포넌트 단위로 적절하게 분할하고 격리시켜야 한다.

선택사항 열어놓기

  • 좋은 아키텍처는 컴포넌트 구조와 관련된 이 관심사들 사이에서 균형을 맞추고, 각 관심사 모두를 만족시킨다. 하지만 이러한 균형을 잡기가 매우 어렵지만 아키텍처 원칙은 구현하는 비용이 비싸지 않아 관심사들 사이에서 균형을 잡는데 도움이 된다.

계층 결합 분리

  • 단일 책임 원칙과 공통 폐쇄 원칙을 적용하여, 그 의도의 맥락에 따라서 다른 이유로 변경되는 것들은 분리하고, 동일한 이유로 변경되는 것들은 묶는다.
    • 만약 유스케이스가 두가지 요소를 모두 포함한다면 서로 다른 것들을 분리할 것이다. 이렇게 함으로써 두 요소를 서로 독립적으로 변경할 수 있을 뿐만 아니라, 유스케이스는 여전히 가시적이며 분명하게 유지할 수 있다.
    • 서로 다른 두 유형의 규칙은 각자 다른 속도로, 그리고 다른 이유로 변경될 것이기 때문이다
    • 이러한 수평적인 계층으로 분리하는 것의 예로 UI, 애플리케이션에 특화된 업무 규칙, 애플리케이션과는 독립적인 업무 규칙, 데이터 베이스 등을 들 수 있다.

유스케이스 결합 분리

  • 서로 다른 이유로 변경되는 것에는 또 유스케이스 그 자체가 있다.
    • 주문 입력 시스템에서 주문을 추가하는 유스케이스는 주문을 삭제하는 유스케이스와는 틀림없이 다른 속도로, 그리고 다른 이유로 변경된다.
  • 이와 동시에 유스케이스는 시스템의 수평적인 계층을 가로지르도록 자른 수직으로 좁다란 조각이기도 하다.
    • 주문 추가 유스케이스의 UI 부분 / 주문 삭제 유스케이스의 UI 부분
  • 이렇게 결합을 분리하면 기존 요소에 지장을 주지 않고도 새로운 유스케이스를 계속해서 추가할 수 있게 된다.
  • 또한 이렇게 다른 관점(sapect)을 사용하게 되면, 새로운 유스케이스를 추가하더라도 기존 유스케이스에 영향을 주는 일은 거의 없을 것이다.

결합 분리 모드

  • 위와같이 결합 분리를 하게 되면 높은 처리량을 보장해야 하는 유스케이스와 낮은 처리량으로도 충분한 유스케이스는 이미 분리되어 있을 가능성이 높다. 간단히 말해 유스케이스를 위해 수행하는 그 작업들은 운영에도 도움이 된다.

개발 독립성

  • 결합이 분리되면 팀 단위 작업에서 간섭은 줄어든다

배포 독립성

  • 배포측면에서도 분리한 유스케이스에 따라 고도의 유연성이 생긴다.

중복

  • 소프트웨어에서 중복은 일반적으로 나쁜것이다.
    • 하지만 서로 다른 속도와 다른 이유로 변경된다면 두 코드는 진짜 중복이 아니다. 이 점을 유의해야 한다. 그렇지 않고 합쳐버리면 나중에 코드를 다시 분리하는데 큰 수고를 감수해야 한다.
    • 특정 데이터베이스 레코드의 데이터 구조가 특정 화면의 데이터 구조와 상당히 비슷하다는 점을 발견했다고 그대로 그 레코드를 ViewModel로 분리하지 않고 있는 그대로 UI까지 전달한다면 우발적 중복이므로 조심해라.

결합 분리 모드(다시)

  • 소스 수준 분리 모드
    • 소스코드 모듈 사이의 의존성을 제어할 수 있다.
    • 모든 컴포넌트가 같은 주소 공간에서 실행되고 서로 통신할 때는 간단한 함수 호출을 사용한다.
  • 배포 수준 분리 모드
    • jar, DLL, 공유 라이브러리와 같이 배포 가능한 단위들 사이의 의존성을 제어할 수 있다.
  • 서비스 수준 분리 모드
    • 의존하는 수준을 데이터 구조 단위까지 낮출 수 있다.
    • 이를 통해 모든 실행 가능한 단위는 소스와 바이너리 변경에 대해 서로 완전히 독립적이게 된다
    • 문제점은 개발 시간 측면 뿐 아니라 시스템 자원 측면에서도 비용이 많이 든다는 사실이다.
      • 이처럼 컴포넌트가 서비스화될 가능성이 있다면 나는 컴포넌트 결합을 분리하되 서비스가 되기 직전에 멈추는 방식을 선호 그리고 선택권을 열어둔다.
      • 배포나 개발에서 문제가 생기면 일부 결합을 배포수준까지 분리해 대응하고 이렇게 점차적으로 시스템을 변경해나간다.
      • 즉 개발, 배포, 운영적인 문제가 증가하면 서비스 수준으로 전환할 배포 단위들을 신중하게 선택한 후, 점차적으로 서비스화하는 방향으로 시스템을 변경해나간다

결론

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

17장 경계: 선 긋기

  • 소프트웨어 아키텍처는 선을 긋는 기술. 이것을 경계(boundary)라고 부른다
  • 경계는 소프트웨어 요소를 서로 분리하고, 경계 한편에 있는 요소가 반대편에 있는 요소를 알지 못하도록 한다.
  • 인적자원의 효율을 떨어뜨리는 요인은? 바로 결합(coupling)이다

두 가지 슬픈 이야기

예시를 토대로, 기본 뼈대만 갖춘 웹 서버는 단순한 단일 소프트웨어이기에 구현이 간단할 뿐만 아니라 어떤 웹 프레임워크를 사용할지에 대한 결정을 훨씬 나중으로 연기할 수 있도록 해주었기 때문이다. 간단히 말해서 경계선을 긋는 행위는 결정을 늦추고 연기하는 데 도움이 되었고, 궁극적으로는 시간을 절약해준다. 이것이 바로 좋은 아키텍처라면 반드시 해야 하는 일이다.

어떻게 선을 그을까? 그리고 언제 그을까?

관련이 잇는 것과 없는 것 사이에 선을 긋는다. GUI는 업무 규칙과는 관련 없기 때문에, 이 둘 사이에는 반드시 선이 있어야 한다.

인터페이스 뒤로 숨은 데이터 베이스

  • 이 도표에서 DatabaseAccess가 존재한다는 사실을 알고 있는 클래스는 없다는 뜻이다

  • 두 컴포넌트 사이에 이러한 경계선을 그리고 화살표의 방향이 Business Rules를 향하도록 만들었으므로, BusinessRules에서는 어떤 종류의 데이터 베이스도 사용할 수 있음을 알 수 있다. Database 컴포넌트는 다향안 구현체로 교체될 수 있으며, BusinessRules는 조금도 개의치 않는다.

입력과 출력은?

  • 입력과 출력은 중요치 않다는 사실
  • 인터페이스 뒤에는 인터페이스를 조작하는 모델이 존재한다는 사실을 잊어버리지 않아야 한다. 중요한 것은 업무 규칙이다.
  • 이번에 마찬가지로 GUI와 BusinessRules 컴포넌트가 경계선에 의해 분할된다는 사실을 볼 수 있다.

  • GUI는 다른 종류의 인터페이스로 얼마든지 교체할 수 있으며 BusinessRules는 전혀 개의치 않는다는 사실을 알 수 있다.

플러그인 아키텍처

  • 데이터페이스와 GUI에 대해 내린 두 가지 결정을 하나로 합쳐서 보면 컴포넌트 추가와 관련한 일종의 패턴이 만들어진다. 이 해턴은 시스템에서 서드파티 플러그인을 사용할 수 있게 한 바로 그 패턴과 동일하다.
  • 핵심적인 업무 규칙은 분리되어있고, 또한 독립적이다.

  • 이 설계에서 사용자 인터페이스는 플러그인 형태로 고려되었기에 수 많은 종류의 사용자 인터페이스를 플러그인 형태로 연결할 수 있게 된다.

플러그인에 대한 논의

  • 경계는 변경의 축이 있는 지점에 그어진다. 경계의 한쪽에 위치한 컴포넌트는 경계 반대편의 컴포넌트와는 다른 속도로, 그리고 다른 이유로 변경된다
  • GUI는 업무 규칙과는 다른 시점에 다른 속도로 변경되므로, 둘 사이에는 반드시 경계가 필요하다. 업무 규칙은 의존성 주입 프레임워크와는 다른 시점에 그리고 다른 이유로 변경되므로, 둘 사이에도 반드시 경계가 필요하다

결론

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

의존성 화살표는 저수준 세부사항에서 고수준의 추상화를 향하도록 배치된다.

18장 경계 해부학

경계 횡단하기

  • '런타임에 경계를 횡단한다'함은 그저 경계 한쪽에 있는 기능에서 반대편 기능을 호출하여 데이터를 전달하는 일에 불과하다.

두려운 단일체

  • 아키텍처 경계중에서 가장 단순하며 가장 흔한 형태는 물리적(함수와 데이터가 단일 프로세서에 같은 주소공간을 공유)으로 엄격하게 구분되지 않는 형태다. 이전 장에서는 이를 소스 수준 분리 모드라고 불렀다.
  • 배포 관점에서 보면 이는 단일체(monolith)라고 불리는 단일 실행 파일에 지니지 않으며 이 단일체는 경계가 드러나지 않는다.
  • 이러한 아키텍처는 거의 모든 경우에 동적 다형성에 의존하여 내부 의존성을 관리한다.
  • 가장 단순한 형태의 경계 횡단은 저수준 클라이언트에서 고수준 서비스로 향하는 함수 호출이다.

  • 제어흐름은 왼쪽에서 오른쪽으로 경계를 횡단한다. 이때 Client는 Data 인스턴스를 전달하는데 더 정교한 다른 기법을 통해 전달할 수도 있다.

  • 고수준 클라이언트가 저수준 서비스를 호출해야 한다면 동적 다형성을 사용하여 제어흐름과는 반대 방향으로 의존성을 역전시킬 수 있다. 이렇게 하면 런타임 의존성은 컴파일 타임 의존성과는 반대가 된다.
  • 이처럼 정적 링크된 모노리틱 구조의 실행파일이라도 이처럼 규칙적인 방식으로 구조를 분리하면 프로젝트를 개발, 테스트, 배포하는 작업에 큰 도움이 된다.

배포형 컴포넌트

  • 아키텍처의 경계가 물리적으로 드러날수도 있는데 그 중 가장 단순한 형태는 동적 링크 라이브러리다.(자바 jar 파일, 루비 젬(gem) 등)
  • 이는 배포수준 결합 분리 모드에 해당하며 배포수준의 컴포넌트는 동일한 프로세서와 주소공간에 위치하므로 단일체와 동일하다. 그래서 관리하는 전략도 단일체와 동일하다.

스레드

모든 스레드가 단 하나의 컴포넌트에 포함될 수도 있고, 많은 컴포넌트에 걸쳐 분산될 수도 있다.

로컬 프로세스

  • 훨씬 강한 물리적 형태를 띠는 아키텍처 경계. 대게 소켓이나 메일박스, 메시지 큐와 같이 운영체제에서 제공하는 통신기능을 이용하여 서로 통신한다.
  • 로컬 프로세스를 일종의 최상위 컴포넌트라고 생각하자. 즉, 로컬 프로세스는 컴포넌트간 의존성을 동적 다형성을 통해 관리하는 저수준 컴포넌트로 구성된다.
  • 로컬 프로세스간 분리 전략은 항상 고수준 컴포넌트를 향한다. 따라서 로컬 프로세스에서는 고수준 프로세스의 소스 코드가 저수준 프로세스의 이름, 물리 주소, 레지스트리 조회 키를 절대로 포함해서는 안된다. 저수준 프로세스가 고수준 프로세스의 플러그인이 되도록 만드는 것이 아키텍처의 관점의 목표라는 사실을 기억하자

서비스

  • 물리적인 형태를 띠는 가장 강력한 경계는 바로 서비스다. 서비스는 자신의 물리적 위치에 구애받지 않는다. 서비스들은 모든 통신이 네트워크를 통해 이뤄진다고 가정한다.
  • 통신은 함수 호출에 비해 매우 느리다. 그래서 주의를 기울여서 빈번하게 통신하는 일을 피해야 한다. 이를 제외하고는 로컬 프로세스에 적용한 규칙이 서비스에도 그대로 적용된다.

결론

  • 단일체를 제외한 대다수의 시스템은 한 가지 이상의 경계 전략을 사용한다. 실제로 서비스는 상호작용하는 일련의 로컬 프로세스 퍼사드에 불과할 때가 많다.
  • 즉, 대체로 한 시스템 안에서도 통신이 빈번한 로컬 경계와 지연을 중요하게 고려해야 하는 경계가 혼합되어 있음을 의미한다.

19장 정책과 수준

  • 소프트웨어 시스템이란 정책을 기술한 것이다.
  • 소프트웨어 아키텍처를 개발하는 기술에는 이러한 정책을 신중하게 분리하고 정책이 변경되는 양상에 따라 정책을 재편성하는 일도 포함된다.
    • 동일한 이유로 동일한 시점에 변경되는 정책은 동일한 수준에 위치하며, 동일한 컴포넌트에 속해야 한다.
    • 서로 다룬 이유로 혹은 다른 시점에 변경되는 정책은 다른 수준에 위치하며, 반드시 다른 컴포넌트로 분리해야 한다.
    • 흔히 아키텍처 개발은 재편성된 컴포넌트들을 비순환 방향 그래프로 구성하는 기술을 포함한다

수준

  • 수준은 입력과 출력까지의 거리다. 시스템의 입력과 출력 모두 멀리 위치할수록 정책의 수준은 높아진다.
    • 입력과 출력을 다루는 정책이라면 시스템에서 최하위 수준에 위치한다.

  • 번역 컴포넌트는 최고 수준의 컴포넌트인데 입력과 출력으로부터 떨어져있기 때문
  • 데이터 흐름과 소스코드 의존성이 항상 같은 방향을 가리키지는 않는다. 소스코드 의존성은 그 수준에 따라 결합되어야 하며, 데이터 흐름을 기준으로 결합되어서는 안된다

  • 점선으로 된 경계를 횡단하는 의존성은 모두 경계 안쪽으로 향한다. 경계로 묶인 영역이 이 시스템에서 최고 수준의 구성요소다
  • Console Reader, Console Writer는 입력과 출력에 가깝기 때문에 저수준이다
  • 정책을 컴포넌트로 묶는 기준은 정책이 변경되는 방식에 달려있다
    • 단일 책임 원칙(SRP)과 공통 폐쇄 원칙(CCP)에 따르면 동일한 이유로 동일한 시점에 변경되는 정책은 함께 묶인다
    • 고수준 정책은 덜 빈번하게 변경하되 중요한 이유로 변경되는 편이고
    • 저수준 정책은 빈번하게 변경하고 덜 중요한 이유로 변경된다.
  • 이처럼 모든 소스코드 의존성의 방향이 고수준 정책을 향할 수 있도록 정책을 분리했다면 변경의 영향도를 줄일 수 있다.

20장 업무 규칙

  • 업무규칙은 사업적으로나 수익을 얻거나 비용을 줄일 수 있는 규칙 또는 절차다.
    • ex) 대출에 N%이자를 부과한다는 것. 이러한 규칙을 핵심 업무규칙 Critical Business Rule이라 부른다.
  • 핵심규칙과 핵심 데이터는 본질적으로 결합되어있기 때문에 이러한 유형의 객체를 엔티티 부른다.

엔티티

  • 컴퓨터 시스템 내부의 객체로서, 핵심 업무 데이터를 기반으로 동작하는 일련의 조그만 핵심업무 규칙을 구체화한다.
  • 엔티티 객체는 핵심 업무 데이터를 직접 포함하거나 핵심 업무 데이터에 매우 쉽게 접근할 수 있다.
  • 업무에서 핵심적인 개념을 구현하는 소프트웨어는 한데 모으고, 구축중인 자동화 시스템의 나머지 모든 고려사항과 분리시킨다.
  • 예를들어 데이터베이스, 사용자 인터페이스, 서드 파티 프레임워크에 대한 고려사항들로 인해 오염되어서는 절대 안된다.
  • 어떤 시스템에서도 업무를 수행할 수 있으며, 시스템의 표현 형식이나 데이터 저장방식, 해당 시스템에서 컴퓨터가 배치되는 방식과도 무관하다.
  • 엔티티는 순전히 업무에 대한 것이며, 이외의 것은 없다

유스케이스

  • 엔티티내의 핵심 업무 규칙과는 반대로, 유스케이스는 애플리케이션에 특화된 업무 규칙을 설명한다
  • 유스케이스는 엔티티 내부의 핵심업무 규칙을 어떻게, 그리고 언제 호출할지를 명시하는 규칙을 담는다. 엔티티가 어떻게 춤을 출지를 유스케이스가 제어하는 것이다
  • 또한 유스케이스만 봐서는 이 애플리케이션이 웹을 통해 전달되는지, 리치 클라이언트인지 등등 구분하기란 어렵다
    • 사용자에게 어떻게 보이는지보다 애플리케이션에 특화된 규칙을 설명하며 이를통해 사용자와 엔티티 사이의 상호작용을 규정한다.
    • 엔티티는 자신을 제어하는 유스케이스에 대해 아무것도 알지 못한다.
      • 엔티티는 고수준이고 유스케이스는 저수준인데 그 이유는 유스케이스는 해당 시스템의 입력과 출력에 보다 가깝게 위치하기 때문이다

요청 및 응답 모델

  • 의존성을 제거하는 일은 중요하다. 요청 및 응답 모델이 독립적이지 않다면 그 모델에 의존하는 유스케이스도 결국 해당 모델이 수반하는 의존성에 간접적으로 결합되어 버린다.

결론

  • 업무 규칙은 사용자 인터페이스나 데이터베이스와 같은 저수준의 관심사로 인해 오염되어서는 안되며, 원래 그대로의 모습으로 남아있어야 한다.
  • 이상적으로는 업무 규칙을 표현하는 코드는 반드시 시스템의 심장부에 위치해아 하며, 덜 중요한 코드는 이 심장부에 플러그인되어야 한다.
  • 업무 규칙은 시스템에서 가장 독립적이며, 가장 많이 재사용할 수 있는 코드여야 한다.

21장 소리치는 아키텍처

아키텍처의 테마

  • 주택이나 도서관의 계획서가 해당 건축물의 유스케이스에 대해 소리치는 것처럼, 소프트웨어 애플리케이션의 아키텍처도 애플리케이션의 유스케이스에 대해 소리쳐야 한다.
  • 아키텍처는 프레임워크에 대한 것이 아니다

아키텍처의 목적

  • 좋은 아키텍처는 유스케이스를 그 중심에 두기 때문에 프레임워크나 도구 환경에 전혀 구애받지 않고 유스케이스를 지원하는 구조를 아무런 문제 없이 기술할 수 있다.

하지만 웹은?

  • 웹은 전달 매커니즘이므로 웹을 통해 전달된다는 사실은 세부사항이다. 시스템 구조를 절대 지배해서는 안된다.

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

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

테스트하기 쉬운 아키텍처

  • 아키텍처가 유스케이스를 최우선으로 한다면, 그리고 프레임워크와는 적당한 거리를 둔다면, 프레임워크를 전혀 준비하지 않더라도 필요한 유스케이스 전부에 대해 단위테스트를 할 수 있어야 한다.
  • 테스트를 돌리는데 웹서버가 반드시 필요한 상황이 되어서는 안된다. 데이터베이스 연결도 안된다. 프레임워크로 인한 어려움을 겪지 않고도 반드시 이 모두를 있는 그대로 테스트 할 수 있어야 한다.

결론

새로 합류한 프로그래머는 시스템이 어떻게 전달될지 알지 못한 상태에서도 시스템의 모든 유스케이스를 이해할 수 있어야 한다.

22장 클린 아키텍처

  • 육각형 아키텍처(Hexagonal Architecture), DCI(Data, Context and Interaction), BCE 이들 아키텍처의 공통된 부분은 관심사의 분리다. 소프트웨어를 계층으로 분리함으로써 관심사의 분리라는 목표를 달성할 수 있었다.
  • 또한 다음과 같은 특징을 지니도록 만든다
    • 프레임워크 독립성
    • 테스트용이성
    • UI독립성
    • 데이터베이스 독립성
    • 실제로 업무규칙은 외부 세계와의 인터페이스에 대해 전혀 알지 못한다.

  • 이 다이어그램은 이들 아키텍처를 전부 실행 가능한 하나의 아이디어로 통합하려는 시도다.

의존성 규칙

  • 소스코드 의존성은 반드시 안쪽으로, 고수준의 정책을 향해야 한다.
  • 각각의 동심원은 소프트웨어에서 서로 다른 영역을 표현한다. 안으로 들어갈수록 고수준의 소프트웨어가 된다. 바깥쪽 원은 매커니즘이고 안쪽 원은 정책이다.
  • 내부의 원에 속한 요소는 외부의 원에 속한 어떤 것도 알지 못한다.

엔티티

  • 전사적인 핵심 업무 규칙을 캡슐화한다.
  • 운영 관점에서 특정 애플리케이션에 무언가 변경이 필요하더라도 엔티티 계층에느 절대로 영향을 주어서는 안된다

유스케이스

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

인터페이스 어댑터

  • 어댑터는 데이터를 유스케이스와 엔티티에게 가장 편리한 형식에서 데이터베이스나 웹같은 외부 에이전시에게 가장 편리한 형식으로 변환한다.
  • Presenter, View, Controller는 모두 인터페이스 어댑터 계층에 속한다.
  • 모델은 그저 데이터 구조 정도에 지나지 않으며, 컨트롤러에서 유스케이스로 전달되고, 다시 유스케이스에서 프레젠터와 뷰로 되돌아간다.
  • 또한 이 계층에는 데이터를 외부 서비스와 같은 외부적인 형식에서 유스케이스나 엔티티에서 사용되는 내부적인 형식으로 변환하는 또 다른 어댑터가 필요하다.

프레임워크와 드라이버

  • 이 계층에서는 안쪽 원과 통신하기 위한 접합 코드 외에는 특별히 더 작성해야 할 코드가 그다지 많지 않다.

원은 네개여야만 하나?

  • 항상 네 개만 사용해야 한다는 규칙은 없지만 어떤 경우에도 의존성 규칙은 적용된다.

경계 횡단하기

  • 그림 우측 하단에서처럼 의존성 역전 원칙을 사용하여 경계를 횡단한다.
    • 직접 호출해버리면 내부의 원에서는 외부 원에 있는 어떤 이름도 언급해서는 안되기 때문에 위배된다.

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

  • 경계를 가로지르는 데이터는 흔히 간단한 데이터 구조로 이루어져있다. 하지만 외부에서 쓰는 데이터 포멧을 내부의 원에서 알면 안되기 때문에 항상 내부의 원에서 사용하기에 가장 편리한 형태를 가져야만 한다.

결론

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

23장 프레젠터와 험블 객체

험블 객체 패턴

  • 험블객체패턴은 디자인 패턴으로 테스트하기 어려운 행위와 테스트하기 쉬운 행위를 단위 테스트 작성자가 분리하기 쉽게 하는 방법으로 고안되었다.
  • 구현방법은 행위들을 모두 두개의 클래스로 나눈 후 테스트하기 어려운 행위를 험블 객체에 옮기고 나머지는 다른 객체에 넣는다.

프레젠터와 뷰

  • 뷰는 험블 객체이고 테스트하기 어렵다. 데이터를 GUI로 이동시키지만 데이털르 직접 처리하지는 않는다
  • 프레젠터는 테스트하기 쉬운 객체다. 애플리케이션으로부터 데이터를 받아 화면에 표현할 수 있는 포맷으로 만드는 것이다.
    • ex: Data 객체를 적절한 포맷의 문자열로 만드는 것

테스트와 아키텍처

  • 테스트 용이성은 좋은 아키텍처가 지녀야 할 속성으로 오랫동안 알려져 왔다.
  • 험블 객체 패턴이 좋은 예인데 아키텍처 경계가 정의되기 때문이다. (뷰 / 프레젠터)

데이터베이스 게이트웨이

  • 유스케이스 인터랙터와 데이터 베이스 사이에는 데이터베이스 게이트웨이가 위치한다.
  • 데이터베이스는 험블객체이다. 인터랙터는 애플리케이션에 특화된 업무 규칙을 캡슐화하기때문에 험블 객체가 아니다. 그러므로 테스트하기 용이하다.
    • 이는 스텁(stub)이나 테스트 더블(test-double)로 적당히 교체할 수 있다.

데이터 매퍼

  • 객체 관계 매퍼 같은건 사실 존재하지 않는다. 객체는 데이터구조가 아니기 때문이다.
  • 객체와 달리 데이터 구조는 함축된 행위를 가지지 않는 public 데이터 변수의 집합이다. ORM 보다는 데이터 매퍼라고 부르는 편이 나은데 관계형 데이터 베이스 테이블로부터 가져온 데이터를 데이터 구조에 맞게 담아주기 때문이다.
  • ORM 시스템은 데이터 베이스 계층에 위치해야 한다.

서비스 리스너

  • 서비스의 경우에도 서비스 경계를 생성하는 험블 객체 패턴을 발견할 수 있다.
  • 데이터 구조를 경계를 가로질러서 특정 모듈로 전달한다. 그러면 해당 모듈은 데이터를 적절한 포맷으로 만들어서 외부 서비스로 전송한다. 해당 모듈은 데이터를 적절한 포맷으로 만들어서 외부 서비스로 전송한다.

결론

각 아키텍처 경계마다 경계 가까이 숨어 있는 험블 객체 패턴을 발견할 수 있다. 그 경계는 테스트하기 어려운 무언가와 테스트하기 쉬운 무언가로 분리되겠다. 이로인해 테스트의 용이성을 크게 높힐 수 있다.

24장 부분적 경계

아키텍처 경계를 완벽하게 만드는 데는 비용이 많이 든다. 비용이 너무 크다고 판단되면 부분적 경게를 구현해볼수 있다.

마지막 단계를 건너뛰기

  • 컴파일하고 배포할 수 있는 컴포넌트를 만들고 단일 컴포넌트에 그대로 모아만 두는 것이다. 쌍방향 인터페이스도 그 컴포넌트에 있고 입력 출력 데이터. 구조도 거기에 있으며 모든것이 완전히 준비되어 있다.
  • 하지만 완벽한 경계와 사전 설계가 충분히 필요하다.

일차원 경계

  • 추후 완벽한 형태의 경계로 확장할 수 있는 공간을 확보하고자 할 때 Strategy 패턴을 사용한 전형적인 사례다.
    • ServiceBoundary 인터페이스는 클라이언트가 사용하며 ServiceInpl 클래스가 구현한다.
  • 이러한 분리는 빨간 점선처럼 오용해서는 안되도록 주의가 필요하다

퍼사드

  • 이보다 단순한 경계는 퍼사드 패턴으로 Facade 클래스에는 모든 서비스 클래스를 메서드 형태로 정의하고 서비스 호출이 발생하면 해당 서비스 클래스로 호출을 전달한다.
  • 클라이언트는 이들 서비스에 직접 접근할 수 없지만 Client가 이 모든 서비스 클래스에 대해 추이 종속성을 가지게 된다.

결론

아키텍처 경계가 언제, 어디에 존재해야 할지, 그리고 그 경계를 완벽하게 구현할지 아니면 부분적으로 구현할지를 결정하는 일 또한 아키텍트의 역할이다.

25장 계층과 경계

시스템이 세가지 컴포넌트(UI, 업무 규칙, 데이터베이스)로만 구성되지는 않을 수 있다.

  • 게임 규칙, 게임의 예제에서 중요한 아키텍처 경계를 정말로 모두 발견한 것일까? 예를 들어 UI에서 언어가 유일한 변경의 축은 아니고 텍스트를 주고받는 매커니즘을 다양하게 만들고 싶을 수도 있다. 이 변경의 축에 의해 정의되는 아키텍처 경계가 잠재되어 있을 수 있다.

흐름 분리하기

  • 항상 상단의 단일 컴포넌트에서 서로 만나지는 않는다.
  • 이보다 더 높은 수준에는 또다른 정책 집합이 존재하는데 이는 여러 경우의 시나리오를 간략하해 묘사하면 아키텍처의 경계를 발견할 수 있다.

결론

  • 아키텍처 경계가 어디에나 존재한다. 우리는 경계가 언제 필요한지를 신중하게 파악하면 된다.
  • 이러한 경계를 제대로 구현하려면 비용이 많이 든다는 것도 알아야한다.
    • 하지만 무시한다면 다시 추가하는 비용이 더 커진다.
  • YAGNI(You Aren't Going to Need It). 오버엔지니어링이 언더 엔지니어링보다 나쁠 때가 훨씬 많다.
  • 다른 한편으로는 어떤 아키텍처 경계도 존재하지 않는 상황에서 경계가 정말로 필요하다는 사실을 발견한 후 그때서야 경계를 추가하려면 비용이 많이 들고 큰 위함을 감수해야 한다.
  • 목표를 달성하려면 빈틈없이 지켜봐야 한다.

26장 메인(Main) 컴포넌트

모든 시스템에는 최소한 하나의 컴포넌트가 존재하고 이 컴포넌트가 나머지 컴포넌트를 생성하고 조정하며 관리한다. 이것을 메인(Main) 컴포넌트라 부른다.

궁극적인 세부사항

  • 메인 컴포넌트는 궁극적인 세부사항으로 가장 낮은 수준의 정책이다. 메인은 시스템의 초기 진입점이다.
  • 메인은 모든 팩토리와 전략, 그리고 시스템 전반을 담당하는 나머지 기반 설비를 생성한 후 시스템에서 더 높은 수준을 담당하는 부분으로 제어권을 넘기는 역할을 맡는다.
  • 의존성 주입 프레임워크를 이용해 의존성을 주입하는 일은 바로 이 메인 컴포넌트에서 이뤄져야 한다. (???)
  • 메인은 클린 아키텍처에서 가장 바깥 원에 위치하는 지저분한 저수준 모듈이라는 점이다.
    • 메인은 고수준의 시스템을 위한 모든것을 로드한 후 제어권을 고수준의 시스템에게 넘긴다.
  • 메인을 애플리케이션의 플러그인이라고 생각하자
    • 메인을 플러그인 컴포넌트로 여기고, 그래서 아키텍처 경계 바깥에 위치한다고 보면 설정 관련 문제를 훨씬 쉽게 해결할 수 있다.

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

  • 시스템의 아키텍처는 의존성 규칙을 준수하며 고수준의 정책을 저수준의 세부사항으로부터 분리하는 경계에 의해 정의된다.
    • 단순히 애플리케이션의 행위를 분리할 뿐인 서비스라면 값비싼 함수 호출에 불과하며, 아키텍처 관점에서 꼭 중요하다고는 볼 수 없다.
  • 결국 서비스는 프로세스나 플랫폼 경계를 가로지르는 함수 호출에 지나지 않는다. 아키텍처적으로 중요한 서비스도 있지만, 중요하지 않은 서비스도 존재한다. 이 장에서는 전자인 관점에서 본다.

서비스의 이점

결합 분리의 오류

  • 큰 이점 하나는 서비스 사이의 결합이 확실히 분리된다는 점이다. 대신 서비스는 서로 다른 프로세스에서, 직접 변수에 접근할 수 없고, 인터페이스는 잘 정의되어있어야 한다.
    • 하지만 프로세서 내의 또는 네트워크 상의 공유 자원 때문에 결합 될 가능성이 여전히 존재한다.

개발 및 배포 독립성의 오류

  • 서비스를 사용함에 따라서 갖는 이점 중 또 하나는 전담팀에서 각 서비스를 작성하고 유지보수하며 운영하는 책임을 질 수 있다.
    • 이러한 개발 및 배포 독립성은 확장 가능한 것으로 간주된다.
    • 하지만 극히 일부이다.
      • 대규모 엔터프라이즈 시스템은 서비스 기반 시스템 이외에도 모노리틱 시스템이나 컴포넌트 기반 시스템으로도 구축할 수 있다는 사실은 역사적으로 증명되어왔다.
      • 데이터나 행위에서 어느정도 결합되어있다면 결합된 정도에 맞게 개발, 배포, 운영을 조정해야만 한다.

야옹이 문제

이전 9장 택시 통합 시스템 예시를 다시 가지고와서 확장 가능한시스템을 구축하고싶었기에 수많은 작은 마이크로 서비스를 기반으로 구축하기로 결정했다.

  • 이 서비스 다이어그램대로 서비스를 운영하다가 야옹이 서비스를 시작한다면 택시 업체 한곳이 추가로 들어가고 세부적인 필터를 적용해 배차를 해야한다는 등 추가, 변경 기능이 있게 된다면 거의 전부를 변경해야 한다.
    • 다시말해 이 서비스들은 모두 결합되어 있어서 독립적으로 개발하고 배포하거나 유지될 수 없다.
    • 이게 바로 횡단 관심사가 지닌 문제다. 모든 소프트웨어 시스템은 서비스 지향이든 아니든 이 문제에 직면하게 마련이다.
    • 이렇게 묘사괸것과 같은 종류의 기능적 분해는 새로운 기능이 기능적 행위를 횡당하는 상황에 매우 취약하다

객체가 구출하다

  • 컴포넌트 기반 아키텍처에서는 SOLID 설계 원칙을 적용하는데, 다형적으로 확장할 수 있는 클래스 집합을 생성해 새로운 기능을 처리하도록 함을 알 수 있다

  • 경계와 의존성 규칙을 준수하는것을 볼 수 있다.
  • Rides / Kittens 컴포넌트로 나누고 기존 컴포넌트들에 있는 추상 기반 클래스를 템플릿 메서드나 전략 패턴 등을 이용해서 오버라이드 한다.
  • 이 전략을 따르더라도 야옹이 기능을 구현하려면 TaxiUI는 어쩔수 없이 변경해야만 하지만 그 외의 것들은 변경할 필요가 없다.
  • 즉 야옹이 기능은 결합이 분리되며, 독립적으로 개발하여 배포할 수 있다.

컴포넌트 기반 서비스

  • 서비스에도 컴포넌트 기반 서비스를 만들수 있다. 서비스가 소규모 단일체일 필요는 없다.

  • 이전 예에 이어가면서 서비스들의 존재는 이전과 달라진게 없지만 각 서비스의 내부는 자신만의 컴포넌트 설계로 되어 있어서 파생 클래스를 만드는 방식으로 신규 기능을 추가할 수 있다. 파생 클래스들은 각자의 컴포넌트 내부에 놓인다.

횡단 관심사

  • 아키텍처 경계가 서비스 사이에 있지 않다는 사실이다. 오히려 서비스를 관통하며, 서비스를 컴포넌트 단위로 분할한다.
  • 횡단 관심사를 처리하려면 아래 다이어그램에서 보듯이, 서비스 내부는 의존성 규칙도 준수하는 컴포넌트 아키텍처로 설계해야 한다.
  • 아키텍처 경계를 정의하는 것은 서비스 내에 위치한 컴포넌트다.

결론

  • 서비스는 시스템의 확장성과 개발 가능성 측면에서 유용하지만 그 자체로는 아키텍처적으로 그리 중요한 요소는 아니다.
  • 시스템의 아키텍처는 시스템 내부에 그어진 경계와 경계를 넘나드는 의존성에 의해 정의된다.

28장 테스트 경계

  • 테스트는 태생적으로 의존성 규칙을 따른다.
  • 테스트는 세부적이며 구체적인것으로 의존성은 항상 테스트 대상이 되는 코드를 향한다.
  • 테스트는 독립적으로 배포 가능하다. 배포독립성이 달리 필요하지 않은 시스템에서도 테스트는 독립적으로 배포될 것이다.
  • 테스트는 시스템 컴포넌트 중에서 가장 고립되어 있다. 테스트가 시스템 운영에 꼭 필요치는 않다.

테스트를 고려한 설계

  • 테스트가 시스템의 설계와 잘 통합되지 않으면, 테스트는 깨지기 쉬워지고 시스템은 뻣뻣해져서 변경하기가 어려워진다.
    • 물론 문제는 결합이다. 시스템에 강하게 결합된 테스트라면 시스템이 변경됐을때 테스트를 망가트린다.
    • 깨지기 쉬운 테스트는 시스템을 뻣뻣하게 만든다는 부작용을 낳을때가 많다.
  • 테스트를 고려해서 설계해야 한다. 이는 소프트웨어 설계의 첫번째 규칙인 변동성이 있는 것에 의존하면 안된다

테스트 API

  • 이 목표를 달성하려면 테스트가 모든 업무 규칙을 검증하는데 사용할 수 잇도록 특화된 API를 만들면 된다
  • 테스트 API는 테스트를 애플리케이션으로부터 분리할 목적으로 사용한다.
  • 단순히 테스트를 UI에서 분리하는것만이 아닌, 테스트 구조를 애플리케이션 구조로부터 결합을 분리하는게 목표다

구조적 결합

  • 상용클래스나 메서드 중 하나라도 변경되면 딸려있는 다수의 테스트가 변경되어야 한다. 결과적으로 테스트는 깨지기 쉬워지고, 이로 인해 상용 코드를 뻣뻣하게 만든다.
  • 테스트 API의 역할은 애플리케이션의 구조를 테스트로부터 숨기는데 있다.
    • 이렇게 만들면 상용 코드를 리팩터링하거나 진화시키더라도 테스트에는 전혀 영향을 주지 않는다.
    • 또한 테스트를 리팩터링하거나 진화시킬때도 상용 코드에는 전혀 영향을 주지 않는다.
    • 테스트는 계속해서 더 구체적으로 변하고 상용코드는 더 추상적으로 변할것이다.
  • 하지만 구조적 결합이 강하면 필수적인 진화 과정을 방해하고 상용코드의 범용성과 유연성이 충분히 좋아지지 못하게 한다.

보안

배포 시 위험을 피하고 싶으면 테스트 API 자체와 테스트 API 중 위험한 부분의 구현부는 독립적으로 배포할 수 있는 컴포넌트로 분리해야 한다.

결론

  • 테스트는 시스템의 일부다. 일부로 생각하지 않으면 깨지기 쉽고 유지보수하기 어려워지는 경향이 있다.

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

더그 슈미트는 이렇게 말했다.

소프트웨어는 닳지 않지만, 펌웨어와 하드웨어는 낡아 가므로 결국 소프트웨어도 수정해야 한다

소프트웨어는 긴시간 유용하게 쓸수 잇는 것인 반면 펌웨어는 하드웨어가 발전할수록 낡아갈것이다.

그래서 저자가 재정의한다면

소프트웨어는 닳지 않지만, 펌웨어와 하드웨어에 대한 의존성을 관리하지 않으면 안으로부터 파괴될 수 있다.

안드로이드 앱 개발자 역시 업무 로직을 안드로이드 API로부터 분리하지 않는다면 펌웨어를 작성하는 셈이다

엔지니어와 프로그래머에게 전하는 메시지는 분명하다. 펌웨어를 수없이 양산하는 일을 멈추고, 코드에게 유효 수명을 길게 늘릴 수 있는 기회를 주어라.

앱-티튜드 테스트

켄트 벡은 소프트웨어를 구축하는 세가지 활동을 다음과 같이 기술했다. 기술 옆에는 저자의 해설이 덧붙여졌다.

먼저 동작하게 만들어라 - 소프트웨어가 동작하지 않는다면 사업은 망한다

그리고 올바르게 만들어라 - 코드를 리팩터링해서 당신을 포함한 나머지 사람들이 이해할 수 있게 만들고, 요구가 변경되거나 요구를 더 잘이해하게 되었을 때 코드를 개선할. 수 있게 만들어라

그리고 빠르게 만들어라 - 코드를 리팩터링해서 '요구되는' 성능을 만족시켜라

  • 현장에서 지켜본 수많은 임베디드 시스템 소프트웨어는 동작하게 하라는 활동만 염두해두고 작성된 것처럼 보인다.
  • 앱이 동작하도록 만드는 것을 개발자용 앱-티튜드 테스트라고 부른다.
    • 하나의 예시로 특정 임베디드 장치에서만 테스트할 수 잇음을 암시하는 파일 구조를 가지고 있고 특수한 마이크로프로세서 아키텍처에서 동작하게 된다면 앱-티튜드 테스트를 통과했지만 클린 임베디드 아키텍처를 가진다고 말하기는 어렵다.

타깃-하드웨어 병목현상

  • 임베디드가 지닌 특수한 문제 중 하나는 타깃-하드웨어 병목현상이다. 임베디드 코드가 클린 아키텍처 원칙과 실천법을 따르지 않고 작성된다면, 대개의 경우 코드를 테스트할 수 잇는 환경이 해당 특정 타깃으로 국한될 것이다. 그리고 그 타깃이 테스트가 가능한 유일한 장소라면 타깃-하드웨어 병목현상이 발생하여 진척이 느려질 것이다

클린 임베디드 아키텍처는 테스트하기 쉬운 임베디드 아키텍처다.

계층

  • 계층은 여러가지가 잇지만 세개의 계층부터 시작한다면 하드웨어가 맨 아래 위치한다. 이전에도 말했듯이, 하드웨어는 점점 낡아간다

  • 최소한 하드웨어가 정의된 이후라면 하드웨어와 나머지 시스템 사이의 분리는 주어진다. 무엇이 어디에 위치하고, 한 모듈이 다른 모듈에 대해 어디까지 알게 할지 신중하게 처리하지 않는다면 완성된 코드는 변경하기가 매우 어렵게 된다.

  • 소프트웨어와 펌웨어가 서로 섞이는 일은 안티패턴이다. 변경하기 어려울 뿐만 아니라 변경하는 일 자체가 위험을 수반한다.

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

  • 그림에서 보듯이 소프트웨어와 펌웨어 사이의 경계와는 달리 잘 정의하기가 대체로 힘들다.

  • 그래서 이 경계를 분명하게 만드는 것인 하드웨어 추상화 계층(Hard-ware Abstraction Layer, HAL)을 둔다.
  • HAL은 자신보다 위에 있는 소프트웨어를 위해 존재하므로, HAL의 API는 소프트웨어의 필요에 맞게 만들어져야 한다.
  • 펌웨어는 바이트 또는 바이트의 배열을 플래시 메모리에 저장할 수 있는데 소프트웨어는 그게 플래시메모리에 저장되는지, 하드디스크에 저장되는지 클라우드에 저장되는지 전혀 개의치 않는다

HAL 사용자에게 하드웨어 세부사항을 드러내지 말라

  • HAL을 제대로 만들었다면, HAL은 타깃에 상관없이 테스트할 수 있는 경계층 또는 일련의 대체 지점을 제공한다.
  • 클린 임베디드 아키텍처라면 이들 장치 접근 레지스터를 직접 사용하는 코드는 소수의, 순전히 펌웨어로만 한정시켜야 한다.
    • 이들 레지스터를 알고 있는 것은 모두 펌웨어가 되어야 하며, 실리콘 칩에 종속되고 코드를 프로세서와 직접적으로 묶어버리면 안정적인 하드웨어가 출시되기 이전에 코드를 실행시키고자 할 때 어려움을 겪을 수 있다.
    • 이러한 마이크로 컨트롤러를 사용할 때 저수준 함수들을 프로세서 추상화 계층(Processor Abstraction Layer, PAL)의 형태로 격리시켜줄 수 있다.
    • PAL 상위에 위치하는 펌웨어는 타깃-하드웨어에 관계없이 테스트할 수 있게 되어 펌웨어 자체도 덜 딱딱해질 수 있다.
운영체제는 세부사항이다.
  • 임베디드 시스템에서 실시간 운영체제를 사용하거나 임베디드 버전의 리눅스 / 윈도우를 사용한다면 운영체제를 세부사항으로 취급하고 운영체제에 의존하는 일을 막아야 한다

  • 소프트웨어는 운영체제를 통해 운영환경이 제공하는 서비스에 접근한다.
  • OS는 소프트웨어를 펌웨어로부터 분리하는 계층이다.
    • OS를 직접 사용하면 문제가 될 가능성이 높다. 새로운 OS가 제공하는, 이전 OS와는 다른 기능과 시스템 명령어에 맞게 그 의미 자체를 조정해야 할 가능성이 높다.
  • 클린 임베디드 아키텍처는 운영체제 추상화 계층(Operating System Abstraction Layer, OSAL)을 통해 소프트웨어를 운영체제로부터 격리시킨다.
    • OSAL의 이식작업도 비용이 큰 편이지만 복잡한 코드 덩어리를 수정하는 것보단 낫다.
    • OSAL은 테스트 지점을 만드는 데에 도움이 된다.

인터페이스를 통하고 대체 가능성을 높이는 방향으로 프로그래밍하라

  • 계층형 아키텍처(Layered Architecture)는 인터페이스를 통해 프로그래밍 하자는 발상을 기반으로 한다.
    • 모듈들이 서로 인터페이스를 통해 상호작용한다면 특정 서비스 제공자를 다른 제공자로 대체할 수 있다.
  • 구현 세부사항은 변경될 거라고 가정하라. 세부사항을 아고 있는 부분이 적을수록 추적하고 변경해야 할 코드도 적어진다.
  • 클린 임베디드 아키텍처에서는 모듈들이 인터페이스를 통해 상호작용하기 때문에 각각의 계층 내부에서 테스트가 가능하다.

결론

모든 코드가 펌웨어가 되도록 내버려두면 제품이 오래 살아남을 수 없게 된다. 오직 타깃 하드웨어에서만 테스트할 수 있는 제품도 마찬가지다.


클린 아키텍처 소프트웨어 구조와 설계의 원칙
로버트 C. 마틴 저

http://www.yes24.com/Product/Goods/77283734

profile
step by step

0개의 댓글