Book Review - Clean Architecture
책 리뷰 - Clean Architecture
- 이 글은 Clean Architecture 책의 내용 일부를 발췌하여 작성하였습니다. 자세한 설명 및 내용은 도서를 참고하시면 됩니다.
1부. 소개
1장 설계와 아키텍처란?
- 소프트웨어 아키텍처의 목표는 필요한 시스템을 만들고 유지보수하는데 투입되는 인력을 최소화하는 데 있다.
- 투입 직원수↑ ➢ 생산성은 일정 수준에 수렴함 ➢ 투입 대비 생산성↓ ➢ 아키텍처 존재시 엉망으로 만들 때 보다 생산성↑
2장 두가지 가치에 대한 이야기
가치 1. 행위(Behavior)
- 프로그래머는 이해 관계자가 기능 명세서나 요구사항 문서를 구체화 할 수 있도록 도움.
- 또한, 이러한 요구사항을 만족하도록 코드 작성.
가치 2. 아키텍처(Architecture)
- ‘소프트(Soft) 웨어’ - 기계의 행위를 Soft(쉽게) 변경 할 수 있어야 함
- 아키텍처가 특정 형태를 다른 형태보다 선호하면 할수록, 새로운 기능을 이 구조에 맞추는게 더 힘들어짐
- 아키텍처는 형태에 독립적이여야 하고, 그럴수록 더 실용적이다.
행위(Behavior) < 아키텍처(Architecture) : 아키텍처가 후순위가 되면 시스템 개발비용↑, 혹은 기능변경이 불가능 해 질 수도
2부. 벽돌부터 시작하기: 프로그래밍 패러다임
3장. 패러다임 개요
- 패러다임이란?
- 프로그래밍을 하는 방법. 어떤 프로그래밍 구조를 사용할지, 언제 구조를 사용해야 하는지 결정
- 구조적 프로그래밍 : 제어흐름의 직접적인 전환에 대해 규칙을 부과 (if/while과 같은 제어, 반복문 활용)
- 객체 지향 프로그래밍 : 제어흐름의 간접적인 전환에 대해 규칙을 부과
- 함수형 프로그래밍 : 할당문에 대해 규칙 부과 (변수 할당에 부과되는 규율) 패러다임은 무엇을 해야 할 지 보다는 무엇을 해서는 안되는지를 말해준다.
4장. 구조적 프로그래밍
* 분기, 반복(if, while)이라는 단순 제어 + 순차 실행 (sequential execution) ➢ 특별한 장점 ⍒ 복잡한 요구사항을 더 작은 기능 단위로 분해. 대규모 시스템 → 모듈과 컴포넌트 → 입증 할 수 있는 아주 작은 기능으로 세분화 ⍒ 테스트를 통해 버그가 없음을 증명 ➢ 안정적인 서비스 제공
5장. 객체지향 프로그래밍
-
OO(Object-Oriented) 본질 : 캡슐화, 상속, 다형성⭐︎
-
다형성 </br> ⤷ 의존성 역전(dependency inversion) : 소프트웨어 아키텍트는 시스템의 소스코드 의존성 전부에 대해 방향을 결정할 수 있는 절대적 권한 갖게 됨 </br> ⤷ 기능컴포넌트 독립을 통해 ‘배포 독립성’, ‘개발 독립성’ 갖게 됨
6장. 함수형 프로그래밍
- 함수형은 ‘가변 변수’를 전혀 사용하지✖︎
➢ 경합(race)조건, 교착 상태(dead lock) 조건, 동시 업데이트 (concurrent update) 문제 발생 가능성 ✖︎ </br> ⤷ ‘불변 컴포넌트’ Level에서는 함수형 사용이 유리함
3부. 설계 원칙
SOLID 원칙
- 함수와 데이터 구조를 클래스로 배치하는 방향, 그리고 이들 클래스를 서로 결합하는 방법을 설명</br> 목적 1. 변경에 유연 2. 이해하기 쉽다. 3. 많은 소프트웨어 시스템에 사용 할 수 있는 컴포넌트의 기반이 된다.
7장. SRP: 단일 책임 원칙
- 하나의 모듈은 오직 하나의 사용자 또는 이해 관계자에 대해서만 책임져야 한다.</br> 1액터 → 1 모듈
8장. OCP: 개방-폐쇄 원칙
- 소프트웨어 개체의 행위는 확장 할 수 있어야 하지만, 이때 개체를 변경해서는 안된다.
- 기능이 어떻게(how), 왜(why), 언제(when) 발생했는지에 따라 기능을 분리하고, 분리한 기능을 컴포넌트의 계층구조로 조직화. </br> ⭐︎ 모든 컴포넌트는 단방향
9장. LSP: 리스코프 치환 원칙
- 하위 타입이 변경되더라도 상위 코드에 영향✖︎
10장. ISP: 인터페이스 분리 원칙
11장. DIP: 의존성 역전 원칙
추상 인터페이스에 변경이 생기면 이를 구체화한 구현체들도 따라서 수정해야 한다. 반대로 구체적인 구현체에 변경이 생기더라도 그 구현체가 구현하는 인터페이스는 항상, 좀 더 정확히 말하면 대다수의 경우 변경될 필요가 없다. 따라서 인터페이스는 구현체보다 변동성이 낮다.
의존성 관리를 위해 추상 팩토리(Abstract Factory) 패턴 사용
4부. 컴포넌트 원칙
12장. 컴포넌트
- 컴포넌트 :
- 배포 단위, 시스템의 구성 요소로 배포될 수 있는 가장 작은 단위.
- 잘 설계된 컴포넌트는 반드시 독립적으로 배포가능함. 따라서 독립적으로 개발 가능한 능력을 갖춰야 한다.
13장. 컴포넌트 응집도
- 응집도와 관련된 3가지 원칙
- REP : 재사용 / 릴리즈 등가 원칙
- 재사용 단위는 릴리즈(release) 단위와 같다.
- 즉, 단일 컴포넌트는 응집성 높은 클래스와 모듈들로 구성되어야 함. 컴포넌트를 규정하는 모든 모듈은 서로 공유하는 중요한 테마나 목적이 있어야 함.
- CCP : 공통 폐쇄 원칙
- 동일한 이유로 동일한 시점에 변경되는 클래스를 같은 컴포넌트를 묶어라. 서로 다른 시점에 다른 이유로 변경되는 클래스는 다른 컴포넌트로 분리하라.
- 즉, 단일 컴포넌트는 변경의 이유가 여러개 있어서는 안된다.
- CRP : 공통 재사용 원칙
- 컴포넌트 사용자들을 필요하지 않는 것에 의존하게 강요하지 말라.
- 한 컴포넌트에 속한 클래스들을 더 작게 그룹지을 수 없다. 즉, 그 중 일부 클래스에만 의존되고 다른 클래스와는 독립적일 수 없음.
- REP : 재사용 / 릴리즈 등가 원칙
- 컴포넌트 응집도에 대한 균형 다이어그램
- 프로젝트 초기 : CCP가 REP 보다 중요 → 시간이 지나면서 CRP, REP가 더 중요해짐
- 즉, 프로젝트의 컴포넌트 구조는 시간과 성숙도에 따라 변함
14장. 컴포넌트 결합
- 컴포넌트 사이의 관계를 설명하는 3가지 원칙
- ADP : 의존성 비순환 원칙
- 컴포넌트 의존성 그래프에 속한 cycle이 있어서는 안된다.
- 순환 cycle 존재시 → 단위 테스트가 힘듬. 컴포넌트 분리가 어려움. 개발/빌드 어려움
- 순환 끊기 방법 1. 의존성 역전원칙(DIP) 적용. 2. 새로운 컴포넌트 생성
- SDP : 안정된 의존성 원칙
- 안정성의 방향으로 (더 안정된 쪽에) 의존한다. (안정성 : 쉽게 움직이지 않는 상태)
- SDP를 준수하면 변경하기 어려운 모듈이 변경하기 쉽게 만들어진 모듈에 의존하지 않도록 만들 수 있다.
- 안정성 지표
I(불안정성) = Fan out / (Fan in + Fan out) I = 0 : 최고로 안정된 컴포넌트 (변경이 쉽다) I = 1 : 최고로 불안정한 컴포넌트 (변경 어려움) Fan in : 안으로 들어오는 의존성. 내부 클래스에 의존하는 컴포넌트 외부의 클래스 갯수 Fan out : 바깥으로 나가는 의존성. 컴포넌트 외부의 클래스에 의존하는 컴포넌트 내부의 클래스 갯수
- 컴포넌트 성격에 따라 I 값 위치 정해짐
- AD(안정된 추상화 원칙)
- 컴포넌트는 안정된 정도 만큼만 추산되어야 한다. → 안정성과 추상화 사이의 관계 정의
- 추상화 정도 축정
A = Na / Nc A = 0 : 추상 클래스가 하나도 없음 A = 1 : 추상 클래스만 포함 Na : 컴포넌트의 추상 클래스와 인터페이스의 개수 Nc : 컴포넌트의 클래스 개수
- D거리 측정
D 거리 = | A + I - 1 | 0에 가까울 수록 주계열 근처.
- 주계열 (Main Stream)에 위치한 것이 가장 안정적인 상태이다.
5부. 아키텍처
15장. 아키텍처란?
- 아키텍처의 목적 : 시스템의 생명주기를 지원하는 것
- 좋은 아키텍처는 시스템을 쉽게 이해하고, 쉽게 개발하며, 쉽게 유지보수 하고, 또 쉽게 배포되개 해준다.
- 아키텍처의 궁극적 목표 : 시스템 수명에 대한 비용 최고화. 프로그래머의 생산성 최대화.
16장. 독립성
- 좋은 아키텍처는?
- 시스템의 유즈 케이스 : 시스템의 아키텍처는 시스템의 의도를 지원 해야한다
- 시스템의 운영 : 요구와 관련된 각 유즈케이스에 걸맞는 처리량과 응답시간을 보장해야 함
- 시스템의 개발 : 각 개발팀이 독립적으로 행동하기 편한 아키텍처를 반드시 확보하여 개발하는 동안 팀들이 서로 방해하지 않도록 해야 함
- 시스템의 배포 : 시스템에 빌드 된 후 즉각 배포 할 수 있도록 지원해야 함
- +++ 향 후 시스템 변경이 필요할 때 어떤 방향으로든 쉽게 변경 할 수 있어야 함
- 계층 설계 방법
- ‘단일 책임 원칙’ + ‘공통 폐쇄 원칙’ ⇢ 서로 결합되지 않은 수평적인 계층으로 분리
- 유즈케이스 결합 분리
- 시스템은 수평적 계층으로 분할 + 유즈케이스는 수직으로 분할. 단, 유즈케이스들이 각 계층에서 서로 겹치지 않아야 한다.
17장. 경계: 선긋기
- 경계는 소프트웨어 요소를 서로 분리하고, 경계 한편에 있는 요소가 반대편에 있는 요소를 알지 못하게 함
- 소프트웨어 아키텍처에서 경계선을 그리려면 먼저 시스템을 컴포넌트 단위로 분할해야 한다 ➢ 의존성 역전 원칙, 안정된 추상화 원칙 응용</br> 의존성 화살표는 저수준 세부사항에서 고수준의 추상화를 향하도록 배치
18장. 경계해부학
- 경계 단위 : 아래로 갈수록 물리적으로 명확한 경계 생김
- 소스코드 단위
- 동적링크 라이브러리 단위
- 스레드
- 로컬 프로세스
- 서비스
- 1개 이상의 경계 전략 활용
19장. 정책과 수준
- 소프트웨어 시스템이란? 정책을 기술한 것
- 좋은 아키텍처라면 각 컴포넌트를 연결할 때 의존성 방향이 컴포넌트의 수준을 기반으로 연결되도록 만들어야 한다.</br> 즉, 저수준 컴포넌트가 고수준 컴포넌트에 의존하도록 설계되어야 한다.
- ‘수준(Level)’ : ‘입력과 출력’까지의 거리 → 멀어질수록 고수준
20장. 업무규칙
- 업무규칙 : 사업적으로 수익을 얻거나 비용을 줄일 수 있는 규칙 또는 절차
- ‘엔티티(Entity)’ : 핵심 업무 데이터 기반으로 동작하는 일련의 조그만 핵심 업무 규칙을 구체화 하는 것</br>
- 엔티티 객체는 핵심 업무데이터를 직접 포함하거나 매우 쉽게 접근 할 수 있음</br>
- 엔티티는 고 수준의 개념이다.
- ‘유즈케이스(Use Case)’ : 자동화된 시스템이 사용되는 방법 설명
- 자동화된 시스템이 사용되는 방법 설명 → Application에 특화된 업무 규칙 설명
- 입력데이터를 받아 출력 데이터를 생성. but 인터페이스에 의존 ✖︎
- 유즈케이스는 저 수준의 개념이다.
- 저수준(유즈케이스) 개념은 고수준 (엔티티) 개념을 알고 있다.
- 고수준 (엔티티) 개념은 저수준 (유즈케이스) 개념을 모른다.
21장. 소리치는 아키텍처
- 웹은 아키텍처인가? NO! → 웹, 앱 등은 전달 메커니즘 (입출력 장치)
- 즉, 아키텍처는 View나 Controller가 아닌 Model을 설계 해야 함
22장. 클린 아키텍처
- 아키텍처의 목표 : ‘관심사의 분리’
- 아키텍처는 모두 시스템의 다음과 같은 특징은 지니도록 만든다
- 프레임워크 독립성
- 테스트 용이성
- UI 독립성
- 데이터베이스 독립성
- 모든 외부 에이전시에 대한 독립성
23장. 프레젠터와 험블 객체
- 행위 → 본질과 테스트하기 어려운 행위(험블객체)로 나눔
- 프레젠터와 뷰, 데이터베이스와 게이트웨이, 서비스 리스너와 인터페이스 경계에 험블 객체 패턴이 숨어있다
24장. 부분적 경계
- 코드 단위 분리
- 일차원 경계
- Facade 패턴 활용
25장. 계층과 경계
26장. 메인(Main) 컴포넌트
- 메인 컴포넌트
- ‘시스템의 초기 진입점’
- 이 컴포넌트가 나머지 컴포넌트를 생성하고 조정하며 관리한다. </br> → 의존성 주입 프레임워크를 이용해 의존성을 주입하는 일은 바로 메인 컴포넌트에서 이루어져야 함
- 메인은 클린 아키텍처에서 가장 바깥 원에 위치하는 지저분한 저수준 모듈이라는 점.
- 메인은 고수준의 시스템을 위한 모든 것을 로드 한 후, 제어권을 고수준의 시스템에게 넘긴다.
27장. ‘크고 작은 모든’ 서비스들
28장. 테스트 경계
- 테스트는 아키텍처에서 가장 바깥쪽 원으로 생각 할 수 있다.
- 시스템 내부의 어떤 것도 테스트에는 의존하지 않으며, 테스트는 시스템이 컴포넌트를 향해 항상 원의 안쪽으로 의존한다.
- 또한 테스트는 독립적으로 배포 가능하다.
- 시스템과 테스트를 설계 할 때, GUI를 사용하지 않고 업무 규칙을 테스트 할 수 있게 해야한다.
29장. 클린 임베디드 아키텍처
- (생략..)