# Clean Architecture

## 클린 아키텍처란?

* 로버트 C 마틴이 제시한 개념
* 소프트웨어 시스템 아키텍처 디자인 패턴 중 하나
* 소프트웨어 시스템을 설계하고 구성하는 방법
* 소프트웨어 시스템을 깔끔하게 유지하고 확장 가능하며 유지보수성을 높힘

### 아키텍처 특징

* 프레임워크 독립성
* 테스트 용이성
* UI 독립성
* 데이터베이스 독립성
* 모든 외부 에이전시에 대한 독립성

### 가장 중요한 규칙 (의존성)

* 안쪽에 있을수록 고수준의 정책을 의미함
  * 고수준의 정책은 바깥쪽을 의존하지 않게
* 의존성은 반드시 안쪽으로 향함

### 주요 구성 요소

#### 엔티티

* 비즈니스 규칙을 담고 있는 엔티티, 가장 안쪽에 위치하며 가장 안정적인 부분
* 핵심 업무 규칙
* 외부의 변경으로부터 영향을 받지 않는 영역

#### 유스케이스

* 비즈니스 규칙을 적용하고 시스템의 행위를 제어하는 부분
* 어플리케이션에 특화된 업무 규칙을 포함

#### 인터페이스 어댑터

* 데이터를 외부와 주고받을 때 사용하는 부분
* 컨트롤러, 프레젠터, 게이트웨이

#### 프레임워크와 드라이버

* 외부 도구, 프레임워크, 라이브러리 등과의 연결을 담당
* 프레임워크, 데이터베이스, 웹 서버 등

### 장점

#### 1. 유지보수성 향상

* 각 구성 요소가 독립적으로 존재, 각각의 책임이 분명하게 정의
* 코드를 변경하거나 유지보수하는데 용이
* 변경이 필요한 부분을 신속하게 식별하고 수정

#### 2. 확장성 및 변동성 제어

* 안쪽의 원은 안정적이고 변동성이 낮은 엔티티
  * 시스템의 핵심 비즈니스 규칙은 안정적으로 유지
* 바깥쪽의 원은 변동성이 큰 부분
  * 외부 요인에 대한 대응이 유연

#### 3.  테스트 용이성

* 각 구성 요소는 독립적으로 테스트 가능
* 의존성이 명확하게 정의되어 있어 모킹등을 통한 테스트가 용이

#### 4. 프레임워크와 도구의 유연한 교체

* 외부 프레임워크, 라이브러리, 디비 등과의 의존성이 인터페이스 어댑터를 통한 추상화
* 필요에 따라 이들을 교체하거나 업그레이드하는데 용이

#### 5. 개발자와 비즈니스 로직의 분리

* 핵심 비즈니스 로직이 안쪽의 원에 위치, 엔티티
* 프레임워크나 인터페이스와 독립적으로 작성
* 개발자가 핵심 비즈니스 로직에 집중할 수 있음

#### 6. 플랫폼 독립성

* 안쪽의 원에는 플래폼에 종속적인 부분이 없음 -> 엔티티
* 바깥쪽의 원에서 의존성, 종속성을 처리
* 플랫폼 간의 이식성이 향상

#### 7. 비즈니스 규칙의 명시적 표현

* 비즈니스 규칙이 명시적으로 드러나도록 하는 구조를 제공
* 비즈니스 요구사항을 반영하도록 하며, 코드를 이해하고 관리하는데 돕는다.

#### 8. 도메인 주도 설계(Domain-Driven Design)와의 조합

* 도메인 주도 설계 원칙과 잘 조화
* 비즈니스 규칙을 중심으로 시스템을 구성하고 확장 가능

## 설계

> Clean Architecture에 따르는 구조화

&#x20;/![](https://1912740209-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F3GVYdZvmqQyBVq29ebqk%2Fuploads%2FFbfZOnC4VYmYxShFgWyc%2F%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202024-01-07%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%208.10.54.png?alt=media\&token=47188200-0f58-4c47-8b10-9e46b31d1229)

### Domain Layer

