Skip to content

아키텍처

Yeonu Kim edited this page May 31, 2026 · 2 revisions

이 프로젝트는 클린 아키텍처(Clean Architecture) 를 기반으로 설계되었습니다. 각 레이어는 명확한 책임을 가지며, 의존성은 항상 안쪽(도메인) 방향으로만 흐릅니다.


레이어 구조

Domain
  ↑
Usecase
  ↑
Infrastructure / Application / Presenter
  ↑
Widget / Page (UI)
레이어 위치 역할
Domain src/feature/*/domain/ 비즈니스 엔티티, 스키마, 타입을 정의합니다. 외부 의존성이 없는 순수한 레이어입니다.
Usecase src/feature/*/usecase/ 핵심 비즈니스 로직을 담당합니다. 타입(인터페이스)과 impl 구현체를 분리하여 정의합니다.
Infrastructure src/infrastructure/ API 클라이언트, 토큰 저장소 등 외부 시스템과의 연동을 담당합니다.
Application src/feature/*/application/ @tanstack/react-query를 사용해 Usecase를 감싸는 서버 상태 관리 훅입니다.
Presenter src/feature/*/presenter/ 폼 유효성 검사, 입력 상태 관리 등 UI 로직을 처리합니다. React 훅을 포함할 수 있습니다.
Widget src/widgets/*/ui/ 재사용 가능한 UI 컴포넌트입니다. Context를 통해 하위 레이어를 주입받아 사용합니다.
Page src/pages/ 라우트 단위의 화면입니다. Widget을 조합하는 역할만 담당하며, 자체적인 비즈니스 로직을 갖지 않습니다.

의존성 주입 (DI)

레이어 간 결합도를 낮추기 위해 의존성 주입(Dependency Injection) 을 사용합니다.

1. App.tsx에서의 직접 주입

App.tsx에서 Infrastructure → Usecase → Application 순서로 인스턴스를 생성하고, Context에 등록합니다.

// src/App.tsx
const api = implApi({ externalCall });
const tokenRepository = implTokenRepository({ setToken });
const authUsecase = implAuthUsecase({ api, tokenRepository });
const authQuery = useAuthQuery({ authUsecase });

2. Context API를 통한 DI

Usecase, Infrastructure, Application 레이어는 여러 패키지(Widget, Page)에서 공통으로 사용됩니다. 이를 위해 Context API로 제공하여 어디서든 쉽게 주입받을 수 있도록 설계했습니다.

Context 파일 제공하는 것
QueryContext src/feature/shared/context/query-context.ts Application 레이어 (react-query 훅 모음)
UsecaseContext src/feature/shared/context/usecase-context.ts Usecase 인스턴스
TokenContext src/feature/shared/context/token-context.ts 현재 액세스 토큰 상태

Widget과 Page에서는 useGuardContext를 사용해 Context 값을 안전하게 주입받습니다. Context가 등록되지 않은 상태에서 접근하면 명시적인 에러가 발생합니다.

const { authQuery } = useGuardContext(QueryContext);

데이터 흐름 예시

로그인 기능을 예시로 전체 흐름을 설명합니다.

Page (sign-in.tsx)
  └─ Widget (sign-in-form.tsx)
       ├─ authFormPresenter.useValidator()     ← Presenter: 폼 유효성 검사
       └─ useGuardContext(QueryContext)
            └─ authQuery.useSignIn()           ← Application: 서버 상태 관리
                 └─ authUsecase.signIn()       ← Usecase: 비즈니스 로직 처리
                      └─ api[POST /api/...]()  ← Infrastructure: HTTP 요청

UI 작업 가이드

UI를 수정할 때는 src/widgets/src/pages/ 만 수정하면 됩니다.

  • Page: Widget을 조합하는 껍데기 역할입니다. 직접 비즈니스 로직을 작성하지 않습니다.
  • Widget: useGuardContext로 Context를 받아 Application/Presenter 레이어를 사용합니다.
  • 공통 원자 컴포넌트 (Button, Input, Card 등): src/widgets/common/ui/에 위치합니다.

Usecase 응답 타입 규약

모든 Usecase 함수는 UsecaseResponse<T> 타입을 반환합니다. (src/feature/shared/response.ts)

type UsecaseResponse<T> = Promise<
  | { type: 'success'; data: T }
  | { type: 'error'; code: string; message: string }
>

Application 레이어에서 response.type으로 성공/실패를 분기 처리합니다.


개발 환경 참고

도구 위치 설명
MSW src/mocks/ 개발(DEV) 환경에서 자동 활성화되는 API 모킹
Storybook src/stories/ 컴포넌트 단위 UI 개발 및 문서화
Biome biome.json 린터 및 포매터 설정

Clone this wiki locally