← 가이드 목록 T-Taxi · 개발자

개발 가이드 (Development Conventions)

에이전트 팀 6개 차원 아키텍처 적합성 분석(2026-06-01)에서 도출한 개발 컨벤션·안전수칙. "왜"는 아키텍처 검토, 프로세스는 .moai/memory/development-guide.md. (원본 MD: docs/architecture/development-guide.md)

!한눈에 — 적합성 종합

아키텍처 선택(스택·토폴로지)과 백엔드 구현 모두 적합 — Cloudflare Serverless-First(Next.js@Pages + Hono@Workers + DO + Queues + Cron + Hyperdrive→온프렘 PostgreSQL/PostGIS). 기술부채성 선택 없음.
남은 개선은 구조 교체가 아니라 프론트 공유 패키지화·테스트 배선·마이그레이션 정리·문서 정합성. (API 평가는 코드 재검증 기준 — 초기 자동분석 1개 차원의 허위 결함 보고는 폐기)
API 백엔드4/5 — route→service→db·DI 양호(17 서비스·createDb SSOT·트랜잭션 16곳·DomainError 마스킹). bookings/auth 2파일 한도 초과·요청당 커넥션 정리만 보강
실시간/비동기4/5 — DO Hibernation·큐·DLQ·cron 모범적. 알림/정산 consumer 2개 stub·DLQ 소비자 부재
모노레포/빌드4/5 — 레이아웃·의존 그래프 우수. 테스트 배선(vitest가 api만)·Turbo ^build만 손보면 됨
데이터/마이그레이션3/5 — Drizzle+PostGIS·백업 성숙. 0010 중복 prefix·공간컬럼 text 모델링(push 위험)
프론트 3앱2/5shared api-client/ui 부재 → 3앱 복붙(api·auth·error-reporter·version·loginId·i18n)
문서↔실제2/5 — .moai/project 드리프트·codemaps "no source yet" 스테일(본 작업서 교정)

1실제 토폴로지 (Ground Truth)

.moai/project/structure.md보다 본 절이 우선(드리프트 교정본).
apps/      customer-web · driver-web · admin-web      → CF Pages (Next15+React19)
workers/   api(Hono · Hyperdrive · KV · R2 · Queue×2 · 매분 cron)
           realtime(DO: TRACKING · DRIVER_NOTIFY · ADMIN_NOTIFY)
           consumers/notification · consumers/settlement
           flight-poller(독립 워커, */5 cron)
packages/  shared-types(Zod·타입 SSOT) · web-push(VAPID 발송)
infra/ scripts/ tests/ secrets/ docs/
패키지 소비빌드 없이 소스로(exports→./src, bundler resolution) — stale-dist 버그 제거
DB 접근① Workers=Hyperdrive DB(per-request postgres-js) ② ops/backup=직접 42.127.251.21:35432(age 볼트)
Push자체 Web Push(VAPID) — FCM 아님
Dispatch수락창 300초, 강제=api 매분 cron sweep over dispatch_logs. 구 30s DispatchObject DO는 제거됨(v4)

2계층 규칙 — 현행 패턴 유지·강화

route → service → db 단방향이 이미 구현됨(17개 service + auth/, 16개 route가 import, 서비스는 db: DrizzleDB 의존성 주입). 이 패턴을 표준으로 유지.
routeHTTP 파싱 + Zod 검증 + 봉투 + createDb로 db 획득 후 service 호출. ≤50 LOC 지향
service비즈니스 로직 + 트랜잭션 경계(db.transaction() 16곳 사용 중). Context 비의존, db 주입받음
dbDrizzle 도메인 스키마(15파일) + 단일 팩토리 createDb(env)(postgres-js/Hyperdrive)
에러service에서 DomainError 변환 → 전역 onError가 미지 에러를 generic 500 마스킹(유지)
shared-types요청/응답/도메인 Zod = SSOT, route 검증·service 시그니처 공용

3API 백엔드 — 적합도 4/5

토대 건전: Hono 13라우터·Web Crypto HS256 JWT·auth/role 가드·도메인 Zod·route→service→db+DI·createDb SSOT·트랜잭션 16곳·DomainError 마스킹.

