Clean Architecture
ํด๋ฆฐ ์ํคํ
์ฒ๋?
๋ก๋ฒํธ C ๋งํด์ด ์ ์ํ ๊ฐ๋
์ํํธ์จ์ด ์์คํ ์ํคํ ์ฒ ๋์์ธ ํจํด ์ค ํ๋
์ํํธ์จ์ด ์์คํ ์ ์ค๊ณํ๊ณ ๊ตฌ์ฑํ๋ ๋ฐฉ๋ฒ
์ํํธ์จ์ด ์์คํ ์ ๊น๋ํ๊ฒ ์ ์งํ๊ณ ํ์ฅ ๊ฐ๋ฅํ๋ฉฐ ์ ์ง๋ณด์์ฑ์ ๋ํ
์ํคํ
์ฒ ํน์ง
ํ๋ ์์ํฌ ๋ ๋ฆฝ์ฑ
ํ ์คํธ ์ฉ์ด์ฑ
UI ๋ ๋ฆฝ์ฑ
๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ ๋ฆฝ์ฑ
๋ชจ๋ ์ธ๋ถ ์์ด์ ์์ ๋ํ ๋ ๋ฆฝ์ฑ
๊ฐ์ฅ ์ค์ํ ๊ท์น (์์กด์ฑ)
์์ชฝ์ ์์์๋ก ๊ณ ์์ค์ ์ ์ฑ ์ ์๋ฏธํจ
๊ณ ์์ค์ ์ ์ฑ ์ ๋ฐ๊นฅ์ชฝ์ ์์กดํ์ง ์๊ฒ
์์กด์ฑ์ ๋ฐ๋์ ์์ชฝ์ผ๋ก ํฅํจ
์ฃผ์ ๊ตฌ์ฑ ์์
์ํฐํฐ
๋น์ฆ๋์ค ๊ท์น์ ๋ด๊ณ ์๋ ์ํฐํฐ, ๊ฐ์ฅ ์์ชฝ์ ์์นํ๋ฉฐ ๊ฐ์ฅ ์์ ์ ์ธ ๋ถ๋ถ
ํต์ฌ ์ ๋ฌด ๊ท์น
์ธ๋ถ์ ๋ณ๊ฒฝ์ผ๋ก๋ถํฐ ์ํฅ์ ๋ฐ์ง ์๋ ์์ญ
์ ์ค์ผ์ด์ค
๋น์ฆ๋์ค ๊ท์น์ ์ ์ฉํ๊ณ ์์คํ ์ ํ์๋ฅผ ์ ์ดํ๋ ๋ถ๋ถ
์ดํ๋ฆฌ์ผ์ด์ ์ ํนํ๋ ์ ๋ฌด ๊ท์น์ ํฌํจ
์ธํฐํ์ด์ค ์ด๋ํฐ
๋ฐ์ดํฐ๋ฅผ ์ธ๋ถ์ ์ฃผ๊ณ ๋ฐ์ ๋ ์ฌ์ฉํ๋ ๋ถ๋ถ
์ปจํธ๋กค๋ฌ, ํ๋ ์ ํฐ, ๊ฒ์ดํธ์จ์ด
ํ๋ ์์ํฌ์ ๋๋ผ์ด๋ฒ
์ธ๋ถ ๋๊ตฌ, ํ๋ ์์ํฌ, ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ฑ๊ณผ์ ์ฐ๊ฒฐ์ ๋ด๋น
ํ๋ ์์ํฌ, ๋ฐ์ดํฐ๋ฒ ์ด์ค, ์น ์๋ฒ ๋ฑ
์ฅ์
1. ์ ์ง๋ณด์์ฑ ํฅ์
๊ฐ ๊ตฌ์ฑ ์์๊ฐ ๋ ๋ฆฝ์ ์ผ๋ก ์กด์ฌ, ๊ฐ๊ฐ์ ์ฑ ์์ด ๋ถ๋ช ํ๊ฒ ์ ์
์ฝ๋๋ฅผ ๋ณ๊ฒฝํ๊ฑฐ๋ ์ ์ง๋ณด์ํ๋๋ฐ ์ฉ์ด
๋ณ๊ฒฝ์ด ํ์ํ ๋ถ๋ถ์ ์ ์ํ๊ฒ ์๋ณํ๊ณ ์์
2. ํ์ฅ์ฑ ๋ฐ ๋ณ๋์ฑ ์ ์ด
์์ชฝ์ ์์ ์์ ์ ์ด๊ณ ๋ณ๋์ฑ์ด ๋ฎ์ ์ํฐํฐ
์์คํ ์ ํต์ฌ ๋น์ฆ๋์ค ๊ท์น์ ์์ ์ ์ผ๋ก ์ ์ง
๋ฐ๊นฅ์ชฝ์ ์์ ๋ณ๋์ฑ์ด ํฐ ๋ถ๋ถ
์ธ๋ถ ์์ธ์ ๋ํ ๋์์ด ์ ์ฐ
3. ํ
์คํธ ์ฉ์ด์ฑ
๊ฐ ๊ตฌ์ฑ ์์๋ ๋ ๋ฆฝ์ ์ผ๋ก ํ ์คํธ ๊ฐ๋ฅ
์์กด์ฑ์ด ๋ช ํํ๊ฒ ์ ์๋์ด ์์ด ๋ชจํน๋ฑ์ ํตํ ํ ์คํธ๊ฐ ์ฉ์ด
4. ํ๋ ์์ํฌ์ ๋๊ตฌ์ ์ ์ฐํ ๊ต์ฒด
์ธ๋ถ ํ๋ ์์ํฌ, ๋ผ์ด๋ธ๋ฌ๋ฆฌ, ๋๋น ๋ฑ๊ณผ์ ์์กด์ฑ์ด ์ธํฐํ์ด์ค ์ด๋ํฐ๋ฅผ ํตํ ์ถ์ํ
ํ์์ ๋ฐ๋ผ ์ด๋ค์ ๊ต์ฒดํ๊ฑฐ๋ ์ ๊ทธ๋ ์ด๋ํ๋๋ฐ ์ฉ์ด
5. ๊ฐ๋ฐ์์ ๋น์ฆ๋์ค ๋ก์ง์ ๋ถ๋ฆฌ
ํต์ฌ ๋น์ฆ๋์ค ๋ก์ง์ด ์์ชฝ์ ์์ ์์น, ์ํฐํฐ
ํ๋ ์์ํฌ๋ ์ธํฐํ์ด์ค์ ๋ ๋ฆฝ์ ์ผ๋ก ์์ฑ
๊ฐ๋ฐ์๊ฐ ํต์ฌ ๋น์ฆ๋์ค ๋ก์ง์ ์ง์คํ ์ ์์
6. ํ๋ซํผ ๋
๋ฆฝ์ฑ
์์ชฝ์ ์์๋ ํ๋ํผ์ ์ข ์์ ์ธ ๋ถ๋ถ์ด ์์ -> ์ํฐํฐ
๋ฐ๊นฅ์ชฝ์ ์์์ ์์กด์ฑ, ์ข ์์ฑ์ ์ฒ๋ฆฌ
ํ๋ซํผ ๊ฐ์ ์ด์์ฑ์ด ํฅ์
7. ๋น์ฆ๋์ค ๊ท์น์ ๋ช
์์ ํํ
๋น์ฆ๋์ค ๊ท์น์ด ๋ช ์์ ์ผ๋ก ๋๋ฌ๋๋๋ก ํ๋ ๊ตฌ์กฐ๋ฅผ ์ ๊ณต
๋น์ฆ๋์ค ์๊ตฌ์ฌํญ์ ๋ฐ์ํ๋๋ก ํ๋ฉฐ, ์ฝ๋๋ฅผ ์ดํดํ๊ณ ๊ด๋ฆฌํ๋๋ฐ ๋๋๋ค.
8. ๋๋ฉ์ธ ์ฃผ๋ ์ค๊ณ(Domain-Driven Design)์์ ์กฐํฉ
๋๋ฉ์ธ ์ฃผ๋ ์ค๊ณ ์์น๊ณผ ์ ์กฐํ
๋น์ฆ๋์ค ๊ท์น์ ์ค์ฌ์ผ๋ก ์์คํ ์ ๊ตฌ์ฑํ๊ณ ํ์ฅ ๊ฐ๋ฅ
์ค๊ณ
Clean Architecture์ ๋ฐ๋ฅด๋ ๊ตฌ์กฐํ
/
Domain Layer
์ ํ๋ฆฌ์ผ์ด์ ์ด ์ํํ๋ ์์ ์ ์ค๋ช
ํ๋ซํผ/ํ๋ ์์ํฌ์ ๋ ๋ฆฝ์ ์ผ๋ก ๊ตฌ์ฑ
Model -> ๋ฌธ์ ์ ๊ด๋ จ๋ ์ค์ ์ธ๊ณ์ Object
Repository ->
Model
์ ์ ๊ทผ ๊ฐ๋ฅํ ์ธํฐํ์ด์ค ์ ๊ณตUseCase -> ์ ํ๋ฆฌ์ผ์ด์ ์ ๋น์ฆ๋์ค ๋ก์ง ํฌํจ

