코드를 박은 다음 날 — 4,658줄 DDD 문서를 24분 사이에 다시 쓴 하루
📚 교육용 풀스택 SaaS 개발기 시리즈 (23편)
v2.0 코드 마이그레이션이 끝난 직후, 같은 날 밤 24분 사이에 두 개의 docs 커밋이 들어갔다. v2.0 DDD 문서들은 _archived/v2.0/으로 묻혔고, 마스터 문서가 v2.1로 진화하면서 1,354줄이 갈아 끼워졌고, 그 위에 새 DDD 문서 4,658줄이 새로 적혔다. 코드와 문서를 다른 트랙으로 두지 않기 위해 무엇을 묶었고 무엇을 일부러 묻었는지의 기록.
💡 Tip. 바쁜 현대인들을 위한 본문 요약
- v2.0 코드 마이그레이션을 닫은 다음 날, 같은 밤에 두 개의 docs 커밋이 들어갔다.
56d5b80f(21:50)와f37da73a(22:14), 24분 간격이다- v2.0 DDD 문서 10개는 v2.1로 점진 업데이트하지 않고 통째로
_archived/v2.0/로 옮겼다. 옛 진실을 묘비처럼 남겨두기 위해서다- 새 DDD 문서는 4,658줄.
bounded-contexts.md750줄,aggregates.md1,090줄,repositories.md911줄,domain-events.md712줄에 use-case 6편 1,195줄- 마스터 문서는 567줄이 갈아 끼워졌고
CLAUDE.md는 260줄이 추가됐다. “어떤 문서를 먼저 읽어야 하는가”를 6단계 우선순위로 명문화하는 작업이었다- 점진 업데이트 대신 통째 재작성을 택한 이유는 변경 폭이 너무 넓었기 때문이다 — TOP1~5 메트릭, 복습 모듈, 사용자 선택형 콘텐츠, PersonalRecord 삭제
🗺️ 이 편의 자리 — 코드를 닫자마자 문서를 여는 단계
지난 편에서 v2.0의 마지막 칸인 ApplicationModule 39줄을 채우고 닫았다. 컨트롤러부터 리포지토리까지 다섯 칸의 길이 트여 있었고, 첫 v2 엔드포인트 POST /v2/assignments/:id/bundles가 비로소 사용 가능한 상태가 됐다.
문제는 그 시점의 문서들이었다. 코드는 v2.0인데 DDD 문서는 v1.1이었고, 마스터 문서는 v2.0인데 그 위에 v2.1 변경 결정 4건이 누적돼 있었다. 코드가 박힌 다음 날인 1월 7일 밤, 그 차이를 한 번에 좁히는 두 커밋이 들어갔다.