유지: DB는 createDb(env)만(로컬 커넥션 금지), 멀티스텝 쓰기는 db.transaction(), 로직은 service, route는 검증/봉투, 미지 에러는 generic 500 마스킹.
커넥션 수명주기 ✅해결(2026-06-01): createDb()가 호출마다 새 postgres-js 클라이언트(+풀) 생성·미종료(최대 15회/요청) → env.DBWeakMap 메모이제이션으로 isolate당 1 클라이언트 재사용(postgres-js 내부 풀링). 96 호출부 무변경.
파일 한도(소수 초과): bookings.ts 564·auth.ts 521·dispatch.service 606·booking.service 570 → 점진 분할(대부분 route는 한도 내).
정리: drizzle.config postgres:postgres 기본 폴백 제거(fail-fast) + 마이그레이션 연결 TLS.

4실시간 / 비동기 컨벤션

Dispatch 300s를 명시·테스트화: cron sweep가 유일 강제 수단임을 named 상수·주석으로, >300s 만료 테스트 고정. 필요 시 DO alarm/지연 큐 백스톱.
알림 경로 단일화: 동기 전송 금지 → 전부 NOTIFICATION_QUEUE→consumer, DO 소켓은 transport. flight-poller도 직접 push 대신 큐 적재.
DO 표준화: 3 DO 모두 WebSocket Hibernation, TRACKING ~5s 위치는 storage write 배치/샘플링.
큐 멱등성 강제(at-least-once): 알림=booking_id+phase, 정산=period+driver. 정산은 금전 → 재시도 중복 금지. DLQ 규약.

5프론트엔드 — 최우선 개선

최대 약점: shared 프론트 패키지 부재. structure.md가 참조하는 packages/ui·packages/api-client존재하지 않으며 API 클라이언트·인증/토큰·Query 설정·공통 UI가 3앱에 복붙됨(3중 유지보수).

shared 레이어 도입(최고 레버리지): packages/api-client(타입드 fetch + 401→refresh 인터셉터) + packages/ui(공통 컴포넌트·토큰) → 3앱 리팩터.
인증/세션 중앙화: 401→refresh·토큰 저장·base-URL을 1곳에서(보안 수정 단일화).
상태 규약 코드화: TanStack Query 기본값 + query-key 팩토리 + Zustand 패턴 공유 lib.
• i18n 도메인 어휘(상태·에러) 공유 카탈로그.

6데이터 / 마이그레이션 안전수칙

🔴 중복 순번 정리: workers/api/src/db/migrations/0010_settlement_tables·0010_tracking_tables 동일 prefix → 신규 DB 적용 비결정적. (미기록 시) tracking 재번호 + journal 정합 + 스크래치 검증 + CI 중복/갭 검사.
🔴 공간컬럼 raw SQL 전용: drivers.current_location은 DB=GEOGRAPHY but Drizzle=text. generate가 text로 되돌리면 손상 → PostGIS는 raw SQL만, generate에 공간 ALTER 끼면 폐기.
forward-only: 멱등(IF [NOT] EXISTS)·트랜잭션 래핑·스키마변경↔데이터백필 분리. 롤백=백업복원. 0018 grandfather 백필 재실행 금지.
이중 DB 접근(Workers=Hyperdrive DB / ops=직접 host:port). DR: 일일 pg_dump→age→R2(~14, 리허설 통과), RPO≤24h, SPOF→스케일 전 PITR/standby. init.sql↔마이그레이션 드리프트 주의.

7빌드 / 테스트 / 품질 게이트

테스트 배선 수정(최우선): vitest.workspace.ts가 api만 등록 → 글롭으로 확장 + realtime/consumers/apps에 test 스크립트. 엔트리포인트 1개로 표준화 후 TRUST 85% 신뢰.
Turbo 정리: test/typecheck의 dependsOn:['^build'] 제거(소스 소비라 불필요·CI 지연만). worker build 캐싱 의도 명확화.
• 린트/포맷 SSOT = 루트 biome.json. root tsconfig(strict+noUncheckedIndexedAccess) 상속.

8알려진 이슈 (P1–P5 + N1–N16)