Model Layer ์ค๊ณ
๋ฐ์ดํฐ ๋ชจ๋ธ ์ถ์ถ
๋น์ฆ๋์ค ๊ท์น์ ์ด์ ์ ๋ง์ถ ํ์ํ ๋ฐ์ดํฐ๋ฅผ ์ ์ (ํ๋ ์์ํฌ, ํ๋ซํผ์ ๊ตฌ์ ๋ฐ์ง ์์)
Typescript๋ฅผ ์ด์ฉํด์ ์ ์
export type Position = { x: number, y: number, z: number }
export type CameraType = 'perspective' | 'orthographic'
UseCase Layer ์ค๊ณ
"A๊ฐ ๋ฐ์ํ๋ฉด B๋ฅผ ์ํํ๋ค." ๋ก ์ ์
React์์์ UseCase
React Application์ ์ํด ํธ์ถ๋๋ ๋ ๋๋ง ํจ์
์ฌ์ฉ์ ์ ๋ ฅ์ ์ํ ์ด๋ฒคํธ ํธ๋ค๋ฌ
์๋ฐ์ ์ผ๋ก ๋ฐ์๋๋ effect
๋๋ถ๋ถ ์ปดํฌ๋ํธ ๋ด๋ถ์์ ์ด๋ฐ UseCase๋ค์ด ์คํ๊ฒํฐ์ฒ๋ผ ๋ค์์ฌ ์ ์๋๊ฒ ๋๋ค.
์ ์ ํ Layer๋ก ํ์ด์ ์ ๋ฆฌ ํ์
๋ณ์๊ฐ ์์กด์ฑ์ ๋ถ์ํด์ ๋ฌธ์ ๋ฅผ ํ์ด์ผ ํ๋ค.
๋ค๋ฅธ ๋ณ์๋ก๋ถํฐ ์ถ๋ก ํ ์ ์๋ ์ฃผ์ ๋ฐ์ดํฐ ์์ค๋ฅผ ์ฐพ๊ณ ๋ฐ์ดํฐ ๋ ์ด์ด์์ ๊ด๋ฆฌํ๊ฒ ๋ง๋ฆ
Repository Layer ์ค๊ณ
UseCase์ Repository ์ฌ์ด์ boundary ์ค๊ณ๋ ์ฃผ๊ด์
Repository Layer๋ ๋ชจ๋ธ ๊ด๋ จ ๋์๋ค ๊ฐ์ด๋ฐ์ ์ ์
Repository Layer ๋์ ์์น
Operation ์ต์ํ (์ฌ์ฉํ๋ ์ธํฐํ์ด์ค๋ค๋ง publicํ๊ฒ ๋ ธ์ถ)
์๋ก ๋ชจ๋ getter/setter๋ฅผ ๋ ธ์ถํ๋ฉด ์ผ๊ด๋์ง ์์ ๋ฐ์ดํฐ๋ฅผ ์ฝ๊ฒ ์์ฑ ๊ฐ๋ฅํด์ง๋ค.
๋์์ ์ค๋ฆฝ์ ,
UseCase Layer
์์ ์ ์ํ ๋น์ฆ๋์ค ๋ก์ง๊ณผ ๋ ๋ฆฝ์ ๊ฐ Repository ๋์์ ์์ค๊ฐ์ ์ผ๊ด์ฑ ์ ์ง ํ์
ํ ๋ฒ์ ํ๋์ Repository ๋์
ํ ๋ฒ์ ์ฌ๋ฌ๊ฐ์ Data Source๋ฅผ ๋ณ๊ฒฝํ๋ ๊ฒฝ์ฐ์๋ง atomic operation์ผ๋ก ์ฌ๋ฌ data source ์ ๊ทผ
Presentation Layer
์ ํ๋ฆฌ์ผ์ด์ ์ด ์ค์ ์ธ๊ณ์ ์ํธ์์ฉํ๋ ๋ฐฉ์์ ์ค๋ช
์ฆ, ์ฌ์ฉ์์๊ฒ ํ๋ฉด์ผ๋ก ๋ณด์ฌ์ง๋ ๋ ์ด์ด
๋ฆฌ์ํธ ์ปดํฌ๋ํธ์์ ์ฌ์ฉ์์๊ฒ ์ด๋ค UI๋ฅผ ์ด๋ป๊ฒ ๋ ๋๋งํ ์ง์ ๋ํด ์ด์ ์ ๋๋ค.
Presentation Layer ์ค๊ณ
MVC ํํ๋ฅผ ๊ตฌ์ฑํ๋๊ฒ์ด ์ค์ (Moel, View, Controller)
Model๊ณผ Controller๋ฅผ ํ๋์ ๊ฐ์ฒด๋ก ๋ณํฉ
Presentation Layer์ UseCase Layer ์ฌ์ด์ ๋ธ๋ฆฟ์ง๋ก ์ฌ์ฉ
์ปดํฌ๋ํธ ๋ ๋ ๋ถ๋ถ (View), ์ปค์คํ ํ ์ผ๋ก (Model - Controller) ์ฐธ์กฐํจ์ผ๋ก์จ View์ Model-Controller ๋ถ๋ฆฌ
View์์๋ ์ปค์คํ ํ (Model-Controller)๋ฅผ ํตํด์ ์์ ๋ ์ด์ด์ ์๋ ๋์๋ค(๋ฐ์ดํฐ์์ค)์ ์ ๊ทผํ๊ฑฐ๋ UseCase๋ฅผ ์ ์ฉ ๋ฑ์ ์ฒ๋ฆฌ๋ฅผ ์ ์ฉ
Data Layer
์ ํ๋ฆฌ์ผ์ด์ ์ด ๋ฐ์ดํฐ๋ฅผ ๊ด๋ฆฌํ๋ ๋ฐฉ๋ฒ
Data Layer ์ค๊ณ

