Skip to content

16.3 Wave 1 — Stabilize

Wave 1 ยกระดับ runtime และ dependency ที่ EOL ให้ทันสมัย แต่ยังอยู่บน architecture เดิม

เป้าหมายของ Wave 1

  • Node runtime ทุกตัวเป็น LTS ปัจจุบัน (20+)
  • ทุก service มี health endpoint
  • CI/CD ครอบคลุมทุก repo
  • Centralized logging + monitoring + alerting
  • ลด High debt ได้ 15+ รายการ
  • Effort: 3 เดือน (12 sprint)

ทีมที่ต้องการ

บทบาทจำนวนหน้าที่
Backend engineer2migration esm, Node upgrade, Apollo upgrade, RBAC
Frontend engineer1Apollo cache policy, AntD upgrade, axios consolidate
DevOps engineer1CI/CD, monitoring, logging
Database engineer (part-time)0.5schema migration, slow query

ลำดับ task (dependency สำคัญ)

Sprint 3-4:  esm shim → native ESM


Sprint 5-6:  Node 12 → 20 LTS


Sprint 7-8:  Apollo Server v2 → v4

Sprint 7-8:  ───► parallel: RBAC fix, session index, txn fix

Sprint 9-10: dependency upgrade (redis, ldap, firebase)

Sprint 9-10: ──► parallel: frontend (Apollo cache, AntD)

Sprint 11-12: CI/CD, monitoring, logging, health endpoint

Sprint 3-4 · esm shim → native ESM

Task 1.1 · แปลง esm shim เป็น native ESM

Debt: LEG-2 Effort: 2-3 สัปดาห์ ไฟล์: package.json และ source ทุกไฟล์ใน vtrc-api, cu-central-api

งาน:

  1. เพิ่ม "type": "module" ใน package.json
  2. เปลี่ยน requireimport ทุกที่
  3. เปลี่ยน module.exportsexport default / export
  4. ใช้ __dirname / __filename ทดแทนด้วย import.meta.url
  5. เปลี่ยนไฟล์ config ที่ใช้ .esmrc / -r esm
  6. ลบ dependency esm

กับดักที่พบบ่อย:

  • ไฟล์ config ของ Sequelize ที่ใช้ .sequelizerc — ต้องแปลง
  • dependency ที่เป็น CommonJS — ใช้ import pkg from 'pkg' แล้ว pkg.default
  • การ test ที่ใช้ esm loader — เปลี่ยนเป็น native ESM ของ test runner

Definition of Done:

  • [ ] esm ไม่อยู่ใน package.json
  • [ ] node . รันได้โดยไม่ใช้ -r esm
  • [ ] ทุก test ผ่าน
  • [ ] deploy ไป UAT แล้วทำงานได้

Rollback: คืน git commit ก่อนการแปลง (เป็นไปได้เพราะเป็น code-level change)

Task 1.2 · เครื่องมือช่วย

ใช้เครื่องมือ:

  • @babel/plugin-transform-modules-commonjs (สำหรับ auto-convert บางส่วน)
  • cjs-to-esm (อาจมี bug, ใช้อย่างระมัดระวัง)
  • code review ทุกไฟล์ที่ auto-convert

Sprint 5-6 · Node 12 → 20 LTS

Task 1.3 · ยกระดับ Node runtime

Debt: LEG-1 Effort: 1 สัปดาห์ (หลัง Task 1.1 เสร็จ) ไฟล์: Dockerfile ทุกตัว

งาน:

  1. เปลี่ยน FROM node:12FROM node:20-alpine
  2. แก้ native module ที่ต้อง rebuild (เช่น node-qpdf ถ้ายังใช้)
  3. ปรับ .nvmrc ในทุก repo เป็น 20
  4. ปรับ engines.node เป็น >=20.0.0

กับดัก:

  • html-pdf ใช้ PhantomJS ที่ไม่รองรับ Node 20 — ต้องเปลี่ยน (Task 1.10)
  • บาง dependency legacy ไม่รองรับ Node 20 — ตรวจก่อน

Definition of Done:

  • [ ] ทุก Dockerfile ใช้ Node 20
  • [ ] production service รันได้บน Node 20
  • [ ] ทุก test ผ่าน