* 애플리케이션이 수행하는 작업을 설명
* 플랫폼/프레임워크와 독립적으로 구성
* **Model** -> 문제와 관련된 실제 세계의 Object
* **Repository** -> `Model` 에 접근 가능한 인터페이스 제공&#x20;
* **UseCase** -> 애플리케이션의 비즈니스 로직 포함

<figure><img src="https://1912740209-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F3GVYdZvmqQyBVq29ebqk%2Fuploads%2FyWM8oZghGfkm1HEoKWjq%2F%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202024-01-07%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%208.48.46.png?alt=media&#x26;token=0706c482-6924-4589-ae66-989dca49f829" alt=""><figcaption></figcaption></figure>

#### Model Layer 설계

* 데이터 모델 추출
* 비즈니스 규칙에 초점을 맞춘 필요한 데이터를 정의 (프레임워크, 플랫폼에 구애받지 않음)
* Typescript를 이용해서 정의

```typescript
export type Position = { x: number, y: number, z: number }
export type CameraType = 'perspective' | 'orthographic' 
```

#### UseCase Layer 설계

* "**A가 발생하면 B를 수행한다.**" 로 정의
* React에서의 UseCase
  * React Application에 의해 호출되는 렌더링 함수
  * 사용자 입력을 위한 이벤트 핸들러
  * 자발적으로 발생되는 effect&#x20;
* 대부분 컴포넌트 내부에서 이런 UseCase들이 스파게티처럼 뒤섞여 정의되게 된다.
  * 적절한 Layer로 풀어서 정리 필요
  * 변수간 의존성을 분석해서 문제를 풀어야 한다.
  * 다른 변수로부터 추론할 수 없는 주요 데이터 소스를 찾고 데이터 레이어에서 관리하게 만듦

<details>

<summary>UseCase 정의 예시</summary>

```typescript
import { Repository } from './repository'

export async function clickOnBoard(indexOnBoard: number, repository: Repository) {
  const { board, stepNumber } = await repository.getCurrentStep();
  const newBoard = board.slice();
  if (calculateWinnerOnBoard(newBoard) || newBoard[indexOnBoard]) return;
  
  newBoard[indexOnBoard] = isNextTurnX(stepNumber) ? "X" : "O";
  await repository.deleteStepsAfterCurrentStepNumber();
  await repostiory.addStep(newBoard);
  await repository.setCurrentStepNumber(stepNumber + 1);
}

export async function jumbToStep(stepNumber: number, repository: Repository): Promise<void> {
  return repository.setCurrentStepNumber(stepNumber);
}
```

</details>

#### Repository Layer 설계

* UseCase와 Repository 사이의 boundary 설계는 주관적
* Repository Layer는 모델 관련 동작들 가운데에 정의
* Repository Layer 동작 원칙
  * Operation 최소화 (사용하는 인터페이스들만 public하게 노출)
    * 예로 모든 getter/setter를 노출하면 일관되지 않은 데이터를 쉽게 생성 가능해진다.
  * 동작은 중립적, `UseCase Layer`에서 정의한 비즈니스 로직과 독립적
  * 각 Repository 동작은 소스간의 일관성 유지 필요

    * 한 번에 하나의 Repository 동작
    * 한 번에 여러개의 Data Source를 변경하는 경우에만 atomic operation으로 여러 data source 접근

<details>

<summary>Repository interface 설계 예시</summary>

```typescript
export type Step = {
  board: Board;
  stepNumber: number;
  numOfAllSteps: number;
}

export interface Repository {
  getCurrentStep(): Promise<Step>;
  setCurrentStepNumber(): Promise<void>;
  deleteStepsAfterCurrentStepNumber(): Promise<void>;
  addStep(board: Board): Promise<void>
}
```

</details>

### Presentation Layer

* 애플리케이션이 실제 세계와 상호작용하는 방식을 설명
* 즉, 사용자에게 화면으로 보여지는 레이어
* 리엑트 컴포넌트에서 사용자에게 어떤 UI를 어떻게 렌더링할지에 대해 초점을 둔다.

