분석 대상 선정 이유
이 프로젝트에서 도메인 무게중심이 가장 큰 기능을 골랐습니다. 시퀀스(이메일 캠페인) 파이프라인은 (a) 외부 provider 다중성(SendGrid·Gmail·SES), (b) 영속/휘발 SSOT 혼재(Postgres + Redis + BullMQ), (c) HTTP 와 워커 두 진입점이 같은 use-case 를 호출, (d) 인시던트 빈도 최상위 — 헥사고날 도입의 ROI 가 가장 명확한 영역입니다.
현 구조 스냅샷
| 영역 | 위치 | 규모 |
|---|---|---|
| HTTP 진입 | routes/sequences.routes.ts | 1272 LOC, 단일 파일 |
| 서비스(분할) | services/sequence-*.service.ts | 20+ 파일 — read/write/query/lifecycle/enrollment/execution/metrics/preview/timezone/copy/naming… |
| Barrel | services/sequence.service.ts | 114줄 re-export only |
| 발송 워커 | workers/bullmq/sequence-email-worker/ | processor + 6 step (pre-check → validate → resolve-lead → verify → resolve-content → send) |
강결합 핫스팟
steps/send-email.ts단일 파일이db(drizzle) + 12개 schema + 20+ service 를 직접 import — fan-in 폭발- 워커와 라우트가 같은 use-case(활성화·발송·진행률)를 서로 다른 진입점에서 직접 DB 로 두 번 구현
- SendGrid·Gmail·SES·Redis throttle·BullMQ 가 비즈니스 로직과 동일 레이어 — provider 추가 시 step 파일 수정 필요
sequence-{read,write,query,lifecycle}가sequences테이블을 각각 접근 — 같은 데이터의 4 곳 query 정의 (SSOT 원칙 위반)
헥사고날 전환 시 이득
| # | 이득 | 구체 효과 |
|---|---|---|
| 1 | Port/Adapter 분리 | EmailSenderPort, SequenceRepository, EnrollmentRepository, ThrottlePort 인터페이스화 → send-email.ts 의 drizzle/SendGrid 직접 호출 제거 |
| 2 | Use-case 단일화 | 활성화·발송·재개 use-case 1 곳 → 라우트와 워커가 동일 application service 호출 |
| 3 | 도메인 순수성 | Sequence, Enrollment, StepExecution 을 framework-free 객체로 → 인시던트(#7980 status preserve, #7501 chain break) 류 invariant 를 도메인 테스트로 사전 차단 |
| 4 | Provider swap 비용 0 | SendGrid ↔ Gmail ↔ SES 가 step 코드 무수정 — 현재는 send-email.ts 의 provider 분기 200+ 줄 |
| 5 | 테스트성 | processor 6 step 을 in-memory adapter 로 결정론적 단위테스트 가능 → 현재 ad-hoc mocking 제거 |
| 6 | CI 게이트 확장 | check:routes 처럼 dependency-cruiser 로 "domain → infrastructure import 금지" lint 강제 → drift 자동 차단 |
| 7 | SSOT 정합 | sequence-{read,write,query,lifecycle} 의 중복 query 를 1 repository 로 수렴 → CLAUDE.md "같은 데이터 1곳" 원칙과 일치 |
| 8 | 워커 안정성 | DelayedError / permanent / transient 분기를 도메인 result type 으로 → processor 의 흩어진 try/catch 정리 |
트레이드오프
| 비용 | 설명 |
|---|---|
| 추상화 오버헤드 | Elysia·BullMQ 가 얇은 layer 라 단순 CRUD 라우트는 over-engineering 위험 → 발송/활성화 같은 복잡 도메인에만 선택 적용 권장 |
| Drizzle 타입 손실 | row type 을 repository 인터페이스로 감싸면 type 추론이 약해짐 → DTO 별도 정의 필요 |
| 점진 마이그레이션 부담 | 1272 LOC 라우트 + 20 services 동시 전환 불가 → strangler 패턴으로 send-email step 부터 권장 |
| 팀 학습 비용 | 신규 팀원이 routes → services → db 가 아닌 ports/adapters 멘탈모델 학습 필요 |
권장 시작점
workers/bullmq/sequence-email-worker/ 만 먼저 hex 화 권장. 도메인 무게중심 가장 큼 + 외부 provider(SendGrid/Gmail/SES) 와 DB 가 동시에 묶여 있어 ROI 최대. 라우트는 마지막.
- Phase 1 —
EmailSenderPort추출, SendGrid/Gmail adapter 분리. send-email.ts 의 provider 분기 제거. - Phase 2 —
SequenceRepository,EnrollmentRepository추출. read/write/query/lifecycle 의 query 중복 수렴. - Phase 3 —
SendSequenceEmailuse-case 정의. 워커 processor 가 use-case 호출. 라우트는 점진 이행. - Phase 4 —
dependency-cruiser룰 추가 → domain → infrastructure import 차단. CI 게이트화.