# DSP 프론트엔드 클린 아키텍처 가이드

> `/review` 에서 구조적 위반 여부를 점검할 때 사용하는 프론트엔드 아키텍처 기준이다.

---

## 목적

레이어화된 구조와 명확한 의존성 방향을 통해, 변경이 발생했을 때 어디를 고쳐야 하는지 **예측 가능한 코드**를 만드는 것.

### 해결하려는 문제

| 문제             | 설명                                                                                              |
| ---------------- | ------------------------------------------------------------------------------------------------- |
| 변경 범위 불명확 | 구조화가 안 되어 있어 하나가 바뀌면 어디까지 영향이 가는지 예측하기 어려움                        |
| 테스트 어려움    | 여러 로직이 섞여 있어 순수 로직만 테스트하기 어려움                                               |
| 코드 일관성 부재 | 같은 역할의 코드가 담당자마다 다른 위치에 있고, 구조적 기준이 없어 개인 스타일에 따라 제각각이 됨 |

---

## 전체 레이어 구조

![클린 아키텍처 레이어 구조도](../../../../../platform/assets/rules/fe/clean-architecture-diagram.png)

```
┌─────────────────────────────────────────────────────────┐
│  UI Layer (화면 계층)                                   │
│  app/ · app-config/ · page-container/ · shared/         │
└────────────────────────────▲────────────────────────────┘
                             │ composition 또는 query 통해 데이터 요청
┌────────────────────────────┴────────────────────────────┐
│  API Layer (api/)                                       │
│                                                         │
│  ┌───────────────────────────────────────────────────┐  │
│  │  Composition Layer (선택적)                       │  │
│  │  api/composition                                  │  │
│  │  → 멀티 query 조합이 필요한 경우에만 사용         │  │
│  └────────────────────────▲──────────────────────────┘  │
│                           │                              │
│  ┌────────────────────────┴──────────────────────────┐  │
│  │  Query Layer (단일 쿼리 가공)                     │  │
│  │  api/query   api/selector   api/transform        │  │
│  │  React Query   단일 응답 가공  의미/규칙 재정의   │  │
│  │  (selector, transform 필요시 사용)                │  │
│  └────────────────────────▲──────────────────────────┘  │
│                           │ 원천 데이터 요청             │
│  ┌────────────────────────┴──────────────────────────┐  │
│  │  Data Source Layer (원천 데이터)                   │  │
│  │  api/service → HTTP 통신   api/dto → 백엔드 계약   │  │
│  └───────────────────────────────────────────────────┘  │
│                                                         │
└────────────────────────────▲────────────────────────────┘
                             │ 공통 인프라 사용
┌────────────────────────────┴────────────────────────────┐
│  Core Packages Layer                                    │
│  HttpClient / Design System / 공통 유틸                  │
│  (npm 패키지로 제공되는 조직 공통 자산)                  │
│  → 모든 레이어에서 참조 가능                             │
└─────────────────────────────────────────────────────────┘
```

---

## API 레이어

### 의존 방향

```
Composition Layer (선택적) ─── 멀티 query 조합
  ↑
Query Layer ──────────────── 단일 쿼리 가공
  query / selector / transform
  ↑
Data Source Layer ─────────── 원천 데이터
  service / dto
```

> `composition`, `query`, `selector` 모두 `transform`을 직접 호출할 수 있음.
> `selector`는 복잡한 가공이 필요할 때만 사용하고, 단순 변환은 `query`에서 `transform`을 직접 호출해도 됨.

---

### DTO (`*.dto.ts`)

백엔드와 주고받는 요청/응답 타입 정의. **Swagger DTO 네이밍 똑같이 사용**.

- **순수 TypeScript interface**
- 데이터 가공 없음

```ts
// contract.dto.ts
export interface ContractResponseDto {
  id: number;
  genderCode: 1 | 2;
  endDate: string;
}
```

---

### Service (`*.service.ts`)

서버와 통신하여 원천 데이터를 가져오는 계층. `sales-frontend-api`의 `HttpClientAxios` 통신 객체를 사용한다.

- **순수 함수 (React 모름)**
- 데이터 가공 없음, 원본 DTO 그대로 반환

```ts
// contract.service.ts
import { HttpClientAxios } from 'sales-frontend-api';

export const getContracts = () => new HttpClientAxios().get<GetContractsResponse>('/contracts');
```

---

### Transform (`*.transform.ts`)

데이터의 "의미"를 재정의하는 순수 변환 함수 모음.

- **순수 함수 (React 모름)**
- 코드값 변환, DTO → 내부 타입 정규화, 상태 계산/판단 로직
- 네이밍: `getXXX`, `isXXX`, `normalizeXXX`, `mapXXX`

