# 구조

pnpm 워크스페이스 하나에 서버 · 클라이언트를 여럿 둔다. 서버 · 클라이언트는 언어 · 프레임워크가 달라도 되고,
서로를 잇는 건 `contract/` 의 언어 중립 계약뿐이다: 클라이언트 ↔ 서버는 HTTP/JSON(각 서버가 코드에서 만든
`openapi/<서버>.json`), 서버 ↔ 서버는 gRPC(공통 `proto/`). 계약은 [contracts.md](/reference/contracts/).
왜 이렇게 나눴는지는 [concepts/structure.md](/concepts/structure/).

```
template/
├── contract/
│   ├── openapi/<서버>.json · .ts     # 서버가 코드에서 만든 HTTP API 문서 · 그 TS 타입 (`pnpm gen`, 직접 고치지 않음)
│   ├── index.ts                      # @app/contract: 서버별 네임스페이스 (notes · tags · activity)
│   ├── proto/app/{도메인}/v1/*.proto  # 서버 ↔ 서버 gRPC 계약 (기준). 디렉터리 = 패키지
│   ├── buf.yaml · buf.gen.yaml       # buf lint(STANDARD) · breaking(FILE) · TS 코드 생성 설정
│   └── gen/                          # proto 의 TS 코드 (@app/contract/gen/..., 직접 고치지 않음)
├── servers/                  # 서버 하나 = 폴더 하나, 이름은 맡은 일. 서버마다 자기 언어 · API · 도구를 갖는다
│   ├── notes/                # 메모, 앱의 진입점 — Node 24 · zod · Connect-ES (:8000)
│   ├── activity/             # 활동 기록 — Spring Boot 4.1 · Java 25 · Gradle 멀티 모듈 (:8001 · gRPC :9001)
│   ├── tags/                 # 태그 규칙 · 태그 매기기 — FastAPI · Python 3.12 · uv (:8002 · gRPC :9002)
│   └── gateway/              # 앱의 진입점: /api/<접두사> 를 서버로 — Go 표준 라이브러리 (:8003)
├── clients/                  # 앱 여섯과 공용 API 클라이언트. lint · format 설정은 클라이언트 전체가 하나
│   ├── apps/
│   │   ├── web/              # 웹 앱 (React)
│   │   ├── admin/            # 관리 앱 (Vue)
│   │   ├── site/             # 공개 사이트 (Next)
│   │   ├── lite/             # 가벼운 웹 앱 (순수 TS)
│   │   ├── mobile/           # 모바일 앱 (Expo)
│   │   └── desktop/          # 데스크톱 앱 (Tauri)
│   ├── packages/client/      # @app/client: 서버가 만든 타입으로 API 를 부르는 공용 fetch
│   ├── nginx.conf.template   # 웹 앱 이미지: 정적 파일을 내주고 /api 를 넘긴다
│   └── .oxlintrc.json · .oxfmtrc.json  # 클라이언트 공통 lint · format 설정
├── docker/                   # 인프라(Postgres) · 전체 스택 compose · smoke.sh. 개발 · 테스트 · CI 가 같이 쓴다
├── .github/                  # CI (workflows/ci.yml) · PR 템플릿
├── scripts/                  # rename.mjs(이름 바꾸기, 한 번 쓰고 지워진다) · drop.mjs(쓰지 않는 예시 서버 · 앱 빼기)
├── docs/                     # 규칙 · 이해하기(concepts/) · 절차(guides/) · 설계 결정(decisions/)
├── DESIGN.md                 # UI 의 기준: 색 · 글자 · 간격 · 컴포넌트 토큰과 규칙 (DESIGN.md 형식)
├── .agents/skills/           # 에이전트 skill (.claude/skills 는 이 폴더로 가는 링크)
├── .claude/                  # Claude Code 설정 · hook (settings.json, hooks/). 개인 설정은 settings.local.json (gitignore)
├── pnpm-workspace.yaml       # 패키지 목록 · 클라이언트 의존성 카탈로그 · 설치 스크립트 허용 목록
└── tsconfig.base.json        # TS 패키지 공통 설정
```

각 서버 · 클라이언트의 세부는 그 폴더의 `AGENTS.md`.

예시 서버 셋은 맡은 일이 다르고, 메모를 쓰는 요청 하나에 함께 움직인다. 앱은 gateway 하나만 알고, 서버끼리는 gateway 를 거치지 않고 gRPC 로 부른다.

```
앱 ──HTTP /api/*──▶ gateway (Go) ──▶ /api/notes    → notes (Node) ──gRPC──▶ tags (FastAPI)       저장 전에 태그를 묻는다
                                 │                               └───gRPC──▶ activity (Spring)  저장 뒤 기록을 남긴다
                                 ├──▶ /api/tags     → tags          (admin: 태그 규칙)
                                 └──▶ /api/activity → activity      (admin: 활동 기록)
```

폴더 이름은 맡은 일이고 구현 언어는 그 서버의 선택이다. 같은 일을 언어마다 어떻게 쓰는지는 [languages.md](/reference/languages/).

## 서버 레이어

모든 서버가 같은 레이어로 나누고, 바깥에서 안으로만 부른다.

```
api (HTTP 라우트 · gRPC 서비스 · 보안) → domain (업무 로직, HTTP · gRPC 공용) → storage (DB) · clients (외부 시스템 하나당 하나)
support: 오류 형식 · 라우트 도구처럼 레이어와 무관한 것
```

