An open API service indexing awesome lists of open source software.

https://github.com/falsy/clean-architecture-for-frontend

A sample project showcasing Clean Architecture and monorepo structure for designing multiple web services with a shared domain.
https://github.com/falsy/clean-architecture-for-frontend

clean-architecture ddd documentation oop typescript web

Last synced: about 1 year ago
JSON representation

A sample project showcasing Clean Architecture and monorepo structure for designing multiple web services with a shared domain.

Awesome Lists containing this project

README

          

# Clean Architecture for Frontend

클린 μ•„ν‚€ν…μ²˜λŠ” `DDD(Domain-driven Design)`와 `MSA(Micro Service Architecture)`와 ν•¨κ»˜ λ§Žμ€ ν”„λ‘œμ νŠΈμ—μ„œ ν™œμš©λ˜κ³  μžˆμŠ΅λ‹ˆλ‹€. 이 ν”„λ‘œμ νŠΈμ—μ„œλŠ” TypeScriptλ₯Ό μ‚¬μš©ν•˜λ©΄μ„œ λ™μΌν•œ 도메인을 κ³΅μœ ν•˜λŠ” λ‹€μ–‘ν•œ μ›Ή ν΄λΌμ΄μ–ΈνŠΈ μ„œλΉ„μŠ€λ₯Ό λͺ¨λ…Έλ ˆν¬ ꡬ성과 클린 μ•„ν‚€ν…μ²˜ 섀계λ₯Ό ν†΅ν•΄μ„œ μ„œλΉ„μŠ€λ₯Ό 효과적으둜 ν™•μž₯ν•˜κ±°λ‚˜ μœ μ§€ 보수λ₯Ό μš©μ΄ν•˜κ²Œ ν•˜λŠ” ν•˜λ‚˜μ˜ 아이디어 ν”„λ‘œμ νŠΈμž…λ‹ˆλ‹€.

λ§Œμ•½, ν”„λ‘œμ νŠΈκ°€ λ‹¨μˆœν•œ UIλ₯Ό λ‹€λ£¨λŠ” μž‘μ€ 규λͺ¨μ˜ ν”„λ‘œμ νŠΈμ΄κ±°λ‚˜ API μ„œλ²„κ°€ ν΄λΌμ΄μ–ΈνŠΈμ™€ 맞좀으둜 λŒ€μ‘λ˜λŠ” ν™˜κ²½μ΄λΌλ©΄ 클린 μ•„ν‚€ν…μ²˜ λ„μž…μ€ 였히렀 보일러 ν”Œλ ˆμ΄νŠΈ μ½”λ“œλ‘œ μΈν•œ μ½”λ“œλŸ‰ 증가와 λ³΅μž‘μ„± μ¦κ°€λ‘œ μ„œλΉ„μŠ€μ˜ μœ μ§€ λ³΄μˆ˜μ„±μ΄ λ‚˜λΉ μ§ˆ 수 μžˆμŠ΅λ‹ˆλ‹€.

μƒ˜ν”Œ ν”„λ‘œμ νŠΈλŠ” Yarnμ—μ„œ 기본으둜 μ œκ³΅ν•˜λŠ” `Workspace`λ₯Ό μ‚¬μš©ν•˜μ—¬ λͺ¨λ…Έλ ˆν¬λ₯Ό κ΅¬μ„±ν•˜κ³ , νŒ¨ν‚€μ§€λ‘œ 클린 μ•„ν‚€ν…μ²˜μ˜ Domains λ ˆμ΄μ–΄μ™€ Adapters λ ˆμ΄μ–΄λ₯Ό κ΅¬μ„±ν•˜μ˜€κ³  각각의 μ„œλΉ„μŠ€ μ—­μ‹œ νŒ¨ν‚€μ§€λ‘œ κ΅¬μ„±ν•˜λ©° 각 μ„œλΉ„μŠ€λŠ” Domains λ ˆμ΄μ–΄μ™€ Adapters λ ˆμ΄μ–΄μ˜ μš”μ†Œλ₯Ό κ·ΈλŒ€λ‘œ μ‚¬μš©ν•˜κ±°λ‚˜ λ˜λŠ” 상속, ν™•μž₯ν•˜μ—¬ μ„œλΉ„μŠ€λ₯Ό κ΅¬μ„±ν•©λ‹ˆλ‹€.

## Languages

