규칙

구조

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

template/폴더 구조
  • 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 로 부른다.

앱
gatewayGo
notesNode
tagsFastAPI
activitySpring
  1. 앱 → gateway HTTP /api/*
  2. gateway → notes /api/notes
  3. notes → tags gRPC 저장 전에 태그를 묻는다
  4. notes → activity gRPC 저장 뒤 기록을 남긴다
  5. gateway → tags /api/tags admin: 태그 규칙
  6. gateway → activity /api/activity admin: 활동 기록

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

서버 레이어

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

서버 안의 호출 방향
storageDB데이터 저장과 조회
clients외부 시스템교체 가능한 연결 지점
1 / 3

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.

GitHub 에서 고치기