| 레이어  | notes · Node (`src/`)     | activity · Spring (모듈)                                       | tags · FastAPI (`server/`) |
| ------- | ------------------------- | -------------------------------------------------------------- | -------------------------- |
| 진입점  | `main.ts`                 | `core/core-api` `CoreApiApplication`                           | `main.py` (uvicorn)        |
| api     | `api/http/`               | `core/core-api` `app.core.api.{controller,grpc}`               | `api/routes/` · `api/rpc/` |
| domain  | `domain/`                 | `core/core-api` `app.core.domain.*`                            | `domain/`                  |
| storage | `storage/` (postgres.js) · `migrations/` (node-pg-migrate) | `storage/db-core` (JdbcClient + Flyway) | `storage/` (psycopg 3) · `migrations/` (Alembic) |
| clients | `clients/` (tags · activity 의 gRPC) | `clients/client-*` 모듈 (생기면)                    | `clients/` (생기면)        |
| support | `support/`                | `core/core-api` `app.core.support.*`                           | `support/`                 |

- domain 이 storage 를 그대로 넘기기만 하는 위임도 레이어라서 둔다.
- gateway 는 이 레이어의 예외다(`servers/gateway/AGENTS.md`). 업무 로직 · DB · 자기 API 가 없고 경로 표 하나로 요청을 넘긴다. 업무가 생기면 그 일을 맡은 서버에 둔다.
- clients 는 밖에 인터페이스(교체 지점)만 보이고 구현은 숨긴다. 테스트에서는 Fake 로 바꾼다 ([testing.md](/reference/testing/)).

## 서버 공통 규칙

언어가 달라도 다음은 같게 한다 (클라이언트가 서버를 가리지 않게). 서버마다 어디서 지키는지는 그 서버 `AGENTS.md` 의 "규칙":

- `package.json` 에 `dev` · `test` · `gen` · `lint` · `format` (+ 있으면 `typecheck`) 스크립트. JS 가 아닌 서버는 자기 도구를
  감싼다. 그래서 루트의 `pnpm -r` 이 모든 서버에 돈다.
- lint · format · 의존성 버전은 서버마다 자기 언어 도구 · 자기 설정. 루트에 언어별 설정을 두지 않는다.
  클라이언트는 공통 설정 · 카탈로그 ([clients.md](/reference/clients/)).
- 포트는 `PORT`(HTTP) · `GRPC_PORT`(gRPC). 서버를 여럿 띄우면 서로 다르게 ([development.md](/reference/development/) 의 포트 표).
  부르는 다른 서버의 주소는 `<서버>_GRPC`(예: notes 의 `TAGS_GRPC`).
- 바인딩 주소는 `HOST`, 기본 127.0.0.1 (HTTP · gRPC 같이). 컨테이너 등 밖에 열 때는 `HOST=0.0.0.0`.
  예시 서버에는 인증이 없으니 밖에 열기 전에 인증을 붙이고 gRPC 는 TLS(mTLS)로.
- 쓰기(POST · PUT)는 JSON 본문만 받는다 (form 같은 단순 요청은 다른 사이트에서 몰래 보낼 수 있다). DELETE 는 본문 없이. 입력은 신뢰 경계:
  문서에 적힌 필드 · 타입만 받고 모르는 필드는 버린다.
- 응답 코드: 만들기 201(만든 것을 돌려준다), 지우기 204(본문 없음), 나머지 200. 오류는 `{ "error": "..." }`: 잘못된 입력 400, 없는 자원 · 라우트 404, 다른 메서드 405, 서버 · 다른 서버 · 외부 서비스 오류 502.
- 다른 서버 · 외부 서비스가 실패할 때 어떻게 할지는 호출마다 정해 그 서버 `AGENTS.md` 에 적는다. 예시: notes 는 태그 없이는 저장하지 않으니
  tags 실패면 502, 활동 기록은 저장을 막을 이유가 없으니 activity 실패면 경고만 남기고 응답한다.
- DB 는 Postgres (`docker/docker-compose.middleware.yaml`, ADR-0004, 바꾸려면 [guides/database.md](/guides/database/)). 서버마다 자기 DB(`<서버>`, 테스트 `<서버>_test`)를 갖고,
  다른 서버의 DB 를 직접 읽지 않는다(gRPC 로 부른다). 연결은 `PGHOST` · `PGPORT` · `PGDATABASE` · `PGUSER` · `PGPASSWORD`.
- 서버마다 `Dockerfile` ([development.md](/reference/development/) 의 "이미지").
- 앱이 부르는 서버는 경로 접두사(`/api/<자원>`)가 서버마다 겹치지 않게 하고, gateway 의 경로 표에 한 줄을 더한다.
- gateway 는 위 규칙 중 `gen` · DB 가 없다. 자기 API 가 없어 문서를 만들 것이 없다.

## 클라이언트

모두 JS/TS 이고 `@app/client` 의 `createClient<서버.paths>()` 로 서버를 부른다. 주소는 gateway 하나다(`API_URL`). 서버 데이터는 TanStack Query,
의존성 버전은 카탈로그. 공통 규칙은 [clients.md](/reference/clients/), 앱마다의 것은 그 앱의 `AGENTS.md`.