728x90
반응형
책 정보
https://www.yes24.com/Product/Goods/77283734
15장 아키첵처란?
- 소프트웨어 아키텍처
- 소프트웨어 아키텍트 = 프로그래머
- 코드와 동떨어져서는 안됨
- 발생하는 문제를 경험해보지 않는다면 다른 프로그래머를 지원하는 작업을 제대로 수행할 수 없기 때문
- 아키텍처란 시스템을 구축했던 사람들이 만들어낸 시스템의 형태
- 이러한 일을 용이하게 만들기 위해서는 가능한 한 많은 선택지를, 가능한 한 오래 남겨두는 전략을 따라야 함
- 시스템 아키텍처와 시스템 동작 여부는 관련 X
- 대체로 운영에서는 문제 없음
- 다만, 배포, 유지보수, 계속되는 개발 과정에서 어려움을 겪음
- 아키텍처의 주된 목적은 시스템의 생명주기를 지원하는 것
- 좋은 아키텍처
- 시스템을 쉽게 이해
- 쉽게 개발
- 쉽게 유지보수
- 쉽게 배포
- 목표 : 시스템의 수명과 관련된 비용은 최소화하고, 프로그래머의 생산성은 최대화
- 개발
- 소규모의 팀 -> 아키텍처 관련 제약들이 방해가 된다고 여길 가능성이 높음
- 수많은 시스템에서 아키텍처가 결여된 이유는 바로 이 때문
- 대규모의 팀 -> 잘 설계된 컴포넌트 단위로 분리하지 않으면 개발이 진척되지 않음
- 배포
- 배포 비용이 높을수록 시스템의 유용성은 떨어짐
- 따라서 소프트웨어 아키텍처는 시스템을 단 한 번에 쉽게 배포할 수 있도록 만드는 데 그 목표를 두어야 함
- 안타깝지만 초기 개발 단계에서는 배포 전략을 거의 고려하지 않음 -> 개발하기 쉬울진 몰라도 배포하기는 어려운 아키텍처가 만들어짐
- 운영
- 아키텍처가 시스템 운영에 미치는 영향은 개발, 배포, 유지보수에 미치는 영향보다는 덜 극적임
- 운영에서의 어려움은 단순히 하드웨어를 더 투입해서 해결할 수 있음
- 유지보수
- 모든 측면에서 봤을 때 비용이 가장 많이 듦
- 새로운 기능은 끝도없이 발생
- 결함은 피할 수 없음
- 결함을 수정하는 데도 엄청난 인적 자원 소모
- 유지보수의 가장 큰 비용은 탐사와 이로 인한 위험부담
- 탐사 : 기존 소프트웨어에 새로운 기능을 추가하거나 결함을 수정할 때, 소프트웨어를 파헤쳐허 어디를 고치는 게 최선인지, 그리고 어떤 전략을 쓰는 게 최적일지를 결정할 때 드는 비용
- 신중하게 아키텍처를 만들면 이 비용을 크게 줄일 수 있음
- 시스템을 컴포넌트로 분리하고, 안정된 인터페이스를 두어 서로 격리
- 이를 통해 미래에 추가될 기능에 대한 길을 밝혀 둘 수 있음
- 모든 측면에서 봤을 때 비용이 가장 많이 듦
- 선택사항 열어 두기
- 소프트웨어의 두 가지 가치
- 행위적 가치
- 구조적 가치
- 구조적가치가 더 중요 (소프트웨어를 부드럽게 만들기 때문)
- 소프트웨어를 만든 이유 : 기계의 행위를 빠르게 쉽게 변경하는 방법이 필요했기 때문
- 하지만 이러한 유연성은 시스템의 형태, 컴포넌트의 배치 방식, 컴포넌트가 상호 연결되는 방식에 상당히 크게 의존
- 소프트웨어를 부드럽게 유지하는 방법은 선택사항을 가능한 한 많이, 그리고 가능한 한 오랫동안 열어 두는 것
- 열어 둬야 할 선택사항? -> 중요치 않은 세부사항
- 모든 소프트웨어 시스템은 정책과 세부사항으로 분해됨
- 정책 : 모든 업무 규칙과 업무 절차를 구체화, 시스템의 진정한 가치가 살아 있는 곳
- 세부사항 : 사람, 외부 시스템, 프로그래머가 정책과 소통할 때 필요한 요소
- 입출력 장치, 데이터베이스, 웹 시스템 서버, 프레임워크, 통신 프로토콜 등
- 아키텍트의 목표는 시스템에서 정책을 가장 핵심적인 요소로 식별하고, 동시에 세부사항은 정책에 무관하게 만들 수 있는 형태의 시스템을 구축하는 것
- 이를 통해 세부사항을 결정하는 일은 미루거나 연기할 수 있게 됨
- 데이터베이스 : 고수준의 정책은 어떤 종류의 데이터베이스를 사용하는지 신경 써서는 안됨
- 데이터베이스가 관계형인지, 분산형인지, 계층형인지, 아니면 평범한 플랫 파일인지와는 관련이 없도록 해야 함
- 웹 서버 : 고수준의 정책은 자신이 웹을 통해 전달된다는 사실을 알아서는 안 됨
- 프로젝트 후반까지 어떤 종류의 웹 시스템을 사용할지를 결정하지 않앙도 됨
- REST: 개발 초기에는 REST를 적용할 필요가 없음
- 의존성 주입: 개발 초기에는 의존성 주입 프레임워크를 적용할 필요가 없음
- 데이터베이스 : 고수준의 정책은 어떤 종류의 데이터베이스를 사용하는지 신경 써서는 안됨
- 선택사항을 오랫동안 열어 둔다면 더 많은 실험과 더 많은 시도가 가능하다.
- 좋은 아키텍트는 결정되지 않은 사항의 수를 최대화한다.
- 소프트웨어의 두 가지 가치
- 장치 독립성
- 대표적인 실수 예제: 코드를 입출력 장치와 직접 결합
- 프린터로 인쇄할 일이 있다면, 해당 프린터를 제어하는 입출력 명령어를 직접 사용해서 코드를 작성 -> 장치 종속적
- 카드 판독기에서 카드를 읽어야 할 경우에도 카드 판독기와 직접 상호작용하도록 작성 (천공카드 관리가 어려움)
- 해결책으로 자기 테이프 BUT, 소프트웨어는 카드 판독기와 카드 천공기를 조작하도록 작성. 자기 테이프를 사용하려면 이들 프로그램을 다시 작성해야 함
- 광고 우편
- 라인 프린터 대신 자기 테이프를 사용하도록 운영체제에게 지시 (입출력 추상화 사용으로 문제 X)
- 장치 독립성의 가치는 굉장함
- 테스트할 때는 라인 프린터 사용
- 그 후 자기 테이프에 '인쇄'하도록 지시
- 이 형태는 정책을 세부사항으로부터 분리
- 정책 : 이름과 주소 레코드에 대한 서식
- 세부사항 : 장치
- 어떤 장치를 사용할지에 대한 결정을 연기
- 물리적 주소 할당
- 대규모 회계 시스템 만드는 데 하드 코딩이 된 상황 -> 주소 할당 체계를 변경하여 상대 주소를 사용해야 함
- 시스테멩서 고수준의 청책이 디스크의 물리적 구조로부터 독립되도록 수정
- 결론
- 좋은 아키텍트는 세부사항을 정책으로부터 신중하게 가려내고, 정책이 세부사항과 결합되지 않도록 엄격하게 분리
- 정책은 세부사항에 관한 어떠한 지식도 갖지 못하게 되며, 어떤 경우에도 세부사항에 의존하지 않게 됨
- 좋은 아키텍트는 세부사항에 대하 결정을 가능한 한 오랫동안 미룰 수 있는 방향으로 정책을 설계
16장 독립성
- 좋은 아키텍처는 다음을 지원해야 함
- 시스템의 유스케이스
- 시스템의 운영
- 시스템의 개발
- 시스템의 배포
- 유스케이스
- 시스템의 아키텍처는 시스템의 의도를 지원해야 한다는 뜻
- 시스템이 장바구니 애플리케이션이라면, 이 아키텍처는 장바구니와 관련된 유스케이스를 지원해야 함
- 좋은 아키텍처 : 행위를 명확히 하고 외부로 드러내며, 이를 통해 시스템이 지닌 의도를 아키텍처 수준에서 알아볼 수 있게 만드는 것
- 시스템의 아키텍처는 시스템의 의도를 지원해야 한다는 뜻
- 운영
- 시스템이 초당 100,000명의 고객을 처리해야 한다면, 아키텍처는 이 요구와 관련된 각 유스케이스에 걸맞은 처리량과 응답시간을 보장해야 함
- 개발
- 아키텍처는 개발환경을 지원하는 데 있어 핵심적인 역할을 수행
- Conway 법칙
- 시스템을 설계하는 조직이라면 어디든지 그 조직의 의사소통 구조와 동일한 구조의 설계를 만들어 낼 것이다.
- 배포
- 아키텍처는 배포 용이성을 결정하는 데 중요한 역할
- 목표는 즉각적인 배포
- 좋은 아키텍처는 시스템이 빌드된 후 즉각 배포할 수 있도록 지원
- 이러한 아키텍처를 만들기 위해서는 시스템을 컴포넌트 단위로 적절하게 분할하고 격리해야 함
- 선택사항 열어놓기
- 대부분 모든 유스케이스, 제약사항, 팀 구조, 배포 요구사항을 알지 못함
- 다만, 몇몇 아키텍처 원칙은 구현하는 비용이 비교적 비싸지 않으며, 관심사들 사이에서 균형을 잡는 데 도움이 됨
- 시스템을 제대로 격리된 컴포넌트 단위로 분할할 때
- 선택사항을 오랫동안 열어 둘 수 있게 해줌
- 계층 결합 분리
- 아키텍트는 필요한 모든 유스케이스를 지원할 수 있는 시스템 구조를 원하지만, 유스케이스 전부를 알지는 못함
- 시스템의 기본적인 의도는 알고 있음
- 단일 책임 원칙과 공통 폐쇄 원칙을 적용하여, 그 의도의 맥락에 따라 분리
- 유스케이스 결합 분리
- 유스케이스는 시스템을 분할하는 매우 자연스러운 방법
- 결합 분리 모드
- 유스케이스를 위해 수행하는 그 작업들(결합 분리)은 운영에도 도움이 됨
- 운영 측면에서 이점을 살리기 위해선 결합을 분리할 때 적절한 모드를 선택해야 함
- 분리된 컴포넌트는 반드시 독립된 서비스가 되어야 하고, 일종의 네트워크를 통해 서로 통신해야 함
- 이러한 컴포넌트를 '서비스' 또는 '마이크로서비스'라고 함
- 핵심은 때때로 컴포넌트를 서비스 수준까지 분리해야 함
- 기억해야 할 점 : 좋은 아키텍처는 선택권을 열어 둔다는 사실이고 결합 분리 모드는 이러한 선택지 중 하나
- 개발 독립성
- 컴포넌트 분리 -> 팀 간섭 줄어듬
- 배포 독립성
- 유스케이스와 계층의 결합이 분리되면 배포 측면에서도 고도의 유연성이 생김
- 결합을 제대로 분리했다면 운영 중인 시스템에서도 계층과 유스케이스를 교체할 수 있음
- 중복
- 소프트웨어에서 중복은 나쁜 것
- 진짜 중복 -> 인스턴스 변경시 모든 복사본에 동일한 변경을 적용
- 우발적인 중복 -> 코드를 통합하지 않도록 주의
- 중복이 진짜 중복인지 확인하라
- 결합 분리 모드(다시)
- 계층과 유스케이스의 결합을 분리하는 방법은 다양
- 소스 수준 분리 모드 : 소스 코드 모듈 사이의 의존성을 제어
- 배포 수준 부리 모드: jar 파일, DLL, 공유 라이브러리와 같이 배포 가능한 단위들 사이의 의존성 제어
- 서비스 수준 분리 모드: 의존 수준을 데이터 구조 단위까지 낮출 수 있음
- 계층과 유스케이스의 결합을 분리하는 방법은 다양
- 결론
- 결합 분리 모드는 시간이 지나면서 바뀌기 쉬우며, 뛰어난 아키텍트라면 이러한 변경을 예측하여 큰 무리 없이 반영할 수 있도록 만들어야 함
17장 경계: 선 긋기
- 소프트웨어 아키텍처는 선을 긋는 기술
- 아키텍트의 목표는 시스템을 만들고 유지하는 데 드는 인적 자원을 최소화하는 것
- 효율을 떨어뜨리는 것? 결합
- 두 가지 슬픈 이야기
- 이른 결정에는 위험이 따름 -> 개발 비용 증가
- FitNesse
- 웹 서버를 직접 작성 (오픈 소스 사용 X)
- 구현이 간단
- 웹 프레임워크 사용에 대한 결정을 나중으로 연기
- 데이터베이스 결정 연기
- 웹 서버를 직접 작성 (오픈 소스 사용 X)
- 어떻게 선을 그을까? 그리고 언제 그을까?
- 데이터베이스는 GUI와는 관련이 없으므로, 선이 있어야 함.
- 데이터베이스와 업무 규칙도 마찬가지.
- 입력과 출력은?
- 개발자와 고객은 종종 시스템에 혼란
- GUI가 시스템이라고 생각하곤 함
- 중요한 점 : 입력과 출력은 중요치 않음
- 플러그인 아키텍처
- 소프트웨어 개발 기술의 역사는 플러그인을 손쉽게 생성하며, 확장가능하며 유지보수가 쉬운 시스템 아키텍처를 확립할 수 있게 만드는 방법에 대한 이야기
- 플러그인에 대한 논의
- 단일 책임 원칙은 어디에 경계를 그어야 할지를 알려줌
- 결론
- 경계선을 그리기 위해선 시스템을 컴포넌트 단위로 분할
18장 경계 해부학
- 경계는 다양한 형태로 나타남
- 경계 횡단하기
- 경계 한쪽에 있는 기능에서 반대편 기능을 호출하여 데이터를 전달하는 일
- 소스 코드 의존성 관리가 중요
- 소스 코드 모듈 하나가 변경되면, 의존하는 다른 소스 코드 모듈도 변경하거나, 다시 컴파일해서 새로 배포해야 할지도 모르기 때문
- 두려운 단일체
- 물리적으로 엄격하게 구분되지 않는 형태가 가장 흔한 형태
- 배포 관점에서 보면 이는 소위 단일체라고 불리는 단일 실행 파일
- 배포형 컴포넌트
- 물리적으로 드러나는 가장 단순한 형태 : 동적 링크 라이브러리
- 스레드
- 단일체와 배포형 컴포넌트는 모두 스레드 활용
- 로컬 프로세스
- 훨씬 강한 물리적 형태
- 서비스
- 물리적인 형태를 띠는 가장 강력한 경계가 서비스
- 서비스는 자신의 물리적 ㄷ위치에 쿠애받지 않음
- 저수준 서비스는 반드시 고수준 서비스에 '플러그인'되어야 함.
- 결론
- 대다수의 시스템은 한 가지 이상의 경계 전략을 사용
19장 정책과 수준
- 소프트웨어 시스템은 정책을 기술한 것
- 수준
- 수준의 엄밀한 정의 : 입력과 출력까지의 거리
- 시스템의 입력과 출력 모두로부터 멀리 위치할수록 정책의 수준은 높아짐
- 주목할 점 : 데이터 흐름과 소스 코드 의존성이 항상 같은 방향을 가리키지는 않음
- 입력과 출력에서부터 멀리 떨어진 정책은 저수준 정책에 비해 덜 빈번하게 변경되고, 보다 중요한 이유로 변경되는 경향이 있음
- 결론
- 단일 책임 원칙, 개방 폐쇄 원칙, 공통 폐쇄 원칙, 의존성 역전 원칙, 안정된 의존성 원칙, 안정된 추상화 원칙을 모두 포함
20장 업무 규칙
- 애플리케이션을 업무 규칙과 플러그인으로 구분하려면 업무 규칙이 실제로 무엇인지를 잘 이해해야 함
- 엄밀하게 말하면 업무 규칙은 사업적으로 수익을 얻거나 비용을 줄일 수 있는 규칙 또는 절차
- 엔티티 : 핵심 규칙과 핵심 데이터는 본질적으로 결합되어 있기 때문에 객체로 만들 좋은 후보가 되며 이러한 객체가 엔티티
- 엔티티
- 엔티티는 컴퓨터 시스템 내부의 객체
- 핵심 업무 데이터를 기반으로 동작하는 일련의 조그만 핵심 업무 규칙을 구체화함
- 유스케이스
- 모든 업무 규칙이 엔티티처럼 순수하지 않음
- 자동화된 시스템이 동작하는 방법을 정의하고 제약함으로써 수익을 얻거나 비용을 줄이는 업무 규칙도 존재
- 유스케이스는 자동화된 시스템이 사용되는 방법을 설명
- 유스케이스는 객체
- 애플리케이션에 특화된 업무 규칙을 구현하는 하나 이상의 함수를 제공
- 엔티티는 고수준, 유스케이스는 저수준
- 유스케이스는 단일 애플리케이션에 특화
- 요청 및 응답 모델
- 제대로 구성된 유스케이스 : 데이터를 사용자나 또 다른 컴포넌트와 주고받는 방식에 대해서는 전혀 눈치 챌 수 없어야 함
- 의존성을 제거하는 것은 매우 중요.
- 요청 및 응답 모델이 독립적이지 않다면, 그 모델에 의존하는 유스케이스도 결국 의존성에 간접적으로 결합
- 결론
- 업무 규칙은 소프트웨어 시스템이 존재하는 이유
- 핵심적인 기능
- 사용자 인터페이스나 데이터베이스와 같은 저수준의 관심사로 인해 오염되어서는 안 되며, 원래 그대로의 모습으로 남아 있어야 함
- 업무 규칙을 표현하는 코드는 반드시 시스템의 심장부에 위치
- 덜 중요한 코드는 심장부에 플러그인
- 업무 규칙은 시스템에서 가장 독립적이며 가장 많이 재사용할 수 있는 코드여야 함
21장 소리치는 아키텍처
- 아키텍처의 테마
- 소프트웨어 아키텍처는 시스템의 유스케이스를 지원하는 구조
- 아키텍처는 프레임워크에 대한 것이 아니고 프레임워크로부터 제공받아서는 안됨
- 프레임워크는 사용하는 도구일 뿐
- 아키텍처의 목적
- 좋은 소프트웨어 아키텍처는 프레임워크, 데이터베이스, 웹 서버, 그리고 여타 개발 환경 문제나 도구에 대해서는 결정을 미룰 수 있도록 만듦
- 좋은 아키텍처는 프로젝트의 훨씬 후반까지 레일스, 스프링, 하이버네이트, 톰캣, MySQL에 대한 결정을 하지 않아도 되도록 해줌
- 하지만 웹은?
- 웹은 아키텍처인가? -> NO
- 웹은 전달 메커니즘(입출력 장치)이며, 애플리케이션 아키텍처에서도 그와 같이 다뤄야 한다.
- 프레임워크는 도구일 뿐, 삶의 방식은 아니다
- 프레임워크가 아키텍처의 중심을 차지하는 일을 막을 수 있는 전략을 개발하라
- 테스트하기 쉬운 아키텍처
- 아키텍처가 유스케이스를 최우선으로 한다면, 그리고 프레임워크와는 적당한 거리를 둔다면, 프레임워크를 전혀 준비하지 않더라도 필요한 유스케이스 전부에 대해 단위 테스트를 할 수 있어야 함
- 테스트시 웹 서버나 데이터베이스가 있어야만 할 수 있어서는 안 됨
- 엔티티 객체는 반드시 오래된 방식의 간단한 객체여야 함
- 결론
- 나중에 결정 !
22장 클린 아키텍처
- 수십년 간 아키텍처
- 육각형 아키텍처: 포트와 어댑터라고도 알려짐.
- DCI
- BCE
- 이들 아키텍처는 공통된 목표가 있음 -> 관심사의 분리
- 소프트웨어를 계층으로 분리함으로써 관심사의 분리라는 목표를 달성
- 주요 특징
- 프레임워크 독립성 : 프레임워크의 존재 여부에 의존하지 않음
- 테스트 용이성 : 업무 규칙은 UI, 데이터베이스, 웹 서버, 또는 여타 외부 요소가 없어도 테스트할 수 있음
- UI 독립성 : 시스템의 나머지 부분을 변경하지 않고도 UI를 쉽게 변경할 수 있음
- 데이터베이스 독립성 : 오라클이나 MS SQL 서버를 몽고DB, 빅테이블, 카우치DB 등으로 교체할 수 있음
- 모든 외부 에이전시에 대한 독립성 : 실제로 업무 규칙은 외부 세계와의 인터페이스에 대해 전혀 알지 못함
- 의존성 규칙
- 소스 코드 의존성은 반드시 안쪽으로, 고수준의 정책을 향해야 한다.
- 엔티티
- 엔티티는 전사적인 핵심 업무 규칙을 캡슐화함
- 메서드를 가지는 객체이거나 일련의 데이터 구조와 함수의 집합일 수도 있음
- 엔티티는 가장 일반적이며 고수준인 규칙을 캡슐화함
- 유스케이스
- 유스케이스 계층의 소프트웨어는 애플리케이션에 특화된 업무 규칙을 포함
- 유스케이스는 엔티티로 들어오고 나가는 데이터 흐름을 조정하며, 엔티티가 자신의 핵심 업무 규칙을 사용해서 유스케이스의 목적을 달성하도록 이끔
- 인터페이스 어댑터
- 일련의 어댑터들로 구성
- 어댑터는 데이터를 유스케이스와 엔티티에게 가장 편리한 형식에서 데이터베이스나 웹 같은 외부 에이전시에게 가장 편리한 형식으로 변환된다.
- 프레젠터, 뷰, 컨트롤러는 모두 인터페이스 어댑터 계층에 속함
- 프레임워크와 드라이버
- 모두 세부사항이 위치하는 곳
- 모두 외부에 위치시켜서 피해를 최소화
- 원은 네 개여야만 하나?
- 더 많은 원이 필요할 수도 있음
- 바깥쪽 원은 저수준의 구체적인 세부사항
- 안쪽 원은 범용적이며 높은 수준
- 경계 횡단하기
- 동적 다형성을 이용하여 소스 코드 의존성을 제어흐름과는 반대로 만들 수 있고, 이를 통해 제어흐름이 어느 방향으로 흐르더라도 의존성 규칙을 준수할 수 있음
- 경계를 횡단하는 데이터는 어떤 모습인가
- 흔히 간단한 데이터 구조
- 전형적인 시나리오
- 결론
- 소프트웨어를 계층으로 분리하고 의존성 규칙을 준수한다면 본질적으로 테스트하기 쉬운 시스템을 만들게 될 것
- 데이터베이스나 웹 프레임워크와 같은 시스템의 외부 요소가 구식이 되더라도, 이들 요소를 야단스럽지 않게 교체할 수 있음
23장 프레젠터와 험블 객체
- 프레젠터
- 험블 객체 패턴을 따른 형태
- 아키텍처 경계를 식별하고 보호하는 데 도움이 됨
- 험블 객체 패턴
- 디자인 패턴으로 테스트하기 여러운 행위와 테스트하기 쉬운 행위를 단위 테스트 작성자가 분리하기 쉽게 하는 방법으로 고안
- 단순한 아이디어
- 행위들을 두 개의 모듈 또는 클래스로 나눔
- 이들 모듈 중 하나가 험블
- 가장 기본적인 본질을 남기고, 테스트하기 어려운 행위를 모두 험블 객체로 옮김
- 나머지 모듈에는 테스트하기 쉬운 행위를 옮김
- 험블 객체 패턴을 사용하면 두 부류의 행위를 분리하여 프레젠터와 뷰라는 서로 다른 클래스로 만들 수 있음
- 프레젠터와 뷰
- 뷰
- 험블 객체
- 테스트하기 어려움
- 코드는 간단하게 유지
- 데이터를 GUI로 이동시키지만, 데이터를 직접 처리하지는 않음
- 프레젠터
- 테스트하기 쉬운 객체
- 애플리케이션으로부터 데이터를 받아 화면에 표현할 수 있는 포맷으로 만드는 것
- 뷰는 데이터를 화면으로 전달하는 간단한 일만 처리
- 뷰
- 테스트와 아키텍처
- 험블 객체 패턴처럼 수많은 경계가 존재
- 데이터베이스 게이트웨이
- 유스케이스 인터랙터와 데이터베이스 사이에는 데이터베이스 게이트웨이가 위치
- 데이터베이스 CRUD관련 모든 메서드 포함
- 유스케이스 계층은 SQL을 허용하지 않으므로 필요한 메서드를 제공하는 게이트웨이 인터페이스를 호출
- 데이터 매퍼
- 하이버네이트 같은 ORM은 어느 계층?
- ORM보다는 데이터 매퍼라고 부르는 것이 나아보임 -> 관계형 데이터베이스 테이블로부터 가져온 데이터를 데이터 구조에 맞게 담아주기 때문
- 서비스 리스너
- 애플리케이션이 다른 서비스와 반드시 통신해야 한다면, 험블 객체 패턴을 발견할 수 있을지?
- 외부로부터 데이터를 수신하는 서비스의 경우, 서비스 리스너가 서비스 인터페이스로부터 데이터를 수신하고, 데이터를 애플리케이션에서 사용할 수 있게 간단한 데이터 구조로 포맷을 변경
- 결론
- 경계를 넘나드는 통신은 거의 모두 간단한 데이터 구조를 수반
- 대개 그 경계는 테스트하기 어려운 무언가와 테스트하기 쉬운 무언가로 분리
- 험블 객체 패턴을 사용하면 전체 시스템의 테스트 용이성을 크게 높일 수 있음
24장 부분적 경계
- 경계를 완벽하게 만드는 데 비용이 많이 듦
- 독립적으로 컴파일하고 배포할 수 있는 컴포넌트로 격리하는 데 필요한 모든 의존성을 관리해야 함
- 만드는 것, 유지하는 데에도 많은 비용이 듦
- YAGNI 원칙을 위배 (You Aren't Going to Need it. 필요한 작업만 해라)
- 부분적 경계를 통해 해결
- 마지막 단계를 건너뛰기
- 부분적 경계를 생성하는 방법 중 하나는 독립적으로 컴파일하고 배포할 수 있는 컴포넌트를 만들기 위한 작업은 모두 수행한 후, 단일 컴포넌트에 그대로 모아만 두는 것
- 쌍방향 인터페이스도 그 컴포넌트에 있고, 입출력 데이터 구조도, 모든 것이 완전히 준비 -> 모두를 단일 컴포넌트로 컴파일해서 배포
- 일차원 경계
- 양방향으로 격리된 상태를 유지하려면 초기 설정할 때나 지속적으로 유지할 때도 비용이 많이 듦
- 추후 완벽한 형태의 경계로 확장할 수 있는 공간을 확보하고자 할 때 -> 전략 패턴
- 퍼사드
- 더 단순한 경계는 퍼사드 패턴
- 모든 서비스 클래스를 메서드 형태로 정의하고, 서비스 호출이 발생하면 해당 서비스 클래스로 호출을 전달
- 결론
- 각 접근법은 완벽한 형태의 경계를 담기 위한 공간으로써, 적절하게 사용할 수 있는 상황이 서로 다름
- 아키텍처 경계가 언제, 어디에 존재해야 할지, 그리고 그 경계를 완벽하게 구현할지 아니면 부분적으로 구현할지를 결정하는 일 또한 아키텍트의 역할
25장 계층과 경계
- 컴포넌트
- 시스템이 세 가지 컴포넌트(UI, 업무 규칙, 데이터베이스)로만 구성된다고 생각하기 쉬움
- 컴퓨터게임 예시
- UI는 플레이어가 입력한 메시지를 모두 게임 규칙으로 전달
- 게임 규칙은 게임의 상태를 영속성을 가지는 특정한 데이터 구조로 저장
- 하지만 대다수의 시스템에서 컴포넌트의 개수는 이보다 훨씬 많음
- 시스템이 세 가지 컴포넌트(UI, 업무 규칙, 데이터베이스)로만 구성된다고 생각하기 쉬움
- 움퍼스 사냥 게임
- 게임 설명
- 텍스트를 기반으로 하는 이 게임은 GO EAST와 SHOOT WEST와 같은 매우 단순한 명령어를 사용
- 플레이어는 명령어를 입력하며, 컴퓨터는 플레이어가 보고, 냄새 맡고, 듣고, 경험할 것들로 응답
- 플레이어는 동굴로 된 시스템 안에서 움퍼스를 사냥하며, 함정이나 구덩이 그리고 숨어서 기다리는 나머지 위험들을 피해야 함
- 텍스트 기반 UI는 유지하되, 게임 규칙과 UI를 분리해서 제품을 여러 시장에서 다양한 언어로 발매한다고 가정
- 게임 규칙은 언어 독립적인 API를 사용해서 UI 컴포넌트와 통신
- UI는 API를 사람이 이해할 수 있는 언어로 변환
- 소스 코드 의존성을 적절히 관리하면, UI 컴포넌트가 어떤 언어를 사용하더라도 게임 규칙을 재사용할 수 있음 (게임 규칙은 어떤 종류의 인간 언어가 사용되는지 알지도 못할 뿐만 아니라 신경 쓸 필요도 없음)
- 게임의 상태를 영속적인 저장소에 유지하는 것도 마찬가지 (메모리나 클라우드, RAM일 수 있음)
- 게임 설명
- 클린 아키텍처?
- 중요한 아키텍처 경계를 모두 발견했는가?
- 예를 들어 UI에서 언어가 유일한 변경의 축은 아님
- 텍스트를 주고받는 메커니즘을 다양하게 만들고 싶을 수도 있음 (shell 창, 텍스트 메시지나 채팅 애플리케이션 사용 등)
- 중요한 아키텍처 경계를 모두 발견했는가?
- 흐름 횡단하기
- 데이터 흐름은 항상 두 가지인가?
- 네트워크상에서 여러 사람이 함께 플레이할 수 있게 만든다면 네트워크 컴포넌트를 추가해야 함
- 따라서 시스템이 복잡해질수록 컴포넌트 구조는 더 많은 흐름으로 분리 될 것
- 데이터 흐름은 항상 두 가지인가?
- 흐름 분리하기
- 모든 흐름이 결국에는 상단의 단일 컴포넌트에서 만나는가?
- 게임 규칙 중 일부는 지도와 관련된 메커니즘 처리
- 더 높은 수준에는 또 다른 정책 집합 존재 (플레이어의 생명력, 특정 사건을 해결하는 비용과 얻게 될 소득 등)
- 저수준 정책은 고수준 정책에게 식량 발견이나 구덩이에 빠짐 등을 알림 -> 고수준 정책은 플레이어의 상태를 관리
- 모든 흐름이 결국에는 상단의 단일 컴포넌트에서 만나는가?
- 결론
- 아키텍처 경계가 어디에나 존재한다
- 아키텍처 경계가 언제 필요한지를 신중하게 파악해야 함
- 경계를 제대로 구현하려면 비용이 많이 든다는 사실도 인지해야 하며 경계가 무시되었다면 나중에 다시 추가하는 비용이 크다는 사실도 알아야 함
26장 메인(Main) 컴포넌트
- 메인 컴포넌트
- 모든 시스템에는 최소한 하나의 컴포넌트가 존재
- 이 컴포넌트가 나머지 컴포넌트를 생성하고, 조정하며, 관리 = 메인(Main) 컴포넌트
- 궁극적인 세부사항
- 메인 컴포넌트는 궁극적인 세부사항으로, 가장 낮은 수준의 정책
- 운영체제를 제외하면 어떤 것도 메인에 의존하지 않음
- 요지는 메인은 클린 아키텍처에서 가장 바깥 원에 위치하는, 지저분한 저수준 모듈이라는 점
- 메인은 고수준의 시스템을 위한 모든 것을 로드한 후, 제어권을 고수준의 시스템에게 넘김
- 결론
- 메인을 애플리케이션의 플러그인이라고 생각하자.
- 메인은 초기 조건과 설정을 구성하고, 외부 자원을 모두 수집한 후, 제어권을 애플리케이션의 고수준 정책으로 넘기는 플러그인
- 메인은 플러그인이므로 메인 컴포넌트를 애플리케이션의 설정별로 하나씩 두도록 하여 둘 이상의 메인 컴포넌트를 만들 수도 있음
27장 '크고 작은 모든' 서비스들
- 서비스 지향 '아키텍처'와 마이크로서비스 '아키텍처'
- 서비스를 사용하면 상호 결합이 철절하게 분리되는 것처럼 보임. 이는 일부만 맞는 말
- 서비스를 사용하면 개발과 배포 독립성을 지원하는 것처럼 보임. 이도 일부만 맞는 말
- 서비스 아키텍처?
- 서비스 사용하는 것 != 아키텍처
- 아키텍처는 의존성 규칙을 준수하며 고수준의 정책을 저수준의 세부사항으로부터 분리하는 경계에 의해 정의됨
- 아키텍처적으로 중요한 서비스도 있지만, 중요하지 않는 서비스도 존재
- 서비스 사용하는 것 != 아키텍처
- 서비스의 이점?
- 결합 분리의 오류
- 서비스 사이의 결합이 확실히 분리
- 각 서비스는 서로 다른 프로세서에서, 실행 (반드시 그런건 아님)
- 프로세서 내의 공유 자원때문에 결합될 가능성 존재
- 개발 및 배포 독립성의 오류
- 전담팀이 서비스를 소유하고 운영 -> 확장 가능한 것으로 간주 (반드시 그런건 아님)
- 첫째, 모노리틱 시스템이나 컴포넌트 기반 시스템으로도 구축 가능 -> 서비스는 확장 가능한 시스템을 구축하는 유일한 시스템이 아님
- 둘째, '결합 분리의 오류'에 따르면 서비스라고 해서 항상 독립적으로 개발하고, 배포하며, 운영할 수 있는 것이 아님
- 결합 분리의 오류
- 야옹이 문제
- 예시 - 택시 통합 시스템
- 일년 이상 운영 뒤, 야옹이를 배달하는 서비스를 제공 계획
- 서비스가 모두 결합되어 있어서 독립적으로 개발하고, 배포하거나, 유지될 수 없음
- 횡단 관심사가 지닌 문제
- 객체가 구출하다
- SOLID 설계 원칙을 잘 들여다보면, 다형적으로 확장할 수 있는 클래스 집합을 생성해 새로운 기능을 처리하도록 했음
- 경계와 의존성을 주목
- TaxiUI는 어쩔 수 없이 변경해야 하지만, 그 외의 것들은 변경할 필요가 없음
- 컴포넌트 기반 서비스
- 서비스에도 이렇게 할 수 있는가? -> 예
- 서비스가 반드시 소규모 단일체여야 할 이유는 없음
- 서비스는 SOLID 원칙대로 설계할 수 있으며 컴포넌트 구조를 갖출 수도 있음
- 횡단 관심사
- 아키텍처 경계를 정의하는 것은 서비스 내에 위치한 컴포넌트
- 결론
- 서비스는 시스템의 확장성과 개발 가능성 측면에서 유용하지만, 그 자체로는 아키텍처적으로 그리 중요한 요소는 아님
- 시스템의 구성 요소가 통신하고 실행되는 물리적인 메커니즘에 의해 아키텍처가 정의되는 것이 아님
28장 테스트 경계
- 시스템 컴포넌트인 테스트
- 아키텍처 관점에서는 모든 테스트가 동일 (TDD이든, 대규모의 FitNesse, Cucumber 등이든...)
- 테스트는 태생적으로 의존성 규칙을 따르며 의존성은 항상 테스트 대상이 되는 코드를 향함
- 테스트는 독립적으로 배포 가능
- 테스트는 컴포넌트 중 가장 고립 (시스템 운영에 꼭 필요치는 않음)
- 테스트를 고려한 설계
- 테스트의 고립성 때문에 시스템의 설계 범위 밖에 있다고 여길 수 있음 -> 치명적
- 테스트가 시스템의 설계와 잘 통합되지 않으면, 테스트는 깨지기 쉬워지고, 시스템은 뻣뻣해져서 변경하기가 어려워 짐
- 변동성이 있는 것에 의존하지 말라.
- GUI는 변동성이 큼 -> GUI를 사용하지 않고 업무 규칙을 테스트할 수 있게 해야 함
- 테스트 API
- 테스트가 모든 업무 규칙을 검증하는 데 사용할 수 있도록 특화된 API를 만들면 됨
- 보안 제약사항 무시, 값비싼 자원은 건너뛰고, 시스템을 테스트 가능한 특정 상태로 강제해야 함
- 테스트 API는 테스트를 애플리케이션으로부터 분리할 목적으로 사용
- 구조적 결합
- 테스트 결합 중에서 가장 강한 유형
- 모든 상용 클래스에 테스트 클래스가 각각 존재, 또 모든 상용 메서드에 테스트 메서드 집합이 각각 존재 -> 상용 클래스나 메서드 중 하나라도 변경되면 딸려 있는 다수의 테스트가 변경되어야 함
- 테스트 API의 역할은 애플리케이션의 구조를 테스트로부터 숨기는 데 있음. 상용 코드를 리팩터링하거나 진화시키더라도 테스트에는 전혀 영향을 주지 않음
- 보안
- 테스트 API 자체와 테스트 API 중 위험한 부분의 구현부는 독립적으로 배포할 수 있는 컴포넌트로 분리해야 함
- 결론
- 테스트는 시스템 외부에 있지 않음. 오히려 시스템의 일부
- 테스트를 시스템의 일부로 설계하지 않으면 테스트는 깨지기 쉽고 유지보수하기 어려워짐
29장 클린 임베디드 아키텍처
- 소프트웨어는 닳지 않지만, 펌웨어와 하드웨어는 낡아 가므로 결국 소프트웨어도 수정해야 한다.
- 소프트웨어는 긴 시간 유용하게 쓸 수 있는 것인 반면, 펌웨어는 하드웨어가 발전할수록 낡아 갈 것.
- 소프트웨어는 닳지 않지만, 펌웨어와 하드웨어에 대한 의존성을 관리하지 않으면 안으로부터 파괴될 수 있다.
- 펌웨어는 ROM, EPROM 혹은 플래시 메로리 같은 비휘발성 메모리에 유지된다.
- 펌웨어는 하드웨어 장치에 프로그래밍된 소프트웨어 프로그램 혹은 명령어 집합이다.
- 펌웨어는 개별 하드웨어에 내장되는 소프트웨어다.
- 펌웨어는 읽기 전용 메모리(ROM)에 쓰여진 소프트웨어(프로그래미이거나 데이터)다.
- 하드웨어는 발전할 수 밖에 없고 그러한 현실을 염두에 두고 임베디드 코드를 구조화할 수 있어야 함
- 펌웨어를 수없이 양산하는 일을 멈추고, 코드에게 유효 수명을 길게 늘릴 수 있는 기회를 주어라.
- 앱-티튜드 테스트
- 왜 잠재적인 임베디드 소프트웨어는 그렇게도 많이 펌웨어로 변하는가?
- 동작에 대부분의 노력을 집중하고, 오랫동안 유용하게 남도록 구조화하는 데는 그리 신경 쓰지 않기 때문
- 먼저 동작하게 만들어라 : 소프트웨어가 동작하지 않는다면 사업은 망한다.
- 그리고 올바르게 만들어라 : 코드를 리팩터링해서 당신을 포함한 나머지 사람들이 이해할 수 있게 만들고, 요구가 변경되거나 요구를 더 잘 이해하게 되었을 때 코드를 개선할 수 있게 만들어라.
- 그리고 빠르게 만들어라 : 코드를 리팩터링해서 '요구되는' 성능을 만족시켜라.
- 앱이 동작하도록 만드는 것 = 개발자용 앱-티튜드 테스트
- 타깃-하드웨어 병목현상
- 임베디드 개발자들은 임베디드가 아니었다면 다루지 않아도 될 특수한 관심사를 많이 가지고 있음
- ex) 제한된 메모리 공간, 실시간성 제약과 처리완료 시간, 제한된 입출력, 특이한 사용자 인터페이스, 여러 센서와 실제 세상과의 상호작용 등
- 임베디드가 지닌 특수한 문제 중 하나는 타깃-하드웨어 병목현상
- 임베디드 코드가 클린 아키텍처 원칙과 실천법을 따르지 않고 작성된다면, 대개의 경우 코드를 테스트할 수 있는 환경이 해당 특정 타깃으로 국한 될 것
- 클린 임베디드 아키텍처는 테스트하기쉬운 임베디드 아키텍처다
- 계층
- 계층에는 여러 가지가 있음. 우선 세 개의 계층
- 맨 하단은 하드웨어. 기술의 발전과 무어의 법칙에 따라 변할 것
- 소프트웨어와 펌웨어가 서로 섞이는 일은 안티 패턴 -> 변화에 저항. 변경하기 어려움
- 하드웨어는 세부사항이다
- 소프트웨어와 펌웨어 사이의 경계는 코드와 하드웨어 사이의 경계와는 달리 잘 정의하기가 힘듦
- 임베디드 소프트웨어 개발자가 해야 할 일 하나는 이 경계를 분명하게 만드는 것
- 소프트웨어와 펌웨어 사이의 경계는 하드웨어 추상화 계층(Hard-ware Abstraction Layer, HAL) 이라고 부름
- 계층
- HAL 사용자에게 하드웨어 세부사항을 드러내지 말라
- 프로세서는 세부사항이다
- 이식성을 고려해야 함
- 운영체제는 세부사항이다
- 작성한 코드의 수명을 늘리려면, 무조건 운영체제를 세부사항으로 취급하고 운영체제에 의존하는 일을 막아야 함
- 클린 임베디드 아키텍처는 운영체제 추상화 계층(Operating System Abstraction Layer, OSAL)을 통해 소프트웨어를 운영체제로부터 격리시킴
- 프로세서는 세부사항이다
- 인터페이스를 통하고 대체 가능성을 높이는 방향으로 프로그래밍하라
- 모든 주요 계층(소프트웨어, OS 펌웨어, 하드웨어) 내부에는 원칙들을 적용해야 함
- 관심사를 분리시키고, 인터페이스를 활용하며, 대체 가능성을 높이는 방향으로 프로그래밍하도록 유도함
- 오직 구현체에서만 필요한 데이터 구조, 상수, 타입 정의들로 인터페이스 헤더 파일을 어지럽히지 말라.
- 구현 세부사항의 가시성을 제한하라.
- 구현 세부사항은 변경될 거라고 가정하라.
- DRY 원칙 : 조건부 컴파일 지시자를 반복하지 말라
- 주로 조건부 컴파일을 사용해서 특정 코드 블록을 활성화하거나 비활성화 함 -> DRY 원칙 위배
- 임베디드 개발자들은 임베디드가 아니었다면 다루지 않아도 될 특수한 관심사를 많이 가지고 있음
- 결론
- 모든 코드가 펌웨어가 되도록 내버려두면 제품이 오래 살아남을 수 없게 됨
728x90
반응형
'도서 리뷰 > IT 도서 리뷰' 카테고리의 다른 글
[IT 도서 리뷰] 가상 면접 사례로 배우는 대규모 시스템 설계 기초(5장 ~ 8장) (0) | 2023.09.28 |
---|---|
[IT 도서 리뷰] 클린 아키텍처 (6부 세부사항) (0) | 2023.08.28 |
[IT 도서 리뷰] 클린 아키텍처 (4부 컴포넌트 원칙) (0) | 2023.07.17 |
[IT 도서 리뷰] 클린 아키텍처 (3부 설계 원칙) (1) | 2023.07.03 |
[IT 도서 리뷰] 클린 아키텍처 (2부 벽돌부터 시작하기: 프로그래밍 패러다임) (0) | 2023.07.02 |