규칙
구조
구조
pnpm 워크스페이스 하나에 서버 · 클라이언트를 여럿 둔다. 서버 · 클라이언트는 언어 · 프레임워크가 달라도 되고,
서로를 잇는 건 contract/ 의 언어 중립 계약뿐이다: 클라이언트 ↔ 서버는 HTTP/JSON(각 서버가 코드에서 만든
openapi/<서버>.json), 서버 ↔ 서버는 gRPC(공통 proto/). 계약은 contracts.md.
왜 이렇게 나눴는지는 concepts/structure.md.
contract/
- 서버가 코드에서 만든 HTTP API 문서 · 그 TS 타입 (pnpm gen, 직접 고치지 않음)
- @app/contract: 서버별 네임스페이스 (notes · tags · activity)
- 서버 ↔ 서버 gRPC 계약 (기준). 디렉터리 = 패키지
- buf lint(STANDARD) · breaking(FILE) · TS 코드 생성 설정
- proto 의 TS 코드 (@app/contract/gen/..., 직접 고치지 않음)
servers/
- 메모, 앱의 진입점
- 활동 기록
- 태그 규칙 · 태그 매기기
- 앱의 진입점: /api/<접두사> 를 서버로
clients/
apps/
- 웹 앱 (React)
- 관리 앱 (Vue)
- 공개 사이트 (Next)
- 가벼운 웹 앱 (순수 TS)
- 모바일 앱 (Expo)
- 데스크톱 앱 (Tauri)
- 각 앱이 서버의 타입으로 API 를 호출하는 공통 클라이언트입니다.
- 정적 웹 파일을 제공하고 /api 요청을 서버로 전달합니다.
- 클라이언트 앱과 패키지가 공유하는 린트 · 포맷 설정입니다.
- Postgres 와 전체 스택의 실행 환경을 모읍니다.
- 구조 · 계약 · 테스트 · 개발 규칙과 작업 절차, 설계 결정을 기록합니다.
자동화 · 설정
- CI 워크플로와 PR 템플릿을 둡니다.
- rename.mjs(이름 바꾸기, 한 번 쓰고 지워진다) · drop.mjs(쓰지 않는 예시 서버 · 앱 빼기)
- 색 · 글자 · 간격 · 컴포넌트의 기준입니다.
- 에이전트의 리뷰 · 검증 · 테스트 절차를 둡니다.
- 에이전트 설정과 편집 hook 을 둡니다.
- 패키지 목록 · 의존성 카탈로그 · 설치 정책을 관리합니다.
- TypeScript 패키지가 공유하는 설정입니다.
각 서버 · 클라이언트의 세부는 그 폴더의 AGENTS.md.
예시 서버 셋은 맡은 일이 다르고, 메모를 쓰는 요청 하나에 함께 움직인다. 앱은 gateway 하나만 알고, 서버끼리는 gateway 를 거치지 않고 gRPC 로 부른다.
- 앱 → gateway HTTP /
api/* - gateway → notes /
api/ notes - notes → tags gRPC 저장 전에 태그를 묻는다
- notes → activity gRPC 저장 뒤 기록을 남긴다
- gateway → tags /
api/ admin: 태그 규칙tags - gateway → activity /
api/ admin: 활동 기록activity
폴더 이름은 맡은 일이고 구현 언어는 그 서버의 선택이다. 같은 일을 언어마다 어떻게 쓰는지는 languages.md.
서버 레이어
모든 서버가 같은 레이어로 나누고, 바깥에서 안으로만 부른다.
HTTP 라우트 · gRPC 서비스 · 보안을 담당합니다. 요청을 검증하고 domain 을 호출합니다.
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).
서버 공통 규칙
언어가 달라도 다음은 같게 한다 (클라이언트가 서버를 가리지 않게). 서버마다 어디서 지키는지는 그 서버 AGENTS.md 의 “규칙”:
package.json에dev·test·gen·lint·format(+ 있으면typecheck) 스크립트. JS 가 아닌 서버는 자기 도구를 감싼다. 그래서 루트의pnpm -r이 모든 서버에 돈다.- lint · format · 의존성 버전은 서버마다 자기 언어 도구 · 자기 설정. 루트에 언어별 설정을 두지 않는다. 클라이언트는 공통 설정 · 카탈로그 (clients.md).
- 포트는
PORT(HTTP) ·GRPC_PORT(gRPC). 서버를 여럿 띄우면 서로 다르게 (development.md 의 포트 표). 부르는 다른 서버의 주소는<서버>_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). 서버마다 자기 DB(<서버>, 테스트<서버>_test)를 갖고, 다른 서버의 DB 를 직접 읽지 않는다(gRPC 로 부른다). 연결은PGHOST·PGPORT·PGDATABASE·PGUSER·PGPASSWORD. - 서버마다
Dockerfile(development.md 의 “이미지”). - 앱이 부르는 서버는 경로 접두사(
/api/<자원>)가 서버마다 겹치지 않게 하고, gateway 의 경로 표에 한 줄을 더한다. - gateway 는 위 규칙 중
gen· DB 가 없다. 자기 API 가 없어 문서를 만들 것이 없다.
클라이언트
모두 JS/TS 이고 @app/client 의 createClient<서버.paths>() 로 서버를 부른다. 주소는 gateway 하나다(API_URL). 서버 데이터는 TanStack Query,
의존성 버전은 카탈로그. 공통 규칙은 clients.md, 앱마다의 것은 그 앱의 AGENTS.md.