15.10 Modernization sequencing
บทนี้แปลง heat-map ในบท 14.9 ให้เป็น action plan ที่จัดลำดับเป็น Wave แต่ละ Wave ออกแบบให้ de-risk Wave ถัดไป และระบบไม่มีหน้าต่าง downtime แบบ big bang
ภาพรวม 4 Wave
Wave 0 ───► Wave 1 ───► Wave 2 ───► Wave 3
Stop-bleed Stabilize Strategic Frontend
(2 สัปดาห์) (3 เดือน) (12 เดือน) (15 เดือน)| Wave | เป้าหมาย | ระยะเวลา | ผลลัพธ์ที่ได้ |
|---|---|---|---|
| 0 | ลด Critical security/availability risk โดยไม่เปลี่ยน architecture | 1-2 สัปดาห์ | Critical debt ลดลง 7-10 รายการ |
| 1 | ยกระดับ runtime และ dependency ที่ EOL | 1-3 เดือน | Node 20 LTS, Apollo v4, React 18 |
| 2 | แยก bounded context ไป Go (backend) | 3-12 เดือน | service ย่อยที่ทันสมัย |
| 3 | ย้าย frontend ไป Nuxt 3 | 6-15 เดือน | frontend ที่ทันสมัย |
Wave 0 — Stop-the-bleed (สัปดาห์ที่ 0-2)
แก้ไขด่วนที่ไม่ต้องเปลี่ยน architecture ลดความเสี่ยง Critical ที่กำลังเกิด
| Action | Debt ID | Effort | ผู้รับผิดชอบแนะนำ |
|---|---|---|---|
เปลี่ยน JWT_SECRET ให้ refuse to start ถ้า env ว่าง | SEC-4 | 30 นาที | backend |
| ปิด playground/introspection ใน prod | SEC-2 | 30 นาที | backend |
| CORS allow-list บน centralize-api | SEC-5 | 30 นาที | backend |
| ปิด syncModelToTable ใน prod | CORR-6 | 1 ชั่วโมง | backend |
| ลบ phpMyAdmin จาก prod compose | PERF-7 | 1 ชั่วโมง | ops |
| เปลี่ยน qpdf execSync → execFile | SEC-1, PERF-1 | 2-3 วัน | backend |
| หมุนเวียน device key, ย้ายไป env | SEC-3 | 1-2 วัน | backend + ops |
| หมุนเวียน GCP SA key, ลบออกจาก repo | SEC-11 | 1-2 วัน | ops |
| ลบ typosquat packages (LEG-13) + commit centralize lockfile | LEG-13, LEG-15 | 1 วัน | backend |
เพิ่ม engines.node ทุก package.json | LEG-18 | 30 นาที | backend |
ลบ axois typo + เพิ่ม axios | SEC-7, CORR-9 | 30 นาที | backend |
ลบ logging: ['query'] ใน centralize prod | CORR-5 | 30 นาที | backend |
| ลบ column ซ้ำใน hospital.js | CORR-7 | 30 นาที | backend |
| ยกระดับ jsonwebtoken v8 → v9 | LEG-9 | 1 วัน | backend |
| แก้ xlsx CVE (ใช้ exceljs หรือ CDN build) | LEG-27 | 1 วัน | backend |
| ลบ vtrc-web package-lock.json (เลือก yarn) | LEG-16 | 30 นาที | frontend |
ผลรวม Wave 0: effort ~10 วันทำงาน, ลด debt ได้ 15+ รายการ
เกณฑ์การตัดสินใจว่า Wave 0 เสร็จ:
- [ ] ทุก Critical debt ในบท 14.1 ถูก mark ว่า mitigated
- [ ] penetration test ใหม่ผ่าน
- [ ] uptime monitor + alert ตั้งไว้ (Volume 13)
Wave 1 — Stabilize (เดือนที่ 1-3)
ยกระดับ runtime และ dependency ที่ EOL ให้ทันสมัย แต่ยังอยู่บน architecture เดิม
Backend
| Action | Debt ID | Effort |
|---|---|---|
| แปลง esm shim → native ESM | LEG-2 | 2-3 สัปดาห์ |
| ยกระดับ Node 12 → 20 LTS | LEG-1 | (หลัง LEG-2) |
| Apollo Server v2 → v4 | LEG-3 | 1-2 สัปดาห์ |
| เพิ่ม rate limit + structured logging + session indexed lookup | SEC-10, LEG-24, PERF-3 | 1-2 สัปดาห์ |
| แก้ RBAC inconsistency + test matrix | CORR-4 | 1-2 สัปดาห์ |
| เปลี่ยน ldapjs → ldapts | LEG-8 | 2-3 วัน |
| ยกระดับ redis v3 → v4 | LEG-12 | 2-3 วัน |
| ยกระดับ firebase-admin, googleapis | LEG-29 | 2-3 วัน |
| แก้ transaction swallow pattern | CORR-2 | 1-2 วัน |
| เพิ่ม health endpoint ทุก service | — | 1-2 วัน |
Frontend
| Action | Debt ID | Effort |
|---|---|---|
| เปลี่ยน Apollo default เป็น cache-and-network | PERF-2 | 1-2 วัน |
| รวม axios instances | LEG-20 | 1 สัปดาห์ |
| ยกระดับ AntD v3 → v4 ใน web | LEG-5 | 1 สัปดาห์ |
| ยกระดับ jsonwebtoken, redis (เหมือน backend) | LEG-9, LEG-12 | — |
Cross-cutting
| Action | Effort |
|---|---|
| สร้าง CI/CD pipeline สำหรับ 3 repos ที่เหลือ | 1 สัปดาห์ |
ลบ _bk files 131 ไฟล์ + ตั้ง .gitignore + pre-commit | 1-2 วัน |
| ตั้ง logrotate + backup script (Volume 13) | 2-3 วัน |
| ตั้ง centralized logging (Loki + Grafana) | 1 สัปดาห์ |
ผลรวม Wave 1: ~3 เดือน, ลด debt ได้อีก 20+ รายการ
เกณฑ์การตัดสินใจว่า Wave 1 เสร็จ:
- [ ] Node runtime ทุกตัวเป็น LTS ปัจจุบัน
- [ ] ทุก service มี health endpoint
- [ ] CI/CD ครอบคลุมทุก repo
- [ ] centralized logging + monitoring + alerting ทำงาน
Wave 2 — Strategic (เดือนที่ 3-12)
เริ่มแยก bounded context ไป service ใหม่ที่ใช้ Go (Hexagonal/DDD) ทีละ context จากที่โดดเดี่ยวที่สุดก่อน
ลำดับการแยก bounded context
| ลำดับ | Bounded context | เหตุผลที่ทำก่อน | เป้าหมาย |
|---|---|---|---|
| 1 | document-renderer (PDF/Excel/qpdf) | self-contained, แก้ SEC-1/PERF-1 root cause | Go + chromedp + pdfcpu |
| 2 | identity (auth, session, RBAC, device key) | ปลดล็อก context อื่น, แก้ SEC-3/4, CORR-4 | Go + Casbin + Postgres |
| 3 | payroll (slip, tax) | read-mostly, ขอบเขตชัด | Go + gqlgen |
| 4 | time-attendance | high-volume read, ได้ประโยชน์จาก cache | Go + Redis |
| 5 | leave | multi-level workflow, ขอบเขตชัด | Go + saga |
| 6 | welfare (WDHospital) | ซับซ้อนที่สุด (state machine 13 สถานะ, controller 3,179 LOC) | Go + state machine package |
| 7 | provident-fund, communication, organization, shared-kernel | ที่เหลือ | Go |
โครงสร้างแต่ละ context
<service>/
├── cmd/ # entrypoint (driving adapter)
├── internal/
│ ├── <context>/
│ │ ├── domain/ # entity, value object, domain service
│ │ ├── port/ # use-case interface
│ │ ├── adapter/ # db, redis, sftp, pdf, smtp
│ │ └── usecase/ # application service
├── migrations/ # forward-only migration
└── go.modMigration strategy แต่ละ context
- เขียน service ใหม่ใน Go คู่ขนาน
- ใช้ strangler pattern — route traffic บางส่วนไป service ใหม่ (feature flag)
- ค่อย ๆ เพิ่มสัดส่วน traffic
- เมื่อ service ใหม่ stable ลบ code เดิมจาก vtrc-api
เป้าหมาย Wave 2
- [ ] bounded context 3 ตัวแรก (document-renderer, identity, payroll) live ใน production
- [ ] vtrc-api เล็กลง 30-40%
- [ ] Critical performance debt (PERF-1, PERF-6) หมดไป
Wave 3 — Frontend → Nuxt 3 (เดือนที่ 6-15)
เริ่มได้หลัง identity และ payroll service ใหม่ live (frontend มี API stable ใหม่)
ลำดับ
| ลำดับ | Action | Effort |
|---|---|---|
| 1 | สร้าง Nuxt 3 shell + auth + routing | 2 สัปดาห์ |
| 2 | ย้าย feature ที่ใช้บ่อย (login, profile, payslip) ก่อน | 1-2 เดือน |
| 3 | ย้าย feature ที่ซับซ้อน (leave, welfare) | 2-3 เดือน |
| 4 | ย้าย backoffice | 2-3 เดือน |
โครงสร้าง (Feature-Sliced Design)
apps/web/ # แทน vtrc-web
└── src/
├── app/ # global shell, providers
├── processes/ # routing strategies
├── pages/ # route-level composition
├── widgets/ # composite blocks
├── features/ # user actions
├── entities/ # domain slices
│ └── <slice>/api/ # co-located gql operations
└── shared/ # ui-kit, api, lib, config, i18nเป้าหมาย Wave 3
- [ ] vtrc-web และ vtrc-rc-backoffice ใช้งานบน Nuxt 3
- [ ] React, Apollo Client v2, AntD legacy ไม่ใช้แล้ว
- [ ] frontend bundle เล็กลง > 50%
สิ่งที่ต้องระวังในทุก Wave
- อย่า stop deployment นานเกินไป — ทุก Wave ต้อง deploy ต่อไปได้ระหว่าง migration
- รักษา backward compatibility — API เก่าต้องยังทำงานจนกว่า client ทุกตัวย้าย
- ทดสอบใน UAT ก่อนเสมอ — แต่ละWave ต้องมีช่วง UAT
- เก็บ runbook ให้ทันสมัย — Volume 13 ต้องอัปเดตตามการเปลี่ยนแปลง
- วัดผล — ตั้ง KPI เช่น "Critical debt ลดลง X รายการ" และ track ทุกเดือน
KPI ของแต่ละ Wave
| Wave | KPI | เป้าหมาย |
|---|---|---|
| 0 | Critical debt ที่ mitigated | ≥ 10 รายการ |
| 1 | Node runtime เวอร์ชัน | ≥ 20 LTS ทุก service |
| 1 | service ที่มี health endpoint | 5/5 |
| 2 | bounded context ที่แยกแล้ว | ≥ 3 |
| 2 | vtrc-api LOC | ลดลง 30% |
| 3 | frontend บน Nuxt 3 | 2/2 |
สรุป
ลำดับนี้ออกแบบให้:
- Wave 0 ลดความเสี่ยงทันที
- Wave 1 ทำให้ foundation มั่นคง
- Wave 2 ลดขนาด monolith ทีละส่วน
- Wave 3 ยก frontend ใหม่ทั้งหมด
ทีมสามารถปรับลำดับได้ตามบริบท เช่น ถ้า frontend มี feature ใหม่ด่วน อาจเริ่ม Wave 3 ก่อน Wave 2 จบ
รายละเอียดเชิงลึกของแต่ละ Wave จะอยู่ใน Volume 15 (Modernization Plan)