```ts
// contract.transform.ts
export const getContractStatus = (contract: Contract) => {
  if (contract.endDate < today()) return 'EXPIRED';
  if (contract.paused) return 'PAUSED';
  return 'ACTIVE';
};

export const mapGenderCode = (code: 1 | 2) => (code === 1 ? 'MALE' : 'FEMALE');
```

---

### Selector (`*.selector.ts`)

단일 Query 응답을 가공하는 함수. React Query `select` 옵션 전용.

- **순수 함수 (React 모름)**
- 입력: 서비스 응답 DTO / 출력: UI에서 사용할 형태로 가공
- 필요시 `transform` 호출 가능
- 네이밍: `selectXXX`

```ts
// contract.selector.ts
export const selectContracts = (res: GetContractsResponse) =>
  res.data.map((dto) => ({
    id: dto.id,
    gender: mapGenderCode(dto.genderCode) // transform 호출
  }));
```

---

### Query (`use-*-query.tsx`)

Service를 React Query 훅으로 감싸는 어댑터.

- React, React Query 의존
- 캐시, 비동기 핸들링, 재요청 정책 설정
- 데이터 가공이 필요한 경우 `select`에 selector 연결

```ts
// use-contracts-query.tsx
export const useContractsQuery = () =>
  useQuery({
    queryKey: ['contracts'],
    queryFn: getContracts,
    select: selectContracts
  });
```

---

### Composition (`use-*.composition.tsx`)

여러 Query를 조합하여 하나의 훅으로 제공하는 계층

- 여러 Query 결과 조합 및 loading/error 상태 통합
- 여러 Query 결과를 merge/transform하여 새로운 데이터 구조를 만드는 경우에 사용

#### cross-domain composition 위치 기준

서로 다른 도메인의 Query를 조합해야 하는 경우:

| 상황                             | 위치                                         |
| -------------------------------- | -------------------------------------------- |
| 조합의 주체 도메인이 명확한 경우 | 주체 도메인의 api 폴더 (예: `api/contract/`) |
| 조합 성격이 공통성인 경우        | `api/_shared/` 폴더                          |

```ts
// api/contract/use-contract-composition.tsx
// → contract가 주체인 복합 가공
export const useContractPaymentSummary = () => {
  const contracts = useContractsQuery();
  const payments = usePaymentsQuery();

  const isLoading = contracts.isLoading || payments.isLoading;

  // 복합 가공: 두 응답을 merge하여 새로운 구조 생성
  const summary = 데이터가공(contracts.data, payments.data);
  return { summary, contracts: contracts.data, payments: payments.data, isLoading };
};
```

```ts
// api/_shared/use-dashboard-composition.tsx
// → 조합 성격이 공통성인 경우
export const useDashboardSummary = () => {
  const contracts = useContractsQuery();
  const payments = usePaymentsQuery();
  const customers = useCustomersQuery();

  const isLoading = contracts.isLoading || payments.isLoading || customers.isLoading;

  // 새로운 Data
  const dashboard = 데이터가공(contracts.data, payments.data, customers.data);
  return { dashboard, isLoading };
};
```

---

### API 레이어 요약

| 레이어      | 파일                    | 책임                              | React 의존          |
| ----------- | ----------------------- | --------------------------------- | ------------------- |
| dto         | `*.dto.ts`              | API 입/출력 타입                  | ❌                  |
| service     | `*.service.ts`          | 서버 통신 비동기 메서드           | ❌                  |
| transform   | `*.transform.ts`        | 데이터 의미 변환                  | ❌                  |
| selector    | `*.selector.ts`         | 단일 응답 가공                    | ❌                  |
| query       | `use-*-query.tsx`       | 캐시/비동기 관리(오직 단일 Query) | React Query         |
| composition | `use-*-composition.tsx` | 복수 Query 조합                   | React + React Query |

---

## UI 레이어

### 구조 개요

```
app/                  → 라우팅 엔트리만 (page.tsx, layout.tsx, error.tsx, loading.tsx)
app-config/           → 앱 전역 설정 (Provider, Context, Layout)
page-container/       → 페이지 단위 조립
shared/               → 프로젝트 공통 자산(utils, components, features, hooks 등)
```

> `page.tsx`는 라우팅 엔트리만 담당. 구현은 `page-container/`에 위치.

---

### app/

Next.js App Router 라우팅 엔트리 영역. **라우팅 구조에만 집중한다.**

- `**/page.tsx`: 라우팅 엔트리. `page-container`만 import. 100줄 이내 유지.
- `**/layout.tsx`: `app-config/`의 Provider, Layout을 조합
- `**/loading.tsx`, `**/error.tsx`: app router loading, error 컴포넌트 제공

```tsx
// app/contracts/page.tsx
import { ContractsPageContainer } from '@/page-container/contracts/contracts-page-container';

export default function ContractsPage() {
  return <ContractsPageContainer />;
}
```

---

### app-config/

