엔지니어링 가이드
서버 추가
서버 추가
이름은 맡은 일로, 언어는 무엇이든 된다. 예시 서버 셋(notes · Node, tags · FastAPI, activity · Spring) 중 가까운 것을 본뜨고 아래 연결 지점을 채운다. 뺄 때는 같은 목록을 거꾸로.
1. 폴더와 스크립트
servers/<이름>/package.json 에 dev · test · gen · lint · format · typecheck 를 둔다. JS 가 아니면 자기 도구를 감싼다
(activity 는 ./gradlew, tags 는 uv run). 그래서 루트의 pnpm test · pnpm lint 가 새 서버에도 돈다.
포트는 development.md 의 표와 겹치지 않게 고른다. 바인딩은 HOST, 기본 127.0.0.1.
2. 레이어와 공통 규칙
api → domain → storage · clients 로 나누고 바깥에서 안으로만 부른다. 쓰기(POST · PUT)는 JSON 본문만, 응답 코드와 오류 { "error" } (400 · 404 · 405 · 502) 는
다른 서버와 같게 한다. → architecture.md
3. 계약
gen스크립트가 서버 코드에서contract/openapi/<이름>.json을 쓴다. DB 없이 돌아야 한다(pnpm typecheck도 gen 을 돌린다).- 루트
package.json의gen과contract/index.ts에 한 줄씩 더한다:export type * as <이름> from './openapi/<이름>.ts'. - gRPC 를 쓰면
contract/proto에서 코드를 만든다. proto 를 복사하지 않는다.
4. DB
docker/postgres/init.sql 에 두 줄을 더한다.
CREATE DATABASE <이름>;
CREATE DATABASE <이름>_test;
이 파일은 볼륨을 처음 만들 때만 돈다. 이미 띄운 적이 있으면 같은 문장을 직접 실행한다.
연결은 PGHOST · PGPORT · PGDATABASE · PGUSER · PGPASSWORD, 테스트는 PGDATABASE 만 <이름>_test 로 바꾼다.
표는 그 언어의 마이그레이션 도구로 만든다. 다른 서버처럼 서버가 요청을 받기 전에 적용하고(테스트도 서버를 띄우며 같이), gen 은 DB 없이 돈다.
테스트의 DB 정리는 그 도구의 적용 기록 표를 남긴다. 도구와 파일 위치는 database.md 의 표에 한 줄 더한다.
5. 테스트
실제 서버를 랜덤 포트로 띄워 HTTP · gRPC 로 부르고, 테스트마다 DB 를 비운다. 외부 클라이언트만 Fake 로 바꾼다. → testing.md
6. 이미지 · 스택
-
servers/<이름>/Dockerfile: 컨텍스트는 그 폴더, 공유 파일은COPY --from=workspace, 실행 사용자는 root 가 아니게. → development.md -
docker/docker-compose.yaml에 서비스를 더한다.<이름>: <<: *server build: { context: ../servers/<이름>, additional_contexts: { workspace: .. } } ports: ["127.0.0.1:${<이름>_PORT:-8003}:8003"] -
앱이 이 서버를 부르면 gateway 의 경로 표(
servers/gateway)에 접두사 한 줄을 더하고, compose 의 gateway 에<이름>_URL을 준다. 접두사는 다른 서버와 겹치지 않게. -
docker/smoke.sh에 그 서버의 덩어리(# <이름>부터 빈 줄까지)를 더한다. 다른 서버를 부르는 흐름이 있으면 그 흐름이 이어지는지 하나만 본다.
7. 지시 파일
servers/<이름>/AGENTS.md 에 구조 · 명령 · 규칙 · 테스트 방법을 쓰고 ln -s AGENTS.md CLAUDE.md.
이름을 바꿀 때
폴더 이름은 맡은 일이다(notes · tags · activity). 예시를 본떠 다른 일을 맡기면 이름부터 바꾼다(예: notes → orders).
구현 언어 · 프레임워크는 이름에 넣지 않는다. 그건 그 폴더의 AGENTS.md 가 말한다.
-
폴더를 옮긴다:
git mv servers/notes servers/orders -
이름이 들어간 곳을 찾아 바꾼다. 서버 이름은 패키지 · 계약 · DB · compose · smoke · 환경 변수 · hook · 그 서버를 부르는 앱과 서버에 들어간다.
n=notes; N=NOTES # 바꿀 이름 git grep -n -E "@app/${n}|servers/${n}|${n}\.paths|openapi/${n}\.|'${n}'|\"${n}\"|${n}_test|${N}_(PORT|GRPC)|//${n}:|\[${n}[],]|, ${n}\]|^ ${n}:|DATABASE ${n}|type ${n}[ ,}]|as ${n} " \ -- ':!pnpm-lock.yaml' ':!**/uv.lock' ':!*.md' ':!contract/openapi/*'같은 단어의 필드 · CSS 클래스(
tags: string[]·className = 'tags')는 서버 이름이 아니니 그대로 둔다.scripts/drop.mjs의SERVERS목록도 바꾼다. -
루트에서 확인한다:
pnpm install(lockfile) → 예전contract/openapi/<이름>.*를 지우고pnpm gen→pnpm typecheck·pnpm lint·pnpm test→node scripts/drop.mjs --check. -
이미 띄운 Postgres 에는 새 이름의 DB 가 없다(
init.sql은 볼륨을 처음 만들 때만 돈다). 데이터를 옮기려면docker compose -f docker/docker-compose.middleware.yaml exec db psql -U app -d postgres -c 'ALTER DATABASE notes RENAME TO orders', 테스트 DB 는notes_test→orders_test. -
설명은
git grep -n -w notes -- '*.md'로 찾아 고친다. API 경로(/api/notes)는 자원 이름이라 서버 이름과 따로 정한다.
서버를 뺄 때
pnpm drop <이름> 이 같은 목록을 거꾸로 지운다: 폴더, 루트 gen 과 contract/index.ts 의 줄, contract/openapi/<이름>.*, init.sql, compose, smoke.
그 서버를 부르는 앱(<이름>.paths) · 서버(<이름>_GRPC)가 남아 있으면 멈춘다.
→ development.md. 새 서버를 템플릿에 더했다면 scripts/drop.mjs 의 목록에도 더한다.