- [English](https://github.com/falsy/clean-architecture-with-typescript)
- [ν•œκΈ€](https://github.com/falsy/clean-architecture-with-typescript/blob/main/README-ko.md)

# Clean Architecture

![Alt Clean architecture](.github/images/clean-architecture.png#gh-light-mode-only)
![Alt Clean architecture](.github/images/clean-architecture-dark.png#gh-dark-mode-only)

λ‹€μ–‘ν•œ μ•„ν‚€ν…μ²˜λ“€μ΄ κ·ΈλŸ¬ν•˜λ“― 클린 μ•„ν‚€ν…μ²˜κ°€ κ°–λŠ” κΈ°λ³Έ λͺ©μ μ€ 관심사λ₯Ό λΆ„λ¦¬ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€. 각의 관심사에 따라 계측을 λ‚˜λˆ„κ³  μ„ΈλΆ€ κ΅¬ν˜„μ΄ μ•„λ‹Œ 도메인 μ€‘μ‹¬μœΌλ‘œ μ„€κ³„ν•˜λ©°, λ‚΄λΆ€ μ˜μ—­μ΄ ν”„λ ˆμž„μ›Œν¬λ‚˜ λ°μ΄ν„°λ² μ΄μŠ€, UI λ“±μ˜ μ™ΈλΆ€ μš”μ†Œμ— μ˜μ‘΄ν•˜μ§€ μ•Šλ„λ‘ ν•©λ‹ˆλ‹€.

- μ„ΈλΆ€ κ΅¬ν˜„ μ˜μ—­κ³Ό 도메인 μ˜μ—­μ„ κ΅¬λΆ„ν•©λ‹ˆλ‹€.
- μ•„ν‚€ν…μ²˜λŠ” ν”„λ ˆμž„μ›Œν¬μ— μ˜μ‘΄ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
- μ™ΈλΆ€ μ˜μ—­μ€ λ‚΄λΆ€ μ˜μ—­μ— μ˜μ‘΄ν•  수 μžˆμ§€λ§Œ, λ‚΄λΆ€ μ˜μ—­μ€ μ™ΈλΆ€ μ˜μ—­μ— μ˜μ‘΄ν•  수 μ—†μŠ΅λ‹ˆλ‹€.
- κ³ μˆ˜μ€€, μ €μˆ˜μ€€ λͺ¨λ“ˆ λͺ¨λ‘ 좔상화에 μ˜μ‘΄ν•©λ‹ˆλ‹€.

## Communitaction Flow

![Alt Communitaction Flow](.github/images/communication-flow.png#gh-light-mode-only)
![Alt Communitaction Flow](.github/images/communication-flow-dark.png#gh-dark-mode-only)

클린 μ•„ν‚€ν…μ²˜μ˜ 흐름을 κ°„λ‹¨ν•˜κ²Œ λ‹€μ΄μ–΄κ·Έλž¨μœΌλ‘œ ν‘œν˜„ν•˜λ©΄ μœ„μ™€ κ°™μŠ΅λ‹ˆλ‹€.

# Monorepo

![Alt Monorepo](.github/images/packages.png#gh-light-mode-only)
![Alt Monorepo](.github/images/packages-dark.png#gh-dark-mode-only)

λͺ¨λ…Έλ ˆν¬λŠ” Domains λ ˆμ΄μ–΄μ™€ Adapters λ ˆμ΄μ–΄ 그리고 μ„œλΉ„μŠ€ λ ˆμ΄μ–΄λ₯Ό 각각 νŒ¨ν‚€μ§€λ‘œ μ˜μ‘΄μ„±μ„ λͺ…ν™•ν•˜κ²Œ κ΅¬λΆ„ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
그리고 λ£¨νŠΈμ—μ„œλŠ” TypeScript, ESLint, Jest의 κΈ°λ³Έ μ„€μ •μœΌλ‘œ ν•˜μœ„ νŒ¨ν‚€μ§€μ—μ„œλŠ” ν™•μž₯ν•˜μ—¬ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

> λ§Œμ•½, 도메인을 κ³΅μœ ν•˜λŠ” μ—¬λŸ¬ μ„œλΉ„μŠ€κ°€ μ•„λ‹Œ 단일 μ„œλΉ„μŠ€λ₯Ό μœ„ν•œ ꡬ성이라면 λͺ¨λ…Έλ ˆν¬λ₯Ό μ‚¬μš©ν•˜μ§€ μ•Šκ³  Domains와 Adaptersλ ˆμ΄μ–΄λ₯Ό 각각 νŒ¨ν‚€μ§€μ—μ„œ λ””λ ‰ν† λ¦¬λ‘œ, μ„œλΉ„μŠ€ νŒ¨ν‚€μ§€λŠ” Frameworks λ””λ ‰ν† λ¦¬λ‘œ κ΅¬μ„±ν•˜μ—¬, 전체 ν”„λ‘œμ νŠΈλŠ” 크게 Domians, Adapters, Frameworks둜 λ‚˜λˆ„κ³  이λ₯Ό μ€‘μ‹¬μœΌλ‘œ 섀계할 수 μžˆμŠ΅λ‹ˆλ‹€.

# Directory Structure

```
/packages
β”œβ”€ domains
β”‚ └─ src
β”‚ β”œβ”€ aggregates
β”‚ β”œβ”€ entities
β”‚ β”œβ”€ useCases
β”‚ β”œβ”€ vos
β”‚ β”œβ”€ repositories
β”‚ β”‚ └─ interface
β”‚ └─ dtos
β”‚ └─ interface
β”œβ”€ adapters
β”‚ └─ src
β”‚ β”œβ”€ presenters
β”‚ β”œβ”€ repositories
β”‚ β”œβ”€ dtos
β”‚ └─ infrastructures
β”‚ └─ interface
β”œβ”€ client-a(built with React)
β”‚ └─ src
β”‚ β”œβ”€ di
β”‚ └─ ...
└─ client-b(built with Next.js)
└─ src
β”œβ”€ di
└─ ...
```

## Tree Shaking

μœ„ μƒ˜ν”Œ ν”„λ‘œμ νŠΈμ—μ„œλŠ”, 각각의 μ„œλΉ„μŠ€ νŒ¨ν‚€μ§€μ—μ„œ 곡용 νŒ¨ν‚€μ§€(`Domains`, `Adapters`, `κ·Έ λ°–μ˜ 좔가될 수 μžˆλŠ” νŒ¨ν‚€μ§€λ“€..`)λ₯Ό μ‚¬μš©ν•  λ•Œ λΉŒλ“œλœ 결과물을 μ°Έμ‘°ν•˜μ§€ μ•Šκ³  `Source-to-Source` λ°©μ‹μœΌλ‘œ μ‚¬μš©ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€. μ΄λŠ” μ΅œμ’…μ μœΌλ‘œ μ„œλΉ„μŠ€μ˜ λͺ¨λ“ˆ λ²ˆλ“€λŸ¬κ°€ 효과적으둜 μ‚¬μš©λ˜μ§€ μ•ŠλŠ” μ½”λ“œλ₯Ό μ œκ±°ν•˜κ³  μ‚¬μš©ν•  수 μžˆμ–΄μ•Ό ν•˜κΈ° λ•Œλ¬Έμ—, 곡용 νŒ¨ν‚€μ§€λŠ” λͺ¨λ‘ `ES Modules`둜 μž‘μ„±ν•΄μ•Ό ν•©λ‹ˆλ‹€.

> λŒ€λΆ€λΆ„μ˜ λͺ¨λ“ˆ λ²ˆλ“€λŸ¬λŠ” ES Modules둜 μž‘μ„±λœ μ½”λ“œμ— λŒ€ν•΄μ„œ νŠΈλ¦¬μ…°μ΄ν‚Ήμ„ 기본으둜 μ§€μ›ν•©λ‹ˆλ‹€.

# Domains

도메인 λ ˆμ΄μ–΄μ—μ„œλŠ” λΉ„μ¦ˆλ‹ˆμŠ€ κ·œμΉ™κ³Ό λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ„ μ •μ˜ν•©λ‹ˆλ‹€.

μƒ˜ν”Œ ν”„λ‘œμ νŠΈμ˜ κ²½μš°μ—λŠ” κ°„λ‹¨ν•œ 포럼 μ„œλΉ„μŠ€μ˜ μΌλΆ€λΆ„μœΌλ‘œ μ‚¬μš©μžκ°€ κΈ€ λͺ©λ‘μ„ λ³΄κ±°λ‚˜ κΈ€κ³Ό λŒ“κΈ€μ„ μž‘μ„±ν•  수 μžˆλŠ” μ„œλΉ„μŠ€μž…λ‹ˆλ‹€. λͺ¨λ…Έλ ˆν¬λ‘œ κ΅¬μ„±λœ ν•˜λ‚˜μ˜ νŒ¨ν‚€μ§€λ‘œ Entity와 Use Case 그리고 Value Object λ“±μ˜ μ •μ˜ν•˜κ³ , λ‹€μ–‘ν•œ μ„œλΉ„μŠ€ νŒ¨ν‚€μ§€λŠ” 이λ₯Ό μ‚¬μš©ν•˜μ—¬ μ„œλΉ„μŠ€λ₯Ό κ΅¬μ„±ν•©λ‹ˆλ‹€.

## Entities

EntityλŠ” 도메인 λͺ¨λΈλ§μ˜ 핡심 κ°œλ… 쀑 ν•˜λ‚˜λ‘œ, κ³ μœ ν•œ μ‹λ³„μž(Identity)λ₯Ό 톡해 동일성을 μœ μ§€ν•˜λ©΄μ„œ μƒνƒœμ™€ 행동을 κ°€μ§€λŠ” κ°μ²΄μž…λ‹ˆλ‹€. EntityλŠ” λ‹¨μˆœνžˆ 데이터λ₯Ό λ³΄κ΄€ν•˜λŠ” ꡬ쑰체가 μ•„λ‹ˆλΌ, μžμ‹ μ˜ 데이터λ₯Ό 직접 μ œμ–΄ν•˜κ³  κ΄€λ¦¬ν•˜λŠ” 역할을 ν•˜λ©°, 도메인 λ‚΄μ—μ„œ μ€‘μš”ν•œ λΉ„μ¦ˆλ‹ˆμŠ€ κ·œμΉ™κ³Ό λ‘œμ§μ„ ν‘œν˜„ν•©λ‹ˆλ‹€.

μƒ˜ν”Œ ν”„λ‘œμ νŠΈμ—μ„œλŠ” Post, Comment, User λΌλŠ” 3개의 μ—”ν‹°ν‹°λ‘œ κ΅¬μ„±λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.

## Domain-driven Design(DDD)

클린 μ•„ν‚€ν…μ²˜λŠ” DDD와 κ³΅ν†΅μ μœΌλ‘œ 도메인 μ€‘μ‹¬μ˜ 섀계λ₯Ό μ§€ν–₯ν•©λ‹ˆλ‹€. 클린 μ•„ν‚€ν…μ²˜λŠ” μ†Œν”„νŠΈμ›¨μ–΄μ˜ ꡬ쑰적 μœ μ—°μ„±κ³Ό μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ μœ μ§€ 보수, 그리고 기술의 독립성와 ν…ŒμŠ€νŠΈ μš©μ΄μ„±μ— 쀑점을 두고 있으며 DDDλŠ” λΉ„μ¦ˆλ‹ˆμŠ€ 문제 해결에 μ΄ˆμ μ„ λ§žμΆ”κ³  μžˆμŠ΅λ‹ˆλ‹€.

ν•˜μ§€λ§Œ 클린 μ•„ν‚€ν…μ²˜λŠ” DDD의 μ² ν•™κ³Ό 원칙을 일뢀 μ°¨μš©ν•˜κ³  있으며 DDD와 ν˜Έν™˜λ˜λ©°, DDDλ₯Ό 효과적으둜 μ μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 그리고 κ·Έ 예둜 클린 μ•„ν‚€ν…μ²˜λŠ” DDD의 κ°œλ…μΈ `Ubiquitous Language`와 `Aggregate Root`λ₯Ό ν™œμš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

### Ubiquitous Language

![Ubiquitous Language](.github/images/ubiquitous.png#gh-light-mode-only)
![Ubiquitous Language](.github/images/ubiquitous-dark.png#gh-dark-mode-only)

μœ λΉ„μΏΌν„°μŠ€ μ–Έμ–΄λŠ” ν”„λ‘œμ νŠΈ μ „λ°˜μ— 걸쳐 μ˜μ‚¬μ†Œν†΅μ˜ 일관성을 μœ μ§€ν•˜κΈ° μœ„ν•΄ λͺ¨λ“  νŒ€μ›μ΄ μ‚¬μš©ν•˜λŠ” 곡유 μ–Έμ–΄λ₯Ό λ§ν•©λ‹ˆλ‹€. 이 μ–Έμ–΄λŠ” ν”„λ‘œμ νŠΈ 리더, 도메인 μ „λ¬Έκ°€, 개발자, UI/UX λ””μžμ΄λ„ˆ, λΉ„μ¦ˆλ‹ˆμŠ€ 뢄석가, QA μ—”μ§€λ‹ˆμ–΄ 등을 ν¬ν•¨ν•œ λͺ¨λ“  ν”„λ‘œμ νŠΈ ꡬ성원이 κ³΅μœ ν•΄μ•Ό ν•©λ‹ˆλ‹€. 그리고 이 μ–Έμ–΄λŠ” ν˜‘μ—… 쀑 λ¬Έμ„œν™”λ‚˜ λŒ€ν™”μ— μ‚¬μš©λ  뿐만 μ•„λ‹ˆλΌ μ†Œν”„νŠΈμ›¨μ–΄ λͺ¨λΈκ³Ό μ½”λ“œμ—λ„ λ°˜μ˜λ˜μ–΄μ•Ό ν•©λ‹ˆλ‹€.

### Aggregate Root

![Aggregate](.github/images/aggregate.png#gh-light-mode-only)
![Aggregate](.github/images/aggregate-dark.png#gh-dark-mode-only)

AggregateλŠ” μ—¬λŸ¬ 엔티티와 κ°’ 객체λ₯Ό 포함할 수 μžˆλŠ” 일관성 κ²½κ³„λ‘œ, λ‚΄λΆ€ μƒνƒœλ₯Ό μΊ‘μŠν™”ν•˜μ—¬ μ™ΈλΆ€μ—μ„œμ˜ 접근을 μ œμ–΄ν•©λ‹ˆλ‹€. λͺ¨λ“  μˆ˜μ •μ€ λ°˜λ“œμ‹œ Aggregate Rootλ₯Ό ν†΅ν•΄μ„œλ§Œ 이루어지며, μ΄λŠ” λͺ¨λΈ λ‚΄μ˜ 관계 λ³΅μž‘μ„±μ„ κ΄€λ¦¬ν•˜κ³ , μ„œλΉ„μŠ€ ν™•μž₯ 및 νŠΈλžœμž­μ…˜ λ³΅μž‘μ„± 증가 μ‹œ 일관성을 μœ μ§€ν•˜λŠ” 데 도움이 λ©λ‹ˆλ‹€.

μƒ˜ν”Œ ν”„λ‘œμ νŠΈμ—μ„œλŠ” Postκ°€ Aggregate둜 μ‚¬μš©λ˜μ—ˆμœΌλ©° ν•˜μœ„μ—λŠ” 쒅속적인 κ΄€κ³„μ˜ Comment μ—”ν‹°ν‹°κ°€ μžˆμŠ΅λ‹ˆλ‹€. 그렇기에 Commentλ₯Ό μΆ”κ°€ 및 λ³€κ²½ν•˜κΈ° μœ„ν•΄μ„œλŠ” Postλ₯Ό ν†΅ν•΄μ„œ μ΄λ£¨μ–΄μ§‘λ‹ˆλ‹€. 그리고 Post의 μ†μ„±μœΌλ‘œ μž‘μ„±μž 즉, 글을 μž‘μ„±ν•œ μ‚¬μš©μžμ˜ 정보가 ν•„μš”ν•˜μ§€λ§Œ UserλŠ” 독립적인 엔티티이기 λ•Œλ¬Έμ— 얕은 관계λ₯Ό μœ μ§€ν•˜κΈ° μœ„ν•˜μ—¬ User의 id κ°’κ³Ό name μ •λ³΄λ§Œμ„ Value Object둜 κ°€μ§€κ³  μžˆμŠ΅λ‹ˆλ‹€.

## Use Cases

Use CaseλŠ” μ‚¬μš©μžμ™€ μ„œλΉ„μŠ€ κ°„μ˜ μƒν˜Έμž‘μš©μ„ μ •μ˜ν•˜λ©°, 도메인 객체(Entity, Aggregate, Value Object)λ₯Ό ν™œμš©ν•˜μ—¬ μ„œλΉ„μŠ€κ°€ μ‚¬μš©μžμ—κ²Œ μ œκ³΅ν•΄μ•Ό ν•˜λŠ” λΉ„μ¦ˆλ‹ˆμŠ€ κΈ°λŠ₯을 λͺ…ν™•ν•˜κ²Œ ν•©λ‹ˆλ‹€. μ‹œμŠ€ν…œ μ•„ν‚€ν…μ²˜ κ΄€μ μ—μ„œ Use CaseλŠ” μ• ν”Œλ¦¬μΌ€μ΄μ…˜ 둜직과 λΉ„μ¦ˆλ‹ˆμŠ€ κ·œμΉ™μ„ λΆ„λ¦¬ν•˜λŠ” 역할을 ν•˜λ©°, μ§μ ‘μ μœΌλ‘œ λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ„ μ œμ–΄ν•˜κΈ°λ³΄λ‹€λŠ” 도메인 객체가 κ°€μ§„ λΉ„μ¦ˆλ‹ˆμŠ€ κ·œμΉ™κ³Ό λ‘œμ§μ„ ν™œμš©ν•  수 μžˆλ„λ‘ λ•μŠ΅λ‹ˆλ‹€.

μƒ˜ν”Œ ν”„λ‘œμ νŠΈμ—μ„œλŠ” κ°„λ‹¨ν•˜κ²Œ 전체 μš”μ•½ κΈ€ 리슀트λ₯Ό κ°€μ Έμ˜€κ±°λ‚˜ κΈ€κ³Ό λŒ“κΈ€μ„ μΆ”κ°€, μ‚­μ œ, λ³€κ²½κ³Ό 같은 κ°„λ‹¨ν•œ μƒν˜Έ μž‘μš©μœΌλ‘œ κ΅¬μ„±λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.

## Inversion of Control

![Alt Inversion Of Control](.github/images/inversion-of-control.png#gh-light-mode-only)
![Alt Inversion Of Control](.github/images/inversion-of-control-dark.png#gh-dark-mode-only)

Repository의 경우 Adapter λ ˆμ΄μ–΄μ— ν•΄λ‹Ήν•˜κΈ° λ•Œλ¬Έμ— Use Caseμ—μ„œλŠ” Repository에 λŒ€ν•΄μ„œ μ•Œμ•„μ„œλŠ” μ•ˆλ©λ‹ˆλ‹€. κ·Έλ ‡κΈ° λ•Œλ¬Έμ— Use Caseμ—μ„œλŠ” Repositoryλ₯Ό μΆ”μƒν™”ν•œ μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ°€μ§€κ³  κ΅¬ν˜„ν•˜λ©°, μ΄λŠ” 이후에 `μ˜μ‘΄μ„± μ£Όμž…(DI: Dependency Injection)`λ₯Ό 톡해 λ™μž‘ν•©λ‹ˆλ‹€.

# Adapters

Domains 계측과 μœ μ‚¬ν•˜κ²Œ Adatpers 계측도 λͺ¨λ…Έλ ˆν¬ λ‚΄μ—μ„œ 단일 νŒ¨ν‚€μ§€λ‘œ κ΅¬μ„±ν•˜μ—¬ μ‚¬μš©ν•©λ‹ˆλ‹€. Apapterμ—μ„œλŠ” 일반적으둜 Presenters, Repositories 및 Infrastructure ꡬ성 μš”μ†Œκ°€ ν¬ν•¨λ©λ‹ˆλ‹€. μ΄λŸ¬ν•œ ꡬ성 μš”μ†ŒλŠ” μ˜μ‘΄μ„± μ£Όμž…(DI)을 톡해 μ„œλΉ„μŠ€ νŒ¨ν‚€μ§€μ—μ„œ μ‚¬μš©λ˜λ©° ν•„μš”μ— 따라 μƒμ†ν•˜κ³  μ‚¬μš©μž μ •μ˜ν•˜μ—¬ ν™•μž₯ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

## Infrastructures

Infrastructure λ ˆμ΄μ–΄μ—μ„œλŠ” μ›Ή μ„œλΉ„μŠ€μ—μ„œ 일반적으둜 많이 μ‚¬μš©ν•˜λŠ” HTTPλ₯Ό μ‚¬μš©ν•œ μ™ΈλΆ€ μ„œλ²„μ™€μ˜ ν†΅μ‹ μ΄λ‚˜ λ˜λŠ” LocalStorage와 같은 λΈŒλΌμš°μ €μ˜ Web API와 같은 μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ™ΈλΆ€μ™€μ˜ 연결을 κ΄€λ¦¬ν•©λ‹ˆλ‹€.

## Repositories

일반적으둜 λ°±μ—”λ“œμ—μ„œ Repository λ ˆμ΄μ–΄λŠ” λ°μ΄ν„°λ² μ΄μŠ€μ™€ κ΄€λ ¨λœ `CRUD` μž‘μ—…μ„ μˆ˜ν–‰ν•˜λ©° λ°μ΄ν„°μ˜ μ €μž₯, 쑰회, μˆ˜μ •, μ‚­μ œμ™€ 같은 기본적인 데이터 μ‘°μž‘μ„ μ²˜λ¦¬ν•©λ‹ˆλ‹€. 그리고 κ·ΈλŸ¬ν•œ λ°μ΄ν„°λ² μ΄μŠ€μ™€μ˜ μƒν˜Έμž‘μš©μ„ μΆ”μƒν™”ν•˜μ—¬ λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ—μ„œ 데이터 μ €μž₯μ†Œμ— λŒ€ν•΄ μ•Œ ν•„μš”κ°€ 없도둝 ν•©λ‹ˆλ‹€.

같은 μ›λ¦¬λ‘œ μƒ˜ν”Œ ν”„λ‘œμ νŠΈμ—μ„œ Repository λ ˆμ΄μ–΄λŠ” API μ„œλ²„μ™€μ˜ HTTP 톡신에 κ΄€λ ¨λœ POST, GET, PUT, DELETE μž‘μ—…μ„ μˆ˜ν–‰ν•˜λ©° κ·Έ μƒν˜Έμž‘μš©μ„ μΆ”μƒν™”ν•˜μ—¬ λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ—μ„œλŠ” λ°μ΄ν„°μ˜ μΆœμ²˜μ— λŒ€ν•΄μ„œ μ•Œ ν•„μš”κ°€ 없도둝 ν•©λ‹ˆλ‹€. 그리고 μ™ΈλΆ€ μ„œλ²„λ‘œλΆ€ν„° 받은 λ°μ΄ν„°λŠ” `DTO`둜 μΊ‘μŠν™”ν•˜μ—¬ 이 데이터가 ν΄λΌμ΄μ–ΈνŠΈ λ‚΄λΆ€μ—μ„œ μ‚¬μš©λ  λ•Œμ˜ μ•ˆμ •μ„±μ„ 보μž₯ν•©λ‹ˆλ‹€.

## Presenters

Presenters λ ˆμ΄μ–΄μ—μ„œλŠ” UIμ—μ„œ ν•„μš”λ‘œν•˜λŠ” λ©”μ„œλ“œλ₯Ό κ°€μ§€κ³  μ‚¬μš©μžμ˜ μš”μ²­μ„ μ„œλ²„λ‘œ μ „λ‹¬ν•˜λŠ” 역할을 ν•˜λ©°, μš”μ²­μ— 따라 μ—”ν‹°ν‹° 데이터λ₯Ό UIμ—μ„œ μ‚¬μš©λ˜λŠ” View Model둜 값을 λ³€ν™˜ν•˜μ—¬ μ‘λ‹΅ν•˜λŠ” 역할을 ν•˜κΈ°λ„ ν•©λ‹ˆλ‹€.

## Dependency Injection

![Alt Dependency Injection](.github/images/dependency-injection.png#gh-light-mode-only)
![Alt Dependency Injection](.github/images/dependency-injection-dark.png#gh-dark-mode-only)

각각의 λ ˆμ΄μ–΄λŠ” μ΅œμ’…μ μœΌλ‘œ μ˜μ‘΄μ„± μ£Όμž…(Dependency Injection)을 톡해 λ™μž‘ν•©λ‹ˆλ‹€. 예둜 λ“€λ©΄, 각 λ ˆμ΄μ–΄λ³„λ‘œ μΈν„°νŽ˜μ΄μŠ€λ₯Ό μ •μ˜ν•˜κ³  이 μΈν„°νŽ˜μ΄μŠ€λ₯Ό 기반으둜 λ‹€μ–‘ν•œ κ΅¬ν˜„μ²΄λ₯Ό λ§Œλ“€μ–΄ ν•„μš”μ— 따라 μ£Όμž…ν•˜μ—¬ μ‚¬μš©ν•©λ‹ˆλ‹€.

μƒ˜ν”Œ ν”„λ‘œμ νŠΈμ—μ„œλŠ” Repository μΈν„°νŽ˜μ΄μŠ€λ₯Ό μ •μ˜ν•˜κ³ , 이λ₯Ό κ΅¬ν˜„ν•˜λŠ” NetworkRepository(HTTP 톡신)와 StorageRepository(μ›Ή μŠ€ν† λ¦¬μ§€ μ‚¬μš©)λ₯Ό κ΅¬μ„±ν•œ λ’€, μ„œλΉ„μŠ€μ— 따라 μ„ νƒμ μœΌλ‘œ μ£Όμž…ν•˜μ—¬ μ‚¬μš©ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

이처럼 μ˜μ‘΄μ„± μ£Όμž…μ„ ν†΅ν•œ μ„œλΉ„μŠ€ ꡬ성은 μ—­ν• κ³Ό μ±…μž„μ„ λͺ…ν™•νžˆ λ‚˜λˆ„μ–΄ μˆ˜μ • λ²”μœ„λ₯Ό μ΅œμ†Œν™”ν•  수 있으며, 좔상화에 μ˜μ‘΄ν•˜λŠ” 섀계λ₯Ό 톡해 μƒˆλ‘œμš΄ κ΅¬ν˜„μ²΄λ₯Ό μΆ”κ°€ν•˜μ—¬ 높은 ν™•μž₯μ„±κ³Ό μœ μ—°μ„±μ„ κ°€μ§„ μ„œλΉ„μŠ€λ₯Ό κ°œλ°œν•  수 μžˆμŠ΅λ‹ˆλ‹€.

> 일반적으둜 HTTP 톡신과 μ›Ή μŠ€ν† λ¦¬μ§€λŠ” 각기 λ‹€λ₯Έ 역할을 ν•˜λ―€λ‘œ, μƒ˜ν”Œ ν”„λ‘œμ νŠΈμ²˜λŸΌ 두 κ΅¬ν˜„μ²΄λ₯Ό 같은 λ™μž‘μœΌλ‘œ μ •μ˜ν•˜κ³  μ„ νƒμ μœΌλ‘œ μ‚¬μš©ν•˜λŠ” κ²½μš°λŠ” λ“œλ­…λ‹ˆλ‹€. 이 μ˜ˆμ‹œλŠ” λ‹¨μˆœνžˆ λ‹€μ–‘ν•œ κ΅¬ν˜„μ²΄λ₯Ό μ •μ˜ν•˜κ³  κ·Έ 차이λ₯Ό 보여주기 μœ„ν•œ λͺ©μ μœΌλ‘œ μ‚¬μš©λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

# Services

μƒ˜ν”Œ ν”„λ‘œμ νŠΈμ˜ ν΄λΌμ΄μ–ΈνŠΈ μ„œλΉ„μŠ€λŠ” Client-A, Client-B μ΄λ ‡κ²Œ 2개의 κ°„λ‹¨ν•œ μ„œλΉ„μŠ€λ‘œ κ΅¬μ„±λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€. 두 μ„œλΉ„μŠ€ λͺ¨λ‘ λ™μΌν•œ 도메인 기반의 μ„œλΉ„μŠ€λ‘œ UI μ»΄ν¬λ„ŒνŠΈλŠ” [Atomic Design](https://bradfrost.com/blog/post/atomic-web-design/)을 기반으둜 μ„€κ³„ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

## Client-A

### Use Stack

```
Vite, React, Jotai, Tailwind CSS, Jest, RTL, Cypress
```

Client-AλŠ” `Domains`와 `Adapters` λ ˆμ΄μ–΄μ˜ μš”μ†Œλ“€μ„ κ·ΈλŒ€λ‘œ μ‚¬μš©ν•΄μ„œ μ΅œμ’…μ μœΌλ‘œ `DI`된 μƒμœ„ λ ˆμ΄μ–΄μ˜ 객체λ₯Ό React의 Hooks와 μ „μ—­ μƒνƒœ 라이브러리인 [Jotai](https://jotai.org/)λ₯Ό ν™œμš©ν•˜μ—¬ 각 λ„λ©”μΈμ˜ λ©”μ„œλ“œλ₯Ό κ΅¬ν˜„ν•˜κ³  μ΄λŠ” Presenters λ ˆμ΄μ–΄μ˜ 역할을 μˆ˜ν–‰ν•©λ‹ˆλ‹€.

> 기쑴에 Adapters νŒ¨ν‚€μ§€μ—μ„œ Presenters λ””λ ‰ν† λ¦¬λ‘œ λͺ…μ‹œμ μœΌλ‘œ Presenters λ ˆμ΄μ–΄λ₯Ό λ‚˜λˆ„μ—ˆμ§€λ§Œ μ΄λŠ” ν”„λ ˆμž„μ›Œν¬μ— μ˜μ‘΄ν•˜μ§€ μ•Šμ€ λ²”μš©μ μΈ Presenters이며, μœ„ μƒ˜ν”Œ ν”„λ‘œμ νŠΈμ²˜λŸΌ Reactλ₯Ό μ‚¬μš©ν•˜λŠ” μ„œλΉ„μŠ€μ—μ„œλŠ” 그에 λΆ€ν•©ν•˜λŠ” ꡬ성을 μœ„ν•΄μ„œ μ΅œμ’…μ μœΌλ‘œ μ˜μ‘΄μ„±μ„ μ£Όμž…ν•œ Presenters 객체와 React Hooks을 ν™œμš©ν•˜μ—¬ Presenters μ˜μ—­μ„ ν™•μž₯ κ΅¬μ„±ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

### Dependency Injection

```tsx
import { API_URL } from "../constants"
import infrastructuresFn from "./infrastructures"
import repositoriesFn from "./repositories"
import useCasesFn from "./useCases"
import presentersFn from "./presenters"

export default function di(apiUrl = API_URL) {
const infrastructures = infrastructuresFn(apiUrl)
const repositories = repositoriesFn(infrastructures)
const useCases = useCasesFn(repositories)
const presenters = presentersFn(useCases)

return presenters
}
```

### Presenters

```tsx
import { useCallback, useMemo, useOptimistic, useState, useTransition } from "react"
import { atom, useAtom } from "jotai"
import presenters from "../di"
import PostVM from "../vms/PostVM"
import IPostVM from "../vms/interfaces/IPostVM"

const PostsAtoms = atom([])

export default function usePosts() {
const di = useMemo(() => presenters(), [])

const [post, setPost] = useState(null)
const [posts, setPosts] = useAtom(PostsAtoms)
const [optimisticPost, setOptimisticPost] = useOptimistic(post)
const [optimisticPosts, setOptimisticPosts] = useOptimistic(posts)
const [isPending, startTransition] = useTransition()

const getPosts = useCallback(async () => {
startTransition(async () => {
const resPosts = await di.post.getPosts()
const postVMs = resPosts.map((post) => new PostVM(post))
setPosts(postVMs)
})
}, [di.post, setPosts])

...
}
```

### View Models

Client-Aμ—μ„œλŠ” ν”„λ‘œμ νŠΈ λ ˆμ΄μ–΄μ—μ„œ React의 UI μƒνƒœ 관리에 μ ν•©ν•˜λ„λ‘ View Model을 κ΅¬μ„±ν•˜μ—¬ μ‚¬μš©ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

```ts
import CryptoJS from "crypto-js"
import IUserInfoVO from "domains/vos/interfaces/IUserInfoVO"
import ICommentVM, { ICommentVMParams } from "./interfaces/ICommentVM"

export default class CommentVM implements ICommentVM {
readonly id: string
readonly postId: string
readonly author: IUserInfoVO
readonly createdAt: Date
key: string
content: string
updatedAt: Date

constructor(parmas: ICommentVMParams) {
this.id = parmas.id
this.postId = parmas.postId
this.author = parmas.author
this.content = parmas.content
this.createdAt = parmas.createdAt
this.updatedAt = parmas.updatedAt
this.key = this.generateKey(this.id, this.updatedAt)
}

updateContent(content: string): void {
this.content = content
this.updatedAt = new Date()
this.key = this.generateKey(this.id, this.updatedAt)
}

applyUpdatedAt(date: Date): void {
this.updatedAt = date
this.key = this.generateKey(this.id, this.updatedAt)
}

private generateKey(id: string, updatedAt: Date): string {
const base = `${id}-${updatedAt.getTime()}`
return CryptoJS.MD5(base).toString()
}
}
```

View Modelμ—μ„œλŠ” μœ„μ™€ 같이 κ°’ 변경에 λ”°λ₯Έ λ©”μ„œλ“œλ₯Ό μ œκ³΅ν•˜λ©°(e.g., updateContent) λͺ¨λ“  λ³€κ²½μ—λŠ” updatedAt 값이 ν•¨κ»˜ λ³€κ²½ν•˜κ³ , updatedAt κ°’κ³Ό ID 값을 ν™œμš©ν•˜μ—¬ κ³ μœ ν•œ `Key` 값을 λ§Œλ“€μ–΄ μ‚¬μš©ν•¨μœΌλ‘œμ¨ Reactκ°€ View의 변경을 κ°μ§€ν•˜κ³  λ¦¬λ Œλ”λ§ ν•  수 μžˆλ„λ‘ ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

```tsx
...

export default function usePosts() {
...

const deleteComment = useCallback(
async (commentId: string) => {
startTransition(async () => {
setOptimisticPost((prevPost) => {
prevPost.deleteComment(commentId)
return prevPost
})

try {
const isSucess = await di.post.deleteComment(commentId)
if (isSucess) {
const resPost = await di.post.getPost(optimisticPost.id)
const postVM = new PostVM(resPost)
setPost(postVM)
}
} catch (e) {
console.error(e)
}
})
},
[di.post, optimisticPost, setOptimisticPost, setPost]
)

...
}
```

Presenter λ ˆμ΄μ–΄μ˜ Hooksμ—μ„œλ„ μœ„μ™€ 같이 Comment의 μ‚­μ œ μš”μ²­μ— λŒ€ν•œ κ°„λ‹¨ν•œ μ˜ˆμ‹œλ‘œ, VMμ—μ„œ μ œκ³΅ν•˜λŠ” λ©”μ„œλ“œλ₯Ό ν™œμš©ν•˜μ—¬ 낙관적 μ—…λ°μ΄νŠΈλ₯Ό κ΅¬ν˜„ν•˜κ³  μš”μ²­μ΄ μ„±κ³΅ν•˜λ©΄ μœ„ 변경이 적용된 μƒˆλ‘œμš΄ 데이터λ₯Ό μš”μ²­ν•˜μ—¬ 동기화 ν•˜λ„λ‘ ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

## Client-B

### Use Stack

```
Next.js, Jotai, Tailwind CSS, Jest, RTL, Cypress
```

Client-BλŠ” Client-A와 λ™μΌν•œ 도메인을 ν™œμš©ν•œ, μ„œλΉ„μŠ€ ν™•μž₯을 ν‘œν˜„ν•˜λŠ” μ„œλΉ„μŠ€λ‘œ Client-A μ„œλΉ„μŠ€μ™€ μœ μ‚¬ν•˜μ§€λ§Œ Client-A μ„œλΉ„μŠ€μ™€ λ‹€λ₯΄κ²Œ Next.jsλ₯Ό 기반으둜 ν•˜λ©° 기쑴의 Client-A μ„œλΉ„μŠ€λŠ” API μ„œλ²„μ™€μ˜ HTTP 톡신을 톡해 데이터λ₯Ό μ‘°μž‘ν•˜μ§€λ§Œ Client-BλŠ” HTTP 톡신 없이 둜컬 μ €μž₯μ†Œ(Local Storage)λ₯Ό 기반으둜 λ™μž‘ν•˜λŠ” 차이가 μžˆμŠ΅λ‹ˆλ‹€.

Client-A와 같은 `Domains` λ ˆμ΄μ–΄μ™€ `Adpaters` λ ˆμ΄μ–΄μ—μ„œ μ •μ˜ν•œ μΈν„°νŽ˜μ΄μŠ€μ™€ κ΅¬ν˜„μ²΄λ₯Ό ν™œμš©ν•˜μ—¬ 높은 μ½”λ“œ μž¬μ‚¬μš©μ„±μ„ κ°€μ§€κ³  ꡬ성할 수 μžˆμŠ΅λ‹ˆλ‹€. λ˜ν•œ, μ˜μ‘΄μ„± μ£Όμž…(DI) κ³Όμ •μ—μ„œ HTTP 톡신을 μ‚¬μš©ν•˜λŠ” Repositoryλ₯Ό λŒ€μ‹ ν•˜μ—¬ μ›Ή μŠ€ν† λ¦¬μ§€λ₯Ό μ‚¬μš©ν•˜λŠ” Repositoryλ₯Ό μ‚¬μš©ν•˜λŠ” κ²ƒλ§ŒμœΌλ‘œ κ°„λ‹¨ν•˜κ²Œ μƒˆλ‘œμš΄ μ„œλΉ„μŠ€λ₯Ό κ΅¬ν˜„ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

> Client-BλŠ” ꡬ제적인 κΈ°λŠ₯ κ΅¬ν˜„λ³΄λ‹€λŠ” λ™μΌν•œ 도메인을 ν™œμš©ν•œ λ‹€λ₯Έ ν΄λΌμ΄μ–ΈνŠΈ μ„œλΉ„μŠ€ ꡬ성에 λŒ€ν•œ κ°„λ‹¨ν•œ μ˜ˆμ‹œμž…λ‹ˆλ‹€.

## Design System

μƒ˜ν”Œ μ„œλΉ„μŠ€μ²˜λŸΌ 각 μ„œλΉ„μŠ€κ°€ λ™μΌν•œ ν”„λ ˆμž„μ›Œν¬λ₯Ό μ‚¬μš©ν•œλ‹€λ©΄, λͺ¨λ…Έλ ˆν¬ κ΅¬μ„±μ˜ μž₯점을 ν™œμš©ν•˜μ—¬ κ³΅ν†΅μœΌλ‘œ μ‚¬μš© κ°€λŠ₯ν•œ UI μ»΄ν¬λ„ŒνŠΈλ₯Ό λ³„λ„μ˜ νŒ¨ν‚€μ§€λ‘œ κ΅¬μ„±ν•¨μœΌλ‘œμ¨ μ»΄ν¬λ„ŒνŠΈμ˜ μž¬μ‚¬μš©μ„±μ„ λ†’μ—¬ λ”μš± 효과적으둜 μ„œλΉ„μŠ€λ₯Ό ν™•μž₯ 및 μœ μ§€ λ³΄μˆ˜ν•  μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€.

# μ‹€ν–‰

μƒ˜ν”Œ ν”„λ‘œμ νŠΈλŠ” λ£¨νŠΈμ— λ“±λ‘λœ μ»€λ§¨λ“œλ₯Ό ν™œμš©ν•˜μ—¬ 각 νŒ¨ν‚€μ§€λ₯Ό λΉŒλ“œ λ˜λŠ” μ‹€ν–‰ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

## μ„€μΉ˜

```sh
$ yarn install
```

## μ‹€ν–‰

```sh
# client-a
$ yarn start:a

# client-b
$ yarn start:b
```

# Thank You!

λͺ¨λ“  지원과 관심에 κ°μ‚¬λ“œλ¦½λ‹ˆλ‹€. πŸ™‡β€β™‚οΈ