Data Layer๋ ๋๊ฐ์ Sub Layer ์กด์ฌ
Data:Repository Layer
Domain์ Repository Layer์ ์ ์๋ ๋์ ๊ตฌํ
Data:DataSource Layer
์ค์ ๋ฐ์ดํฐ ์คํ ๋ฆฌ์ง ๊ตฌํ
Main Layer
๋ชจ๋ ๊ตฌ์ฑ์์๋ฅผ ๋ฌถ์ด ํ๋์ ์ ํ๋ฆฌ์ผ์ด์ ์ผ๋ก ๊ฒฐํฉ
Main Layer ์ค๊ณ
์ฌ๋ฌ ๋ ์ด์ด๋ฅผ ํ๋์ ์ ํ๋ฆฌ์ผ์ด์ ์ผ๋ก
Repository ๊ตฌํ์ ์์ฑํ๊ณ View์ ์์กด์ฑ ์ฃผ์
Repository๋ modelController(custom hook)๋ฅผ ํตํด useCase Layer๋ก ์ ๋ฌ
UseCase๋ Repository๋ฅผ ์ฌ์ฉ
UseCase์์ RepositoryImpl๋ฅผ ์์ฑํ๋ฉด ์๋จ
๊ฐ์ฒด ์์ฑ์ Main Layer์์, ๊ฐ์ฒด ์ฌ์ฉ์ UseCase Layer์์๋ก ๋ถ๋ฆฌ
// 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์ ๊ฐ์)
์์คํ์ผ์ ๋ถ์, ํ์ผ๊ฐ ์์กด์ฑ ๊ฒ์ฆํ๋ ๋๊ตฌ
rule์ ๊ฒ์ฆ
์๋ฐ๋ ๊ท์น์ ํ ์คํธ/๊ทธ๋ํฝ์ผ๋ก ํ์
๋ถ์์ ์ผ๋ก ์๊ฐํ๋ ๋ค์ํ ์ถ๋ ฅ ํ์์ dependency graph๋ฅผ ์์ฑ
yarn add -D dependency-cruiser
npx depcruise --init
์์ค ์ฝ๋ ๊ฒ์ฆ
npx depcruise src --include-only '^src' --config --output-type err-long
Dependency graph ์์ฑ
Graphviz ์ฌ์ฉ์ผ๋ก ์์กด์ฑ ๊ทธ๋ํ ์์ฑ
$ brew install graphviz
$ npx depcruise src --include-only '^src' --config --output-type dot|dot -T svg > dependency-graph.svg && open dependency-graph.svg
script ์ถ๊ฐ
์ ๋ช ๋ น์ด๋ค์ ์์ฃผ ์ฌ์ฉ๋๋ฏ๋ก ์คํฌ๋ฆฝํธ์ ๋ฑ๋ก
"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 ์ค์
allowedSeverity: "error",
allowed: [
{
from: { path: "(^src/Presentation)" },
to: { path: ["^src/Domain"] },
}
]
But ๋ค๋ฅธ ์ ํจํ ์์กด์ฑ๋
not-in-allowed
๋ก ํ์๋๋ค.allowed
๊ท์น์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ dependency-cruiser๋ ์ ์ด๋ ํ๋ ์ด์์ ๊ท์น์ ์ถฉ์กฑํ์ง ์๋ ๊ฐ๊ฐ์ ์์กด์ฑ์ ๋ํดnot-in-allowed
๋ก ํ์ํ๋ค.์ ํจํ ์์กด์ฑ์ ๋ช ์์ ์ผ๋ก ๋ชจ๋ ์์ฑํด์ฃผ์ด์ผ ํ๋ค.
๋ฒ๊ฑฐ๋กญ์ง๋ง, ๋ฆฌํฌ์งํ ๋ฆฌ์ ์ ํด๋๋ฅผ ์ถ๊ฐํ ๋ ๋ง๋ค ์์กด์ฑ์ ์ค๊ณํ๊ณ ์ ๊ท์น์ ์ถ๊ฐํ๋ ๊ฒ์ด ์ข์ ์ต๊ด
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ํ๊ฒ ํ์ธํ ์ ์๋ค.
$ npx depcruise src --include-only '^src' --config --output-type ddot|dot -T svg > dependency-graph.svg && open dependency-graph.svg
์์กด์ฑ ๊ฒ์ฆ์ git๊ณผ ์ฐ๋
์ฌ๋ฌ ๋ฉค๋ฒ๊ฐ ๊ฐ์ ๋ฆฌํฌ์งํ ๋ฆฌ์์ ์์ ํ ๋ ํด๋ฆฐ ์ํคํ ์ฒ ์์กด์ฑ์ ์ ์งํ๊ธฐ ์ด๋ ต๋ค.
๋ณ๊ฒฝ์ฌํญ์ ์ ์ฉ*(์ปค๋ฐ)ํ ๋๋ง๋ค ์์กด์ฑ ํฌ๋กค๋ฌ๋ฅผ ์คํํด์ ๊ฒ์ฆ (Husky)
$ npx husky-init && yarn
$ npx husky add .husky/pre-commit 'yarn depcruise:validate'
Last updated