Sprint 7-8 · Apollo + แก้ debt สำคัญ

Task 1.4 · Apollo Server v2 → v4

Debt: LEG-3 Effort: 1-2 สัปดาห์ ไฟล์: vtrc-api, cu-central-api

งาน:

Apollo v4 มี breaking changes สำคัญ:

  1. เปลี่ยน apollo-server@apollo/server
  2. ApolloServer constructor รับ schema เป็น parameter
  3. context function เปลี่ยนรูป
  4. integration กับ Express เปลี่ยน (ใช้ expressMiddleware)
javascript
// v2
const server = new ApolloServer({ typeDefs, resolvers, context });
server.applyMiddleware({ app, path: '/api/graphql' });

// v4
import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';

const server = new ApolloServer({ typeDefs, resolvers });
await server.start();
app.use('/api/graphql', expressMiddleware(server, { context }));

Definition of Done:

  • [ ] ใช้ @apollo/server v4
  • [ ] GraphQL endpoint ทำงานเหมือนเดิม
  • [ ] test ผ่าน

Task 1.5 · แก้ RBAC inconsistency (CORR-4)

Debt: CORR-4 Effort: 1-2 สัปดาห์ ไฟล์: vtrc-api/api/src/lib/repositories/session/session.js

งาน:

  1. เขียน test matrix (role × menu × action) ทุกกรณี
  2. รัน test กับ code ปัจจุบัน — บันทึกผลเป็น baseline
  3. แก้ source ให้ใช้ lookup path เดียวกัน
  4. รัน test อีกครั้ง — ผลลัพธ์ควรดีขึ้นหรือเท่าเดิม
  5. เอกสาร source of truth ของ RBAC

Definition of Done:

  • [ ] test matrix ครอบคลุมทุกกรณี
  • [ ] ไม่มีการอ้างอิง property ที่ไม่มีอยู่ (role.accessRole)
  • [ ] policy test ผ่าน CI

Task 1.6 · Session indexed lookup (PERF-3)

Debt: PERF-3 Effort: 1-2 วัน ไฟล์: vtrc-api/api/src/models/core/session/session.js, repository

งาน:

  1. เพิ่มคอลัมน์ jwtId (indexed) ในตาราง Session
  2. migration script เพื่อเติม jwtId จาก JSON ที่มีอยู่
  3. เปลี่ยน lookup จาก userSession LIKEWHERE jwtId = ?
  4. ทดสอบ performance ก่อน/หลัง

Definition of Done:

  • [ ] ตาราง Session มีคอลัมน์ jwtId (indexed)
  • [ ] lookup ใช้ jwtId
  • [ ] performance ดีขึ้น (> 10x เร็วขึ้นสำหรับตารางใหญ่)

Task 1.7 · แก้ transaction swallow (CORR-2)

Debt: CORR-2 Effort: 1-2 วัน ไฟล์: vtrc-api/api/src/lib/repositories/session/session.js

งาน:

เปลี่ยนจาก .then chain เป็น async/await:

javascript
// เดิม (มี race)
Models.db.transaction().then(t => {
  return Models.Session.create(...).then(() => {
    return t.commit();
  }).catch(err => {
    t.rollback();
    responError(err);
  });
});

// ใหม่ (ชัดเจน)
const t = await Models.db.transaction();
try {
  await Models.Session.create(..., { transaction: t });
  await t.commit();
} catch (err) {
  await t.rollback();
  throw err;
}

Definition of Done:

  • [ ] ไม่มี .then chain ที่มี transaction
  • [ ] test กรณี create fail → rollback เกิดจริง

Sprint 9-10 · Dependency upgrade + Frontend

Task 1.8 · ยกระดับ redis, jsonwebtoken (LEG-12, LEG-9 — ทำต่อจาก Wave 0)

Effort: 2-3 วัน ไฟล์: vtrc-api, cu-central-api

งาน:

  • redis v3 → v4 (API เปลี่ยนเป็น Promise)
  • jsonwebtoken v9 (จาก Wave 0)
  • firebase-admin, googleapis ยกระดับ

Task 1.9 · เปลี่ยน ldapjs → ldapts (LEG-8)

Effort: 2-3 วัน ไฟล์: cu-central-api