📌 핵심: v2.0 마이그레이션은 “코드까지 닫은 날”이 끝이 아니다. 다음 날 SSoT(Single Source of Truth) 위계를 다시 세우는 것까지가 한 사이클이다. 그 사이클을 이번 편이 닫는다.
| 시각 | 커밋 | 본질 |
|---|---|---|
| 21:50 | 56d5b80f docs: v2.1 문서 정비 및 아키텍처 최종 확정 | 마스터 문서 갈아 끼우기 + 옛 DDD 아카이빙 |
| 22:14 | f37da73a docs: DDD 문서 v2.1 전면 재작성 | 빈 자리에 새 DDD 4,658줄 박기 |
24분 사이의 두 커밋이지만 역할이 명확히 다르다. 앞쪽은 무엇을 SSoT로 모실지를 결정했고, 뒤쪽은 그 결정 위에 살이 붙는 문서를 새로 깔았다.
🧹 첫 커밋 — v2.0 DDD를 무덤으로 보내고 마스터를 v2.1로 끌어올리기
56d5b80f의 변경 통계는 19개 파일에 1,354줄 insert, 289줄 delete다. 숫자만 보면 “리팩토링 한 건”이지만 실제로는 세 가지 결단이 한 커밋에 묶여 있다.
결단 1 — v2.0 DDD를 통째로 _archived/v2.0/로 옮긴다
docs/{ => _archived/v2.0}/V2_MIGRATION_ROADMAP.md
docs/{ => _archived/v2.0}/V2_MODULE_MIGRATION_PLAN.md
docs/{ => _archived/v2.0}/domain/aggregates.md
docs/{ => _archived/v2.0}/domain/bounded-contexts.md
docs/{ => _archived/v2.0}/domain/domain-events.md
docs/{ => _archived/v2.0}/domain/repositories.md
docs/{ => _archived/v2.0}/domain/use-cases/01-student-onboarding.md
docs/{ => _archived/v2.0}/domain/use-cases/02-bundle-and-learning.md
docs/{ => _archived/v2.0}/domain/use-cases/03-level-adjustment.md
docs/{ => _archived/v2.0}/domain/use-cases/04-batch-processes.md
docs/{ => _archived/v2.0}/domain/use-cases/05-new-record-challenge.md
docs/{ => _archived/v2.0}/domain/use-cases/06-attendance.md
10개 파일이 한 번에 묻혔다. 점진 업데이트로 v2.0 → v2.1을 끌어올리는 길도 있었지만 일부러 그 길을 안 갔다. 변경 폭이 컸기 때문이다.
⚠️ 주의: 점진 업데이트가 더 좋은 선택이 아니다. 변경 단어가 4~5개 핵심 개념에 걸치면 diff가 곳곳에서 엉킨다. 이 정도가 되면 “흔적을 남긴 한 번의 재작성”이 “흔적이 사방에 남는 누적 패치”보다 훨씬 가독성이 높다.
이번 차례에서 갈아엎힌 핵심 개념은 4개였다.
| v2.0 → v2.1 | 변경 폭 |
|---|---|
| 레벨링 모듈 → 복습 모듈 (90%도 복습 필수, 1회만 적용) | 모듈 명칭 + 발동 조건 |
| 강점/약점/평균 지표 → TOP1~5 지표 체계 | 분류 알고리즘 자체 변경 |
| 콘텐츠 자동 선택 → 진행 시점 5개 후보 → 사용자가 2개 중 택1 | UC 흐름 + UI 노출 모두 변경 |
PersonalRecord 삭제 → ContentAttempt에서 MAX 조회 | 테이블 한 개 통째로 삭제 |
이 4개가 동시에 걸리면 aggregates.md, repositories.md, bounded-contexts.md, domain-events.md가 전부 흔들린다. 점진 업데이트로 4번 손대는 것보다 통째로 다시 쓰는 게 짧고, 무엇보다 “지금 이 문서가 v2.1 기준이다”라고 한 번에 선언할 수 있어 운영자(미래의 나)가 안 헷갈린다.
결단 2 — 00_프로젝트_마스터_v2.0.md를 v2.1로 진화시킨다
마스터 문서는 567줄이 갈아 끼워졌다(289줄 삭제 + 567줄 insert 중 마스터 분량). 파일 이름도 _v2.0.md → _v2.1.md로 바뀌었다. 단순 패치가 아니라 버전 명세를 따라가는 SSoT로서의 정체성을 갱신한 변경이다.
-> **버전**: v2.0 (번들 기반 학습 시스템)
-> **최종 업데이트**: 2025-01-06
+> **버전**: v2.1 (복습 모듈 + 사용자 콘텐츠 선택)
+> **최종 업데이트**: 2025-01-07
마스터 문서의 ‘핵심 가치’ 절이 한 줄 늘어난 것도 같은 맥락이다.
- **완전 자동화**: 과제 생성, 레벨 조정, 지표 분석 자동화
- **개인화**: 강점/약점 지표 기반 맞춤 학습
- - **실시간 적응**: 번들 내 레벨링 모듈로 동적 난이도 조정
+ - **실시간 적응**: 번들 내 복습 모듈로 동적 난이도 조정
+ - **데이터 중심**: 모든 학습 기록을 빅데이터/로깅 목적으로 완전 보존
데이터 중심이 한 줄 추가된 건 PersonalRecord를 삭제하면서 “그래도 모든 시도(ContentAttempt)는 영구 보존한다”는 원칙이 새로 생겼기 때문이다. 비즈니스 가치 한 줄을 마스터에 박아두지 않으면, 다음에 누군가 “이 테이블 너무 커지는데요” 하고 정리하다가 도메인 전제를 깨뜨리기 쉽다.
결단 3 — 아키텍처 확정본을 SSoT 위계의 2번 자리에 올린다
docs/아키텍처_최종_확정.md (744줄 신규/복원)
이 문서는 v1.1 시점에 한 번 적혔다가 v2.0 마이그레이션 중에 잠시 누락된 적이 있었다. 다시 끌어 올리면서 마스터 문서 다음 자리로 위계를 명문화했다.
1. docs/00_프로젝트_마스터_v2.1.md (비즈니스 규칙 - SSoT)
2. docs/아키텍처_최종_확정.md (구현 목표 - 최종 산출물 기준)
3. docs/domain/authentication.md (인증 체계 - v2.1 완료)
4. docs/domain/authorization.md (권한 체계 - v2.1 완료)
5. prisma/schema.prisma (데이터베이스 스키마)
6. docs/_archived/* (v1.1/v2.0 참조용)
💡 인사이트: 두 SSoT는 서로 보완 관계다. 마스터는 “왜 만들고 어떤 규칙을 따르는지”를 적고, 아키텍처 확정본은 “최종 산출물의 모습이 어떠해야 하는지”를 적는다. 같은 자리를 다투면 안 되고, 그렇다고 한 문서로 합치면 둘 다 흐려진다.
CLAUDE.md에서 이 둘의 자리를 1, 2번에 박아 둔 게 핵심이다. 다음 세션의 AI가 처음 읽어야 할 두 문서를 명확히 못 박아 둬야, 다음에 누가 “어디서부터 읽어야 하나” 를 안 헤맨다.
🧱 두 번째 커밋 — 빈 자리에 4,658줄 DDD를 박는다
f37da73a는 단순한 커밋이다. 10개 파일, 4,658줄, 모두 신규 생성이다.
분량 분포
| 파일 | 줄 수 | 핵심 내용 |
|---|---|---|
aggregates.md | 1,090 | Aggregate Root, Entity, Value Object 정의 + 불변성(Invariants) + 비즈니스 규칙 |
repositories.md | 911 | Repository 인터페이스 정의 |
bounded-contexts.md | 750 | 12개 Bounded Context 책임/엔티티/도메인 이벤트 |
domain-events.md | 712 | 24개 도메인 이벤트 카탈로그 |
02-bundle-and-learning.md | 461 | UC-06 ~ UC-11 (번들 학습 흐름) |
03-level-adjustment.md | 236 | UC-11 ~ UC-14 (레벨 조정) |
05-new-record-challenge.md | 175 | UC-14 ~ UC-16 (신기록 도전) |
01-student-onboarding.md | 154 | UC-01 ~ UC-05 (사용자 온보딩) |
04-batch-processes.md | 104 | UC-12 ~ UC-14 (배치 프로세스) |
06-attendance.md | 65 | UC-18 ~ UC-19 (활동 기록) |
이 분량을 한 시간 만에 박는 건 사람 손으론 안 된다. 마스터 문서가 정확히 적혀 있고, 그걸 입력으로 받아 DDD 문서로 풀어주는 AI 보조 흐름이 있어야 가능한 속도다. 24분 사이의 두 커밋은 사실 “마스터 문서 → 아키텍처 → DDD”라는 한 줄 파이프라인을 한 번에 돌린 결과다.
aggregates.md의 한 칸 — Student Aggregate
가장 변한 부분 중 하나가 학생(코드 변수명) Aggregate다. v2.0까지는 강점/약점 지표를 그때그때 계산했지만 v2.1에서는 TOP1~5 순위를 영속 상태로 들고 있게 됐다.
class Student {
// Identity
id: StudentId
name: string
email: string
// Level State
currentLevelId: LevelId
levelChangedAt: DateTime
// Level Adjustment Counters
poorConsecutiveDays: number
excellentConsecutiveDays: number
normalConsecutiveDays: number
lastAchievementDate: Date
lastAchievementState: AchievementState | null
// Downgrade Recovery
downgradedFromLevelId: LevelId | null
// Cognitive Metrics (v2.1 - TOP1~5 분류용)
metricScores: StudentMetricScore[] // 5개 지표 점수
}
// Value Object (v2.1)
class StudentMetricScore {
metricCode: MetricCode
score: number
rank: MetricRank // TOP1~5
updatedAt: DateTime
}
핵심은 마지막 두 줄이다. metricScores가 5개 고정이라는 것, 그리고 rank가 단순한 숫자가 아니라 TOP1~5 enum으로 분류된다는 점이 v2.1의 정체성이다. 이 정체성이 깨지면 콘텐츠 후보 선정 알고리즘 전체가 틀어진다.
class Student {
recordDailyAchievement(state: AchievementState, date: Date): void
applyLevelDowngrade(newLevelId: LevelId): void
applyLevelUpgrade(newLevelId: LevelId): void
applyLevelRestore(): void
resetConsecutiveCounters(): void
// v2.1
updateMetricScores(scores: StudentMetricScore[]): void
getMetricRanking(): Map<MetricRank, MetricCode>
getMetricByRank(rank: MetricRank): MetricCode
}
v2.1에서 추가된 도메인 메서드 3개. 위에서 정의한 불변성 — “metricScores는 항상 5개” — 을 운영하기 위한 인터페이스다. 인터페이스가 있어야 비즈니스 규칙이 코드 모서리에 흩어지지 않고 한 곳에 모인다.
bounded-contexts.md의 한 칸 — Student Context 책임 변경
도메인 경계 문서에서 가장 크게 변한 칸도 같은 자리다.
+ ### 책임
- 학생 등록 및 프로필 관리
- 현재 레벨 상태 추적
- 레벨 조정 카운터 관리
+ - 학생의 5개 인지 지표 점수 관리 (TOP1~5 분류용)
- 하향 복구를 위한 이전 레벨 추적
+ ### 외부 의존성
- Level Context: 학생의 현재 레벨 정보
+ - Metric Context: 학생의 5개 인지 지표 점수 업데이트
- Organization Context: 학생이 속한 반 정보
Metric Context라는 새 의존선이 한 줄 그어진다. v2.0까지는 학습 결과 컨텍스트가 지표를 계산해 주면 끝이었지만, v2.1에서는 별도 Metric Context가 점수의 SSoT다. 이 줄 한 줄이 다음 모듈을 짜는 사람에게 “Metric Context를 침범하지 말라”는 경계선이 된다.
🔍 단서: Bounded Context 도식의 화살표 한 개를 추가한다는 건, 사실은 컴파일 에러가 안 나는 영역에 새로운 협력 책임을 명문화한다는 뜻이다. 코드는 안 변해도 협력 규약은 변할 수 있고, 그 변화가 가장 먼저 적히는 곳이 BC 문서다.
🧭 왜 24분 안에 두 커밋이 같이 들어갔나
24분 간격은 우연이 아니다. 둘을 같은 날 같은 세션에 묶어야 하는 이유가 있었다.
이유 1 — SSoT 진공 상태를 24분도 두지 않는다
56d5b80f 직후의 레포 상태는 위험하다. v2.0 DDD 문서는 _archived로 사라졌고, 새 v2.1 DDD는 아직 없다. 누군가 그 사이에 DDD 문서를 찾으면 404 답변을 받는다. CLAUDE.md에는 “DDD 문서: v1.1 기준 (아카이빙됨, v2.1 재작성 필요)“라고 명시돼 있지만, 그건 안내일 뿐 SSoT는 아니다.
이 진공 상태를 길게 두면 다음 일이 생긴다.
| 시나리오 | 발생 |
|---|---|
| 다른 세션의 AI가 코드를 수정하면서 Aggregate 정의를 본다 | _archived/v2.0/ 의 옛 문서를 SSoT처럼 인용 |
| QA 담당자가 도메인 이벤트 카탈로그를 본다 | ”v2.1에는 없다”고 판단해서 시나리오 누락 |
| 운영자가 “Metric Context가 어디에 있냐”고 묻는다 | 답이 없거나, CLAUDE.md만 가리킴 |
같은 세션 안에서 두 커밋을 묶어야 SSoT 진공이 0이 된다. 24분이라는 간격은 AI 보조 흐름이 4,658줄을 풀어내는 데 필요한 최소 시간이지, 그보다 더 길게 둘 이유가 없다.
이유 2 — 코드 마이그레이션 완료의 마지막 사이드 작업이다
v2.0 코드 마이그레이션은 지난 편에서 ApplicationModule 39줄을 박는 것으로 코드 영역이 끝났다. 하지만 그건 코드 영역만 끝난 것이다. SSoT 영역에는 아직 v1.1 DDD 문서가 살아 있었고, 마스터는 v2.0을 가리켰는데 코드는 v2.1 변경 4건이 누적된 상태였다. 이 비대칭을 0으로 만드는 게 마이그레이션의 마지막 사이드 작업이다.
📌 핵심: 코드와 문서를 다른 트랙으로 두면 둘은 결국 점점 멀어진다. v2.0 마이그레이션 같은 큰 사이클은 끝날 때 한 번, 코드와 SSoT의 거리를 0으로 좁히는 의식이 필요하다. 이번 두 커밋이 그 의식이었다.
이유 3 — CLAUDE.md라는 “AI에게 뭘 먼저 보여줄지”의 표지석
이 두 커밋에서 가장 정치적인 선언은 CLAUDE.md의 260줄짜리 변경이다.
-### 1.3 핵심 가치
- - **완전 자동화**: 과제 생성, 레벨 조정, 지표 분석 자동화
- - **개인화**: 강점/약점 지표 기반 맞춤 학습
- - **실시간 적응**: 번들 내 레벨링 모듈로 동적 난이도 조정
+### 1.3 핵심 가치
+ - **완전 자동화**: 과제 생성, 레벨 조정, 지표 분석 자동화
+ - **개인화**: 강점/약점 지표 기반 맞춤 학습
+ - **실시간 적응**: 번들 내 복습 모듈로 동적 난이도 조정
+ - **데이터 중심**: 모든 학습 기록을 빅데이터/로깅 목적으로 완전 보존
- 1. docs/00_프로젝트_마스터_v2.0.md (비즈니스 규칙)
- 2. docs/V2_MIGRATION_ROADMAP.md (마이그레이션 계획)
- 3. docs/domain/*.md (DDD 설계) ← v2.0 기준 업데이트 필요
- 4. prisma/schema.prisma (데이터베이스 스키마) ← v2.0 마이그레이션 필요
- 5. docs/_archived/* (v1.1 참조용)
+ 1. docs/00_프로젝트_마스터_v2.1.md (비즈니스 규칙 - SSoT)
+ 2. docs/아키텍처_최종_확정.md (구현 목표 - 최종 산출물 기준)
+ 3. docs/domain/authentication.md (인증 체계 - v2.1 완료)
+ 4. docs/domain/authorization.md (권한 체계 - v2.1 완료)
+ 5. prisma/schema.prisma (데이터베이스 스키마) ← v2.1 전면 재작성 필요
+ 6. docs/_archived/* (v1.1/v2.0 참조용)
CLAUDE.md는 새 세션이 시작할 때 가장 먼저 읽히는 문서다. 우선순위가 5단계 → 6단계로 늘어났고, 그 안에서 마이그레이션 로드맵이 빠지고 인증/권한 문서가 새로 들어왔다. 다음 세션의 첫 5분이 어떻게 흘러갈지를 정하는 줄들이다.
🛡️ 같은 실수를 안 반복하기 위해 박아 둔 규칙들
이 두 커밋에서 가장 오래 살아남을 가치는 4,658줄짜리 새 DDD 문서가 아니라, 앞으로 같은 사이클을 어떻게 돌릴지의 규칙이다.
규칙 1 — “버전 변경 = SSoT 사이클 한 바퀴”라는 규약
| 변경 폭 | SSoT 사이클 |
|---|---|
| 패치(v2.1 → v2.1.1) — 알고리즘 1개 변경, 모듈 1개 변경 | 마스터 부분 패치 + 해당 DDD 파일만 수정 |
| 마이너(v2.0 → v2.1) — 4개 이상 핵심 개념 변경 | 마스터 갈아 끼움 + DDD 통째 재작성 + _archived/ 이동 |
| 메이저(v1.1 → v2.0) — 도메인 모델 자체 변경 | 마스터 새로 작성 + DDD/Schema/Module 모두 v2 신규 |
이 표가 CLAUDE.md에 명문으로 박힌 건 아니다. 그렇지만 1/7의 두 커밋은 “마이너 변경 = DDD 통째 재작성”이라는 규약을 사례로 박아 뒀다. 다음에 v2.1 → v2.2가 올 때, 이 사례를 가리키며 “그때처럼 하자”고 말할 수 있다.
규칙 2 — _archived/ 디렉터리는 “옛 진실의 묘비”
docs/_archived/
├── v1.1/ (v1.1 시절 PRD/DDD)
├── v2.0/ (이번에 새로 채워짐)
│ ├── V2_MIGRATION_ROADMAP.md
│ ├── V2_MODULE_MIGRATION_PLAN.md
│ └── domain/
│ ├── aggregates.md
│ ├── bounded-contexts.md
│ ├── domain-events.md
│ ├── repositories.md
│ └── use-cases/...
└── prd_v2.0.txt
_archived/는 언제고 다시 꺼내 볼 수 있도록 박제한 옛 진실이다. 이걸 안 두고 그냥 삭제하면 어떤 일이 생기냐면 — git history로 “왜 이 결정이 됐는지”는 추적할 수 있어도 “당시 SSoT가 어떤 모양이었는지”는 통째로 재구성해야 한다. 디렉터리 한 칸으로 그 비용을 0에 가깝게 만든다.
⚠️ 주의:
_archived/로 옮길 때 **파일 이동(rename)**으로 처리해야 git history가 따라온다. 새 파일을 새로 만들고 옛 파일을 삭제하는 식으로 하면 git이 두 파일을 별개로 본다.56d5b80f커밋은 정확히git mv로 처리됐고, 그래서_archived/v2.0/aggregates.md의 history를 들춰 보면 v1.1 시절의 첫 작성 커밋까지 추적된다.
규칙 3 — DDD 문서는 코드보다 적게, 마스터보다 자세히
마스터 문서 00_프로젝트_마스터_v2.1.md는 1,000줄 가까운 분량이지만, 비즈니스 규칙을 사람의 언어로 적은 문서다. DDD 문서는 그 규칙을 타입 시그니처와 도메인 메서드의 언어로 풀어낸 문서다. 두 문서가 같은 내용을 두 번 적으면 한쪽이 거짓말이 된다.
마스터 문서: "복습 모듈은 정확도 90% 이상에서도 작동한다 (v2.1 변경)"
↓
DDD aggregates: class ReviewModule {
shouldTrigger(accuracy: number): boolean
// accuracy >= 0.9 도 true (v2.1)
}
↓
코드: if (attempt.accuracy >= 0.9 || attempt.accuracy < 0.7) {
this.reviewModule.trigger(...)
}
마스터는 “왜”를, DDD는 “무엇을(시그니처)”, 코드는 “어떻게”를 적는다. 셋의 위계가 명확해야 다음 변경이 들어왔을 때 “어디서부터 손대야 하나”가 안 헷갈린다.
규칙 4 — 배포 가능 상태와 SSoT 정합 상태를 분리해서 본다
배포 가능 = 빌드 + 테스트 + 마이그레이션 + 컨테이너 이미지 OK
SSoT 정합 = 마스터 + 아키텍처 + DDD + Schema + 코드 모두 같은 v를 가리킴
devlog-17 끝에서 v2.0은 배포 가능 상태였지만 SSoT 정합 상태는 아니었다. 이 둘은 다르다. 1/7 밤의 두 커밋이 비로소 SSoT 정합 상태를 만들었고, 그제야 v2.0이라는 사이클이 닫혔다.
| 단계 | 코드 | 마스터 | DDD | Schema |
|---|---|---|---|---|
| Phase 3-5 직후 | v2.0 | v2.0 | v1.1 | v1.1 마이그레이션 완료 |
56d5b80f 직후 | v2.0 | v2.1 | (진공) | v1.1 |
f37da73a 직후 | v2.0 | v2.1 | v2.1 | v1.1 (다음 작업 대상) |
표를 보면 f37da73a 다음에도 한 칸은 비뚤어져 있다. Schema가 아직 v1.1이다. 이 사실을 CLAUDE.md에 정직하게 적어 둔 게 마지막 한 줄이다.
5. prisma/schema.prisma (데이터베이스 스키마) ← v2.1 전면 재작성 필요
이 한 줄이 다음 사이클의 시작점이 된다.
📋 정리 — 핵심 요약
코드 마이그레이션이 끝난 다음 날 밤 24분의 두 커밋은, 사실 v2.0이라는 사이클의 진짜 종결식이었다.
| 항목 | 안티패턴 | 권장 패턴 |
|---|---|---|
| 코드 변경 후 SSoT 정비 | ❌ 다음 스프린트로 미룸 | ✅ 같은 세션 안에서 24분 안에 닫음 |
| 큰 변경의 DDD 업데이트 | ❌ 점진 패치로 4번 손댐 | ✅ _archived/로 보내고 통째 재작성 |
| 옛 문서 처리 | ❌ 그냥 삭제 (git history 의존) | ✅ git mv로 _archived/v$VERSION/에 박제 |
| SSoT 정점 명문화 | ❌ “어디 보면 됨” 으로 두루뭉술 | ✅ CLAUDE.md에 6단계 우선순위 명시 |
| 마스터/DDD 분담 | ❌ 한 문서가 다 적음 | ✅ 마스터=왜, DDD=무엇을, 코드=어떻게 |
| 사이클 종료 판단 | ❌ “코드 빌드 OK = 끝” | ✅ “코드 + 마스터 + DDD + Schema 같은 v” |
다음 편 — v2.1 Domain Layer의 설계 철학에서는 이 4,658줄짜리 DDD 문서를 입력으로 받아 코드의 도메인 레이어를 다시 박는 이야기로 넘어간다. 문서가 SSoT 자리에 정확히 박혀 있으면 그 다음 단계는 “타이핑”이지 “결정”이 아니다.
📚 교육용 풀스택 SaaS 개발기 시리즈 (23편)
- 1. 왜 NestJS + Prisma를 선택했나 — B2B SaaS 백엔드 기술 선택기
- 2. 도메인 모델링 첫날 — B2B SaaS의 핵심 엔티티 정의하기
- 3. 27개 테이블의 탄생 — Prisma 스키마 설계기
- 4. 권한 매트릭스 — Admin/운영자/사용자 3역할 설계
- 5. BigInt PK에서 Int PK로 — 첫 번째 스키마 리팩토링
- 6. Seed 데이터의 함정 — FK 삭제 순서 삽질기
- 7. DDD를 도입하기로 했다 — Repository/Domain/Application 3계층
- 8. 인터페이스 구현체로 바꾸는 날 — NestJS DI와 TypeScript의 간극
- 9. 단위 테스트 인프라 구축 — Jest 설정부터 Mock까지
- 10. E2E 테스트와 Cloud SQL의 고난 — 4/8 passing에서 8/8까지
- 11. REST API 첫 구현 — 6개 Controller, 21개 엔드포인트 완성
- 12. v1.0 완성, 그리고 갈아엎기로 결심한 날
- 13. 번들 구조를 통째로 바꿔야 했던 이유
- 14. Phase 1 문서 정비 — Use Case를 번들 기반으로 다시 쓰다
- 15. Phase 2 스키마 마이그레이션 — 데이터 안 날리고 구조 바꾸기
- 16. Phase 3-1·3-2 — Repository와 Domain 서비스로 36개 빌드 에러 잡기
- 17. Phase 3-3·3-4·3-5 — Application부터 Module까지, v2.0 마이그레이션 닫는 날
- 18. 코드를 박은 다음 날 — 4,658줄 DDD 문서를 24분 사이에 다시 쓴 하루
- 19. v2.1 Domain Layer — 도메인 서비스 1,682줄을 한 커밋에 박은 날의 설계 철학
- 20. v3.0 Application Layer 재작성 — 도메인 서비스 위에 얇은 막을 한 Phase에 박은 날
- 21. 갈아엎고 80일 — v2.0 마이그레이션 8편 메타 회고
- 22. 1인 다역으로 5일 만에 90% — Admin Portal MVP를 끌어올린 토글 한 줄
- 23. Mock에선 되던 게 REST에선 안 됐다 — 응답 포맷 한 칸 차이가 만든 하루