16.4 Wave 2 — Backend extraction to Go
Wave 2 แยก bounded context จาก vtrc-api monolith ไป service ใหม่ที่เขียนด้วย Go (Hexagonal/DDD) ทีละ context
เป้าหมายของ Wave 2
- แยก bounded context 3-7 ตัวออกจาก vtrc-api
- vtrc-api ลดขนาดลง 30-40%
- ลด architecture debt (cross-schema write, god controller, in-process cron)
- Effort: 12 เดือน (24 sprint)
- ทีม: 2-3 backend engineer + 1 DevOps
ลำดับการแยก context
| ลำดับ | Context | เหตุผลที่ทำก่อน | Debt ที่แก้ | Effort |
|---|---|---|---|---|
| 1 | document-renderer | self-contained, แก้ SEC-1/PERF-1 root cause | SEC-1, PERF-1 | 1-2 เดือน |
| 2 | identity | ปลดล็อก context อื่น, แก้ SEC-3/4, CORR-4 | SEC-3, SEC-4, CORR-4 | 2-3 เดือน |
| 3 | payroll | read-mostly, ขอบเขตชัด | (debt รอง) | 1-2 เดือน |
| 4 | time-attendance | high-volume, ได้ประโยชน์จาก cache | (debt รอง) | 1-2 เดือน |
| 5 | leave | workflow หลายขั้น, ขอบเขตชัด | (debt รอง) | 2 เดือน |
| 6 | welfare | state machine 13 สถานะ, controller 3,179 LOC | PERF-6 | 3-4 เดือน |
| 7 | provident-fund, communication, organization | ที่เหลือ | (debt รอง) | 2 เดือน |
ตัวอย่าง — context 1: document-renderer
บทนี้ลงรายละเอียดของ context แรกเป็นตัวอย่าง ส่วน context อื่นใช้ pattern เดียวกัน (รายละเอียด playbook ในบท 15.6)
เป้าหมาย context
แยก PDF/Excel generation ออกจาก vtrc-api ไป service ใหม่ document-renderer (Go)
ขอบเขต
งานที่ย้าย:
- PDF payslip generation (ตอนนี้ใช้ html-pdf + qpdf)
- Excel export (ตอนนี้ใช้ xlsx)
- การเข้ารหัส PDF (qpdf)
งานที่ ไม่ ย้าย:
- ข้อมูลที่ใช้ในการ render — ยังดึงจาก vtrc-api
- authentication — vtrc-api ยังเป็นทางเข้า
Architecture
┌──────────────────┐
│ vtrc-api │
│ │
request ────►│ /api/graphql │
│ │ │
│ ▼ │
│ feature flag │
│ │ │
└─────┼────────────┘
│
┌─────────┴─────────┐
│ │
flag off flag on
│ │
▼ ▼
┌────────────────┐ ┌──────────────────┐
│ html-pdf │ │ document-renderer│
│ (เดิม) │ │ (Go + chromedp) │
└────────────────┘ └──────────────────┘
│
▼
┌───────────────┐
│ MariaDB │
│ (อ่านข้อมูล) │
└───────────────┘Task breakdown
Sprint 13 · เตรียมการ
Task 2.1.1 · เขียน contract test
เก็บ PDF ตัวอย่าง 10 ไฟล์ของข้อมูลต่าง ๆ (payslip, leave form, welfare report) — ใช้เป็น expected output สำหรับ service ใหม่
Task 2.1.2 · เลือก stack
- Go (เวอร์ชัน 1.21+)
chromedpสำหรับ HTML → PDFpdfcpuสำหรับ encryptionxuri/excelizeสำหรับ Excel- HTTP API (REST, ไม่ใช่ GraphQL — service นี้ internal)
Task 2.1.3 · ตั้ง repo
document-renderer/
├── cmd/
│ └── server/
│ └── main.go
├── internal/
│ ├── render/
│ │ ├── domain/ # entity (Document, RenderRequest)
│ │ ├── port/ # interface (Renderer, Encryptor)
│ │ ├── adapter/
│ │ │ ├── chromedp/ # HTML → PDF
│ │ │ ├── pdfcpu/ # encryption
│ │ │ └── excelize/ # Excel
│ │ └── usecase/
│ └── http/
│ └── handler.go
├── migrations/
├── go.mod
└── DockerfileSprint 14-15 · implement
Task 2.1.4 · implement renderer
POST /render/pdf— รับ HTML + options, return PDFPOST /render/excel— รับ data + template, return xlsxPOST /encrypt— รับ PDF + password, return encrypted PDF
Task 2.1.5 · integration test
- รัน contract test ที่เตรียมไว้
- ทุก PDF ที่ generate ต้องเหมือน expected output (หรือใกล้เคียงในระดับที่ยอมรับ)
Sprint 16 · cutover
Task 2.1.6 · vtrc-api ฝัง feature flag
// vtrc-api route
const useNewRenderer = featureFlags.isEnabled('document-renderer', {
fallback: false,
userId: req.user.id
});
if (useNewRenderer) {
const pdf = await fetch('http://document-renderer/render/pdf', { ... });
res.send(pdf);
} else {
// code เดิม
}Task 2.1.7 · deploy แบบค่อย ๆ เพิ่ม traffic
| Week | % traffic ไป service ใหม่ |
|---|---|
| 1 | 5% (internal test user) |
| 2 | 25% |
| 3 | 50% |
| 4 | 100% |
หมายเหตุ: ทุกขั้นต้องสังเกต error rate, latency, และผู้ใช้ complaint
Task 2.1.8 · ลบ code เดิม
เมื่อ service ใหม่รับ 100% traffic มา ≥ 2 สัปดาห์โดยไม่มีปัญหา:
- ลบ
html-pdf,node-qpdfdependency - ลบ endpoint เดิมใน routes.js
Definition of Done
- [ ] service document-renderer รับ 100% production traffic มา ≥ 2 สัปดาห์
- [ ] error rate < 0.1%
- [ ] latency เฉลี่ย < 3 วินาที
- [ ] vtrc-api ไม่มี code PDF generation อีก
- [ ] runbook (Volume 13) อัปเดต
Rollback plan
ทุกขั้นตอนสามารถ rollback โดย:
- ปิด feature flag — traffic กลับไป code เดิมทันที
- (ถ้า code เดิมถูกลบไปแล้ว) deploy version ก่อนลบ
การประเมิน context อื่น
หลัง context 1 (document-renderer) เสร็จ ทีมใช้ playbook เดียวกันกับ context ถัดไป รายละเอียดของแต่ละ context จะสร้างตอนเริ่ม context นั้น (ไม่ล่วงหน้าทั้งหมด เพราะบริบทจะเปลี่ยน)
Context 2 · identity (Sprint 17-24)
ขอบเขต:
- user, role, session, device key
- authentication (login, refresh, logout)
- authorization (@auth directive logic)
ความซับซ้อนสูงเพราะกระทบทุก request — ต้องระมัดระวัง migration
Context 3 · payroll (Sprint 25-30)
ขอบเขต:
- payslip, tax, salary data
- query payroll data จาก MSSQL
- generate payroll report
ค่อนข้าง self-contained เพราะ read-mostly
Context 4 · time-attendance (Sprint 31-34)
ขอบเขต:
- check-in/check-out data
- time report
- leave balance calculation
Context 5 · leave (Sprint 35-40)
ขอบเขต:
- leave request, approval chain
- leave balance, leave type
state machine ที่ซับซ้อนกว่า payroll
Context 6 · welfare (Sprint 41-50)
ขอบเขต:
- WDHospital claim (3,179 LOC god controller)
- approval chain
- bank export
- report
ซับซ้อนที่สุด ทำเป็น context สุดท้าย
Context 7 · ที่เหลือ (Sprint 51-56)
provident-fund, communication, organization, shared-kernel
เกณฑ์การตัดสินใจว่า Wave 2 เสร็จ
- [ ] bounded context ≥ 3 ตัวแยกออกจาก vtrc-api
- [ ] vtrc-api LOC ลดลง ≥ 30%
- [ ] cross-schema write debt (CORR-1) หมดไป
- [ ] in-process cron แทนด้วย distributed scheduler
- [ ] MTTR < 30 นาที
ความเสี่ยงของ Wave 2
| ความเสี่ยง | ผลกระทบ | Mitigation |
|---|---|---|
| ทีมไม่มี skill Go | migration ช้า | training + pairing + ทำ context เล็กก่อน |
| Migration มี bug | ข้อมูลผิด | contract test + dual-write + gradual cutover |
| Service ใหม่ช้ากว่าเดิม | UX แย่ลง | load test ก่อน cutover |
| Domain boundary ไม่ชัด | ต้อง rework | invest time ใน domain modeling ก่อนเริ่ม |
| ทีมเบื่อก่อนเสร็จ | migration ค้าง | celebrate wins, rotate context |
สรุป Wave 2
Wave 2 ใช้เวลา 12 เดือน แยก bounded context ออกจาก monolith ทีละตัว ผลลัพธ์คือระบบที่:
- มีขนาดเล็กลง (vtrc-api ลด 30-40%)
- ทันสมัย (Go)
- observability ดีขึ้น (service ใหม่มี metric)
- deploy ได้เป็นอิสระ (แต่ละ context)
บทถัดไปจะลงรายละเอียด Wave 3 (frontend)