#### Presentation Layer 설계

> **MVC 형태를 구성하는것이 중요** (Moel, View, Controller)

* Model과 Controller를 하나의 객체로 병합
* Presentation Layer와 UseCase Layer 사이의 브릿지로 사용&#x20;
* 컴포넌트 렌더 부분 (View), 커스텀 훅으로 (Model - Controller) 참조함으로써 **View와 Model-Controller 분리**&#x20;
* View에서는 커스텀훅(Model-Controller)를 통해서 상위 레이어에 있는 동작들(데이터소스)에 접근하거나 UseCase를 적용 등의 처리를 적용

<details>

<summary>Presentation Layer 설계 예시</summary>

```typescript
// model-controller (커스텀 훅)
import { Repository, Step } from './repository';
import { clickOnBoard, jumpToStep } from './useCase';

export const useTicTacTocModelController = (repository: Repository) => {
  const [currentStep, setCurrentStep] = useState<Step | null>(null)
  
  useEffect(() => {
    async function init() {
      const initialStep = await repositroy.getCurrentStep();
      setCurrentStep(initialStep);
    }
    init();
  }, [repository])
  
  const handleClickOnBoard = async (indexOnBoard: number) => {
    await clickOnBoard(indexOnBoard, repository);
    const nextStep = await repository.getCurrentStep();
    setCurrentStep(nextStep);
  }
  
  const handleJumpToStep = async (stepNumber: number) => {
    await jumpToStep(stepNumber, reposit ory);
    const nextStep = await repository.getCurrentStep();
    setCurrentStep(nextStep);
  }
  
  return {
    currentStep,
    handleClickOnBoard,
    handleJumpToStep
  }
}
```

```typescript
 // View 
 import { Repository } from './repository';
 import { useTicTacTocModelController } from './tictactocModelController';
 
 type TicTacTocViewProps = {
   repository: Repository
 }
 
 export function TicTacTocView({repository}: TicTacTocViewProps) {
   const { currentStep, handleClickOnBoard, handleJumbToStep } = useTicTacTocModelController(repository);
   
   if (!currentStep) return null;
   
   // Util functions
   const winner = calculateWinnerOnBoard(currentStep.stepNumber);
   const xIsNext = isNextTurnX(currentStep.stepNumber);
   
   return (
   ...jsx
    )
 }
```

</details>

### Data Layer

* 애플리케이션이 데이터를 관리하는 방법

#### Data Layer 설계

<figure><img src="https://1912740209-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F3GVYdZvmqQyBVq29ebqk%2Fuploads%2FciEB5ytWfQRQSjNfXdIO%2F%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202024-01-07%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%209.24.48.png?alt=media&#x26;token=9190b6ed-f6f6-49d8-85b5-00f5a7551148" alt=""><figcaption></figcaption></figure>

* Data Layer는 두개의 Sub Layer 존재
* **Data:Repository Layer**
  * Domain의 Repository Layer에 정의된 동작 구현
* **Data:DataSource Layer**
  * 실제 데이터 스토리지 구현

<details>

<summary>Data Layer 설계 예시</summary>

```typescript
 // RepositoryImpl
 import { DataSource} from './dataSource';
 import { Board } from './model';
 import { Repository, Step } from './repository';
 
 export class RepositoryImpl implements Repository {
   dataSource: DataSource;
   
   cnstructor(dataSource: DataSource) {
     this.dataSource = dataSource;
   }
   
   async getCurrentStep(): Promise<Step> {
     const { history, stepNumber } = await Promise.all([
       this.dataSource.getHistory();
       this.dataSource.getStepNumber();
     ])
     const board = history[stepNumber].board;
     const numOfAllSteps = history.length;
     
     return { board, stepNumber, numOfAllSteps };
   }
   
   async setCurrentStepNumber(stepNumber: number): Promise<void> {
     const history = await this.dataSource.getHistory();
     if (stepNumber < history.length) {
       await this.dataSource.setStepNumber(stepNumber);
     } else {
       throw Error(
         `Step number ${stepNumber} should be smaller than the history size ${history.length}`
       )
     }
   }
   
   async deleteStepAfterCurrentStepNumber(): Promise<void> {
     const [history, stepNumber] = await Promise.all([
       this.dataSource.getHistory(),
       this.dataSource.getStepNumber(),
     ]);
     const trimmedHistory = history.slice(0, stepNumber + 1);
     await this.dataSource.setHistory(trimmedHistory);
   }
   
   async addStep(board:Board): Promise<void> {
     const history = await this.dataSource.getHistory();
     history.push({ board });
     await this.dataSource.setHistory(history);
   }
 }
```