N13~N16 = 2026 추천 설계서 v2(적대적 재검증)의 "지금 할 일". 그 문서의 7개 ADD(Workflows·전면 OTel·패스키·LLM 등)는 9명 규모엔 조기 최적화로 보류/폐기. N13·N14 구현 완료, N15·N16은 의도적 보류(표 하단 마무리 결정 참조).
P1 🟡DB 단일 공용 호스트 SPOF — 백업 실가동·리허설 통과(RPO≤24h), HA는 스케일 시
P2 ✅DispatchObject 30s/300s 불일치 — dead DO 제거(v4)
P3 ✅매분 cron — 유일 타임아웃 메커니즘(중복 아님)
P4 ✅04 문서 미채택 배너 추가
P5 ⏸️공개 read 캐싱 확대 — 보류
N1 ✅마이그레이션 중복 prefix해결(2026-06-01): tracking을 0021로 재번호 + IF NOT EXISTS
N2 ✅공간컬럼 Drizzle text해결(2026-06-01): geographyPoint customType + db:push 차단
N3 🔽프론트 lib 중복은 주로 error-reporter.ts(85줄 near-dup). 재검증: api.ts·auth.tsx는 앱별 실질 상이, version.ts는 의도적 앱별 → 공유 표면 좁음 (과대평가, 낮음)
N4 ✅vitest 배선해결(2026-06-01): pnpm test 깨짐(shared-types 무테스트 exit 1) 수정(--passWithNoTests) + dead workspace 제거. 594 테스트는 api 글롭으로 전부 실행 중
N5 🔽알림/정산 consumer stub은 설계상 지연(FCM/Kakao=Phase2, Web Push는 이미 동작; settlement는 api 위임). 라이브 결함 아님, DLQ 소비자 부재만 잔존 (낮음)
N6 ✅API createDb 다중 호출·미종료해결(2026-06-01): env.DB 키 WeakMap 메모이제이션(isolate당 1 클라이언트, 96 호출부 무변경)
N7 🟦의도적 보류: 파일 한도 소폭 초과(bookings 564·auth 521·dispatch.service 606·booking.service 570). 배포된 결제 핵심 코드의 분할은 회귀 위험 대비 이득 낮음 → 향후 기능 변경 시 도메인 자연 분할로 처리 (낮음)
N8 ✅커버리지 게이트 누수사실무근: 594 테스트/43 파일이 api 단일 글롭으로 전 영역 실행. .moai 문서 드리프트는 교정 완료
N9 ✅codemaps/overview.md 스테일해결(2026-06-01): 실제 토폴로지로 재작성
N10 ✅Turbo ^build 의존해결(2026-06-01): test/typecheck에서 제거(typecheck 10/10·test 2/2 통과)
N11 ✅api wrangler 주석 '30s' 잔재해결(2026-06-01): 모순·중복 cron 주석 단일 정확본 정리(cron 백스톱은 권장 잔존)
N12 ✅drizzle.config 폴백/TLS해결(2026-06-01): 원격 URL에 sslmode= 강제 가드, postgres:postgres는 dev-only 명시
N13 ✅dispatch 타임아웃 SSOT 회귀 테스트해결(2026-06-01): UI 상수를 lib/dispatch-timing.ts로 추출, accept-window-ssot.test.ts가 STALE_DISPATCH_SECONDS==COUNTDOWN_SECONDS 단언(머테이션 검증·596/596 통과)
N14 ✅KV config:pricing silent-fallback 알림해결(2026-06-01): 두 폴백 지점에 미시딩=warn(UNSEEDED)·파싱실패=error(UNPARSEABLE), 반환값 불변. pricing-fallback-alert.test.ts 머테이션 검증·599/599 통과
N15 🟦의도적 보류(트리거: 트래픽↑): 구조화 로깅·requestId — ~9명 트래픽엔 wrangler tail로 충분, 재검증도 "트래픽 증가 시까지 보류" 판정 (낮음)
N16 🟦수동 1회 설정(코드 아님): CF 대시보드 Billing→Notifications에서 예산/사용량 알림 설정. 5분 안전장치 (낮음)
마무리 결정(2026-06-01): "지금 할 일" 중 진짜 가치였던 N13(과거 2회 재발한 dispatch 버그 봉인)만 구현, N14(과거 인시던트 관측성)는 함께 처리. N15·N16은 9명 규모엔 조기 최적화라 의도적 보류 — 트래커 소진보다 실제 사용자 가치(기능·실사용 버그)에 시간 쓰는 것이 합당. 트래픽·사용자 증가 측정 시 N15부터 재개.