앱 전역에서 **하나만 존재**하는 설정성 코드. `app/layout.tsx`에서 조합하여 사용한다.

| 폴더         | 역할                                                     |
| ------------ | -------------------------------------------------------- |
| `providers/` | 외부 라이브러리 초기화 (QueryProvider, ClientSession 등) |
| `contexts/`  | 앱 전역 Context (UserContext 등)                         |
| `layouts/`   | 레이아웃 컴포넌트 (MainLayout, LNB 등)                   |

---

### shared/

여러 페이지에서 **반복 재사용**되는 공통 함수, 리액트 컴포넌트, 스타일 등

> `app-config/`과의 차이: `app-config/`은 앱에 하나만 존재하는 전역 설정, `shared/`는 여러 곳에서 반복 import하여 재사용하는 자산.

| 폴더          | api 호출 | 역할                                                    |
| ------------- | :------: | ------------------------------------------------------- |
| `components/` |    ❌    | 업무를 모르는 무관한 순수 UI 컴포넌트 (2곳 이상 재사용) |
| `features/`   |    ✅    | api 호출 포함 공통 기능 단위 (컴포넌트, 훅 포함)        |
| `hooks/`      |    ❌    | api 호출 없는 순수 재사용 훅 (`useDisclosure` 등)       |
| `utils/`      |    ❌    | 순수 타입스크립트 유틸 함수, 클래스                     |
| `styles/`     |    ❌    | 전역 CSS                                                |

> `features/`만 api 호출 허용. 나머지 폴더는 비즈니스 로직, api 직접 호출 금지.

**`features/` 내부 구조**

api 호출이 필요한 공통 기능을 기능 단위로 묶는다. 컴포넌트, 훅 등 해당 기능에 필요한 자산을 자유롭게 구성한다.

```
shared/features/
  search-modal/
    search-modal.tsx             ← container (api query 호출 + 로직)
    search-modal.view.tsx        ← view (렌더링)
    search-modal.module.scss
    use-search-modal.tsx         ← api 호출 포함 훅
```

> 기능 폴더 안에 훅만 존재해도 된다. (컴포넌트 없이 api 호출 훅만 공유하는 경우)

**`hooks/` vs `features/` 판단 기준**

- api 호출이 **없는** 순수 훅 → `shared/hooks/` (예: `useDisclosure`, `useDebounce`)
- api 호출이 **있는** 훅/기능 → `shared/features/`

---

### 컴포넌트 파일 규칙 (`*.tsx` / `*.view.tsx`)

모든 컴포넌트는 container와 view로 분리.

| 파일                  | 역할     | useState | useQuery |   이벤트 핸들러   |
| --------------------- | -------- | :------: | :------: | :---------------: |
| `xxx.tsx` (container) | 로직처리 |    ✅    |    ✅    |        ✅         |
| `xxx.view.tsx` (view) | 렌더링만 |    ❌    |    ❌    | ❌ (props로 수신) |

**container 역할**

- 상태/로직/이벤트 핸들러 구현
- view에 props로 전달

**view 역할**

- 마크업 및 className/styles
- props 렌더링만
- 이벤트 핸들러는 props로만 받기
- container와 1:1 관계는 아니다.

---

### page-container/

페이지 단위 조립 컴포넌트. `page.tsx`의 구현 본체.

- `api/composition`, `api/query` 호출 가능
- `shared/` 사용 가능
- 해당 페이지 전용 컴포넌트는 하위 `components/`에 위치
- 페이지 전용 공유 컴포넌트 필요시 `components/shared/` 하위에 구성 가능
- 기기별 분기가 필요한 경우 `mobile/`, `tablet/` 폴더로 구분
- `_shared/` 폴더는 어느 깊이에서든 구성 가능 (components, hooks, utils). 해당 폴더의 하위 범위에서만 공용으로 사용

```
page-container/
  mobile/                             ← 모바일 전용(필요시)
    _shared/                          ← 모바일 내 공용 자산
      components/
      hooks/
      utils/
    contracts/
      contracts-page-container.tsx ← 페이지 조립
      components/
        contracts-filter/
          contracts-filter.tsx ← index.tsx 사용 가능 (container)
          contracts-filter.view.tsx ← View
          contracts-filter.module.scss
  tablet/                             ← 태블릿 전용(필요시)
    _shared/                          ← 태블릿 내 공용 자산
      components/
      hooks/
      utils/
    contracts/
      contracts-page-container.tsx ← 페이지 조립
      components/
        contracts-filter/
          contracts-filter.tsx ← index.tsx 사용 가능 (container)
          contracts-filter.view.tsx  ← View
          contracts-filter.module.scss
```

---

## 레이어 의존성 규칙