```typescript
 // DataSource Layer Inteface
 import { History } from './model';
 
/**
 * DataSource access interface
 * Asuuming network acces, all methods are asynchronous
 */
export interface DataSource {
  setHistory(history: History): Promise<void>;
  getHistory(): Promise<History>;
  
  setStepNumber(stepNumber: number): Promise<void>;
  getStepNumber(): Promise<number>;
}
```

```typescript
 // DataSource Impl
 import type { History, Board } from '../../domain/model';
 import type { DataSource } from './dataSource';
 
 export class OnMemoryDataSourceImpl implements DataSource {
   history: History = [];
   stepNumber: number = 0;
   
   constructor() {
     const board = this.#createEmptyBoard();
     this.history.push({ board });
   }
   
   #createEmptyBoard(): Board {
     return Array(9).fill(null);
   }
   
   async setHistory(history: History): Promise<void> {
     this.history = history
   }
   
   async getHistory(): Promise<History> {
     return this.history
   }
   
   async setStepNumber(stepNumber: number): Promise<void> {
     this.stepNumber = stepNumber;
   }
   
   async getStepNumber(): Promise<number> {
     return this.stepNumber;
   }
 }
```

</details>

### Main Layer

* 모든 구성요소를 묶어 하나의 애플리케이션으로 결합

#### Main Layer 설계

* 여러 레이어를 하나의 애플리케이션으로&#x20;
* Repository 구현을 생성하고 View에 의존성 주입
* Repository는 modelController(custom hook)를 통해 useCase Layer로 전달
* UseCase는 Repository를 사용
* UseCase에서 RepositoryImpl를 생성하면 안됨&#x20;
  * 객체 생성은 Main Layer에서, 객체 사용은 UseCase Layer에서로 분리

```typescript
  // Dependency Injection
const dataSource = new OnMemoryDataSourceImpl();
const repository = new RepositoryImpl(dataSource);

export function App() {
  return <TicTacTocView repository={repository} />
}
```

### 의존성 흐름의 위배

> 실제 애플리케이션은 제어 흐름이 항상 한 방향으로 흐르지 않는다.

* UseCase의 비즈니스 로직이 Repository의 인터페이스를 사용
* Repository에서 data layer에서 관리되는 데이터에 접근 등
* 이런 의존성 규칙 위반을 해결하기 위한 수단으로 **의존성 역전**을 사용

#### 의존성 역전 (Dependency Injection)

* **interface** - **implement**의 구조
* 인터페이스 설계에 기반으로 실제 구현체는 Data layer에서 정의
* 정의체는 상위 레이어에 있지만 구현체는 하위에 둠으로써 서로간 느슨한 결합을 통해서 의존성 역전을 구성할 수 있다.

### 의존성 파악 도구

* 코드의 의존성이 단방향으로 가리킬수 있는 의존성 규칙을 유지할 수 있는 도구(Lint와 같은)

#### [Dependency-cruiser](https://github.com/sverweij/dependency-cruiser)

> 소스파일을 분석, 파일간 의존성 검증하는 도구&#x20;

* rule을 검증
* 위반된 규칙을 텍스트/그래픽으로 표시
* 부수적으로 시각화된 다양한 출력 형식의 dependency graph를 생성&#x20;

```bash
yarn add -D dependency-cruiser
npx depcruise --init
```

#### 소스 코드 검증

```bash
npx depcruise src --include-only '^src' --config --output-type err-long
```

#### Dependency graph 생성