งาน:

  • ลบ ldapjs, เพิ่ม ldapts
  • API คล้ายกันแต่ต้องตรวจทุก call site

Task 1.10 · เปลี่ยน html-pdf → Playwright (LEG-10)

Effort: 1 สัปดาห์ ไฟล์: vtrc-api, cu-central-api

งาน:

  • ลบ html-pdf, node-qpdf
  • เพิ่ม playwright + pdfcpu (สำหรับ encryption)
  • เขียน wrapper ใหม่ที่มี interface เดียวกับเดิม

กับดัก: Playwright ใช้ headless Chrome ซึ่งใช้ memory มาก — ต้องจำกัด concurrent generation

Task 1.11 · Frontend — Apollo cache policy (PERF-2)

Effort: 1-2 วัน ไฟล์: vtrc-web/src/config/apollo.js

งาน:

  • เปลี่ยน default fetchPolicy เป็น cache-and-network
  • เพิ่ม cache-first สำหรับ query ที่เปลี่ยนน้อย (organization list, profile)
  • ทดสอบ UX ใน UAT

Task 1.12 · Frontend — AntD v3 → v4 (LEG-5)

Effort: 1 สัปดาห์ ไฟล์: vtrc-web

งาน:

  • อัปเดต antd v3 → v4
  • ใช้ codemod ของ AntD เพื่อ migrate
  • ทดสอบทุกหน้า

Sprint 11-12 · Infrastructure

Task 1.13 · สร้าง CI/CD สำหรับ 3 repos

Effort: 1 สัปดาห์ ไฟล์: cu-central-api, centralize-api, vtrc-rc-backoffice

งาน:

  • เขียน bitbucket-pipelines.yml สำหรับแต่ละ repo
  • pipeline: lint → test → build → push image → (manual deploy)
  • เพิ่ม deployment environment ใน Bitbucket

Task 1.14 · Centralized logging

Effort: 1 สัปดาห์

งาน:

  • ติดตั้ง Loki + Grafana (บน host ใหม่หรือ GCP VM)
  • ติดตั้ง Promtail บนทุก host เพื่อส่ง log
  • สร้าง dashboard สำหรับ error rate, slow request

Task 1.15 · Health endpoint ทุก service

Effort: 1-2 วัน

งาน:

  • เพิ่ม /health endpoint ที่ตรวจสอบ DB + Redis connection
  • ใช้ใน docker-compose healthcheck
  • ตั้ง uptime monitor ใหม่ให้ใช้ /health

Task 1.16 · ลบ _bk files 131 ไฟล์ (LEG-14)

Effort: 1-2 วัน

งาน:

  • ลบไฟล์ทั้งหมดที่ตรง pattern _bk*, _copy*, _removed*
  • เพิ่ม .gitignore + pre-commit hook

เกณฑ์การตัดสินใจว่า Wave 1 เสร็จ

  • [ ] Node runtime ทุกตัวเป็น LTS 20+
  • [ ] ทุก service มี health endpoint
  • [ ] CI/CD ครอบคลุม 5/5 repos
  • [ ] centralized logging + monitoring ทำงาน
  • [ ] High debt ลดลง ≥ 15 รายการ
  • [ ] MTTR < 1 ชั่วโมง (วัดจาก monitoring)
  • [ ] team retrospective — บันทึก lesson learned

ความเสี่ยงของ Wave 1

ความเสี่ยงผลกระทบMitigation
esm migration มี bugservice พังทดสอบใน UAT นาน, ค่อย ๆ migrate ทีละ module
Apollo v4 breaking changeendpoint พังเขียน integration test ก่อน migrate
RBAC fix เปลี่ยน behaviorผู้ใช้บางคนเสียสิทธิ์test matrix ครอบคลุม, ค่อย ๆ roll out
AntD v3 → v4UI พังcodemod + ทดสอบทุกหน้าใน UAT

สรุป Wave 1

Wave 1 ใช้เวลา 3 เดือน ลด High debt ได้ 15+ รายการ และวาง infrastructure สำหรับ Wave 2

เมื่อ Wave 1 เสร็จ ทีมพร้อมเริ่ม Wave 2 (extract bounded context ไป Go)

บทถัดไปจะลงรายละเอียด Wave 2