Skip to content

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
1document-rendererself-contained, แก้ SEC-1/PERF-1 root causeSEC-1, PERF-11-2 เดือน
2identityปลดล็อก context อื่น, แก้ SEC-3/4, CORR-4SEC-3, SEC-4, CORR-42-3 เดือน
3payrollread-mostly, ขอบเขตชัด(debt รอง)1-2 เดือน
4time-attendancehigh-volume, ได้ประโยชน์จาก cache(debt รอง)1-2 เดือน
5leaveworkflow หลายขั้น, ขอบเขตชัด(debt รอง)2 เดือน
6welfarestate machine 13 สถานะ, controller 3,179 LOCPERF-63-4 เดือน
7provident-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 → PDF
  • pdfcpu สำหรับ encryption
  • xuri/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
└── Dockerfile

Sprint 14-15 · implement

Task 2.1.4 · implement renderer

  • POST /render/pdf — รับ HTML + options, return PDF
  • POST /render/excel — รับ data + template, return xlsx
  • POST /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

javascript
// 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 ใหม่
15% (internal test user)
225%
350%
4100%

หมายเหตุ: ทุกขั้นต้องสังเกต error rate, latency, และผู้ใช้ complaint

Task 2.1.8 · ลบ code เดิม

เมื่อ service ใหม่รับ 100% traffic มา ≥ 2 สัปดาห์โดยไม่มีปัญหา:

  • ลบ html-pdf, node-qpdf dependency
  • ลบ 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 โดย:

  1. ปิด feature flag — traffic กลับไป code เดิมทันที
  2. (ถ้า 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 Gomigration ช้าtraining + pairing + ทำ context เล็กก่อน
Migration มี bugข้อมูลผิดcontract test + dual-write + gradual cutover
Service ใหม่ช้ากว่าเดิมUX แย่ลงload test ก่อน cutover
Domain boundary ไม่ชัดต้อง reworkinvest 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)