* [Graphviz](https://graphviz.org/) 사용으로 의존성 그래프 생성

```bash
$ brew install graphviz

$ npx depcruise src --include-only '^src' --config --output-type dot|dot -T svg > dependency-graph.svg && open dependency-graph.svg
```

#### script 추가

> 위 명령어들은 자주 사용되므로 스크립트에 등록

```json
"scripts": {
  "depcruise:validate": "depcruise src --include-only '^src' --config --output-type err-long",
  "depcruise:tree": "depcruise src --include-only '^src' --config --output-type dot|dot -T svg > dependency-graph.svg && open dependency-graph.svg"
}
```

#### Clean Architecture를 위한 Rule Custom

> 일반 규칙으로 감지하지 못하는 상황 \
> 1\. Presentation에서 Data Layer를 참조하는 상황 (순환 참조가 아니므로 일반규칙에선 감지 못함)

* Presentation Layer 의존성이 Domain으로 향하게끔 rule 설정

```javascript
allowedSeverity: "error",
allowed: [
  {
    from: { path: "(^src/Presentation)" },
    to: { path: ["^src/Domain"] },
  }
]
```

* But 다른 유효한 의존성도 `not-in-allowed` 로 표시된다.
* `allowed` 규칙을 사용하는 경우 dependency-cruiser는 적어도 하나 이상의 규칙을 충족하지 않는 각각의 의존성에 대해  `not-in-allowed` 로 표시한다.
* 유효한 의존성을 명시적으로 모두 작성해주어야 한다.
* 번거롭지만, 리포지토리에 새 폴더를 추가할 때 마다 의존성을 설계하고 새 규칙을 추가하는 것이 좋은 습관

```javascript
allowedSeverity: "error",
allowed: [
  {
    from: { path: "(^src/Main)" },
    to: {
      // "$1" is introduced from regular expression's "group matching"
      // You can reference the part method between brackets in "from"
      // string by using "$1" in "to".
      path: ["^$1", "^src/Presentation", "^src/Data", "^src/Domain"],
    }
  },
  {
    // Presentation other than Presentation/hook
    from: { path: "(^src/Presentation)", pathNot: "^src/Presentation/hook" },
    to: { path: ["^$1", "^src/Domain"] },
  },
  {
    // We want no hooks to depend on Presentation to make hooks independent
    // from any graphical presentation
    from: { path: "(^src/Presentation/hook)" },
    to: { path: ["^$1", "^src/Domain"] },
  },
  {
    from: { path: "(^src/Data)" },
    to: { path: ["^$1", "^src/Domain"] },
  },
  {
    from: { path: "(^src/Domain/Usecase)" },
    to: { path: ["^$1", "^src/Domain/Repository", "^src/Domain/Model"] },
  },
  {
    from: { path: "(^src/Domain/Repository)" },
    to: { path: ["^$1", "^src/Domain/Model"] },
  },
  {
    from: { path: "(^src/Data)" },
    to: { path: ["^$1", "^src/Domain"] },
  },
  {
    from: { path: "(^src/Domain/Model)" },
    to: { path: ["^$1"] },
  },
  {
  // Files outside of established folders (e.g. src/index.tsx) can reference
  // any files.
    from: { pathNot: ["^src/Main", "^src/Presentation", "^src/Data", "^src/Domain"] },
    to: {},
  }
]
```

#### 큰 Repo의 의존성 시각화

> 상위 수준(폴더 기준)의 그래프  확인하여 wide하게 확인할 수 있다.

```bash
$ npx depcruise src --include-only '^src' --config --output-type ddot|dot -T svg > dependency-graph.svg && open dependency-graph.svg
```

#### 의존성 검증을 git과 연동

> 여러 멤버가 같은 리포지토리에서 작업할 때 클린  아키텍처 의존성을 유지하기 어렵다.
>
> 변경사항을 적용\*(커밋)할 때마다 의존성 크롤러를 실행해서 검증  (Husky)

```bash
$ npx husky-init && yarn
$ npx husky add .husky/pre-commit 'yarn depcruise:validate'
```
