에이전트 팀 6개 차원 아키텍처 적합성 분석(2026-06-01)에서 도출한 개발 컨벤션·안전수칙. "왜"는 아키텍처 검토, 프로세스는 .moai/memory/development-guide.md. (원본 MD: docs/architecture/development-guide.md)
| 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/5 — shared api-client/ui 부재 → 3앱 복붙(api·auth·error-reporter·version·loginId·i18n) |
| 문서↔실제 | 2/5 — .moai/project 드리프트·codemaps "no source yet" 스테일(본 작업서 교정) |
.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) |
db: DrizzleDB 의존성 주입). 이 패턴을 표준으로 유지.| route | HTTP 파싱 + Zod 검증 + 봉투 + createDb로 db 획득 후 service 호출. ≤50 LOC 지향 |
|---|---|
| service | 비즈니스 로직 + 트랜잭션 경계(db.transaction() 16곳 사용 중). Context 비의존, db 주입받음 |
| db | Drizzle 도메인 스키마(15파일) + 단일 팩토리 createDb(env)(postgres-js/Hyperdrive) |
| 에러 | service에서 DomainError 변환 → 전역 onError가 미지 에러를 generic 500 마스킹(유지) |
| shared-types | 요청/응답/도메인 Zod = SSOT, route 검증·service 시그니처 공용 |
• 유지: DB는 createDb(env)만(로컬 커넥션 금지), 멀티스텝 쓰기는 db.transaction(), 로직은 service, route는 검증/봉투, 미지 에러는 generic 500 마스킹.
• 커넥션 수명주기 ✅해결(2026-06-01): createDb()가 호출마다 새 postgres-js 클라이언트(+풀) 생성·미종료(최대 15회/요청) → env.DB 키 WeakMap 메모이제이션으로 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.
• 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 규약.
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 도메인 어휘(상태·에러) 공유 카탈로그.
• 🔴 중복 순번 정리: 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↔마이그레이션 드리프트 주의.
• 테스트 배선 수정(최우선): 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) 상속.
| P1 🟡 | DB 단일 공용 호스트 SPOF — 백업 실가동·리허설 통과(RPO≤24h), HA는 스케일 시 |
|---|---|
| P2 ✅ | DispatchObject 30s/300s 불일치 — dead DO 제거(v4) |
| P3 ✅ | 매분 cron — 유일 타임아웃 메커니즘(중복 아님) |
| P4 ✅ | 04 문서 미채택 배너 추가 |
| P5 ⏸️ | 공개 read 캐싱 확대 — 보류 |
| N1 ✅ | |
| N2 ✅ | |
| N3 🔽 | 프론트 lib 중복은 주로 error-reporter.ts(85줄 near-dup). 재검증: api.ts·auth.tsx는 앱별 실질 상이, version.ts는 의도적 앱별 → 공유 표면 좁음 (과대평가, 낮음) |
| N4 ✅ | pnpm test 깨짐(shared-types 무테스트 exit 1) 수정(--passWithNoTests) + dead workspace 제거. 594 테스트는 api 글롭으로 전부 실행 중 |
| N5 🔽 | 알림/정산 consumer stub은 설계상 지연(FCM/Kakao=Phase2, Web Push는 이미 동작; settlement는 api 위임). 라이브 결함 아님, DLQ 소비자 부재만 잔존 (낮음) |
| N6 ✅ | |
| N7 🟦 | 의도적 보류: 파일 한도 소폭 초과(bookings 564·auth 521·dispatch.service 606·booking.service 570). 배포된 결제 핵심 코드의 분할은 회귀 위험 대비 이득 낮음 → 향후 기능 변경 시 도메인 자연 분할로 처리 (낮음) |
| N8 ✅ | |
| N9 ✅ | |
| N10 ✅ | |
| N11 ✅ | |
| N12 ✅ | |
| N13 ✅ | |
| N14 ✅ | |
| N15 🟦 | 의도적 보류(트리거: 트래픽↑): 구조화 로깅·requestId — ~9명 트래픽엔 wrangler tail로 충분, 재검증도 "트래픽 증가 시까지 보류" 판정 (낮음) |
| N16 🟦 | 수동 1회 설정(코드 아님): CF 대시보드 Billing→Notifications에서 예산/사용량 알림 설정. 5분 안전장치 (낮음) |