| 레이어                            | 사용 가능                                              | 사용 금지          |
| --------------------------------- | ------------------------------------------------------ | ------------------ |
| `app-config`                      | `shared/`                                              | `page-container`   |
| `page-container`                  | `shared/`, `api/composition`, `api/query` (단일 query) | —                  |
| `shared/components, hooks, utils` | `shared/` 내부                                         | `api/` 직접        |
| `shared/features`                 | `shared/` 내부, `api/`                                 | —                  |
| `api/composition`                 | `query`, `selector`, `transform`, `dto`                | UI 컴포넌트        |
| `api/query`                       | `service`, `dto`, `selector`, `transform`              | —                  |
| `api/selector`                    | `transform`, `dto`                                     | React, React Query |
| `api/transform`                   | 없음 (순수 함수)                                       | React, React Query |
| `api/service`                     | `dto`                                                  | React, React Query |

> 데이터 가공/조합은 반드시 `api/` 안에서만 수행.

---

## 폴더 구조 예시

```
/
  app/                                  # 라우팅 엔트리만
    layout.tsx
    error.tsx
    contracts/
      page.tsx
      loading.tsx
      error.tsx

  app-config/                           # 앱 전역 설정 (하나만 존재)
    providers/                          # 외부 라이브러리 초기화
      query-provider.tsx
      client-session.tsx
    contexts/                           # 앱 전역 Context
      user-context.tsx
    layouts/                            # 레이아웃 컴포넌트
      main-layout.tsx

  api/                                  # API 레이어
    contract/
      contract.dto.ts                   # DTO (백엔드 계약)
      contract.service.ts               # 서버 통신
      contract.transform.ts             # 데이터 의미 변환
      contract.selector.ts              # 단일 응답 가공
      use-contracts-query.tsx            # React Query 훅
      use-contract-composition.tsx      # 멀티 query 조합

  page-container/                       # 페이지 단위 조립
    mobile/                             # 모바일 전용
      _shared/                          # 모바일 내 공용 자산
        components/
        hooks/
        utils/
      contracts/
        contracts-page-container.tsx    # 페이지 조립
        components/
          contracts-filter/
            contracts-filter.tsx        # container
            contracts-filter.view.tsx   # view
            contracts-filter.module.scss
    tablet/                             # 태블릿 전용
      _shared/                          # 태블릿 내 공용 자산
        components/
        hooks/
        utils/
      contracts/
        contracts-page-container.tsx
        components/
          contracts-filter/
            contracts-filter.tsx
            contracts-filter.view.tsx
            contracts-filter.module.scss

  shared/                               # 프로젝트 공통 재사용 자산
    components/                         # 순수 UI 컴포넌트 (api 호출 X)
      data-table/
        data-table.tsx
        data-table.view.tsx
        data-table.module.scss
    features/                           # api 호출 포함 공통 기능
      search-modal/
        search-modal.tsx
        search-modal.view.tsx
        search-modal.module.scss
    hooks/                              # 재사용 커스텀 훅
      use-disclosure.tsx
    utils/                              # 순수 유틸 함수
      format-money.ts
    styles/                             # 전역 CSS
      global.css
```

---

## 네이밍 컨벤션

### API 레이어 파일

| 대상             | 규칙                    | 예시                           |
| ---------------- | ----------------------- | ------------------------------ |
| DTO 파일         | `*.dto.ts`              | `contract.dto.ts`              |
| Service 파일     | `*.service.ts`          | `contract.service.ts`          |
| Transform 파일   | `*.transform.ts`        | `contract.transform.ts`        |
| Selector 파일    | `*.selector.ts`         | `contract.selector.ts`         |
| Query 파일       | `use-*-query.tsx`       | `use-contracts-query.tsx`      |
| Composition 파일 | `use-*-composition.tsx` | `use-contract-composition.tsx` |
| Mock 데이터 파일 | `*.mock.data.ts`        | `contract.mock.data.ts`        |
| Mock 핸들러 파일 | `*.mock.handler.ts`     | `contract.mock.handler.ts`     |

### API 레이어 함수/훅

| 대상           | 규칙                                        | 예시                 |
| -------------- | ------------------------------------------- | -------------------- |
| Transform 함수 | `getXXX`, `isXXX`, `normalizeXXX`, `mapXXX` | `getContractStatus`  |
| Selector 함수  | `selectXXX`                                 | `selectContracts`    |
| Query 훅       | `useXXXQuery`                               | `useContractsQuery`  |
| Composition 훅 | `useXXX`                                    | `useContractSummary` |

### UI 레이어 파일

| 대상               | 규칙              | 예시                                |
| ------------------ | ----------------- | ----------------------------------- |
| Container 컴포넌트 | `xxx.tsx`         | `contract-list-section.tsx`         |
| View 컴포넌트      | `xxx.view.tsx`    | `contract-list-section.view.tsx`    |
| 스타일             | `xxx.module.scss` | `contract-list-section.module.scss` |
