Skip to content

2.8 · ข้อจำกัดทางสถาปัตยกรรม + debt

บทสุดท้ายของ Volume 2 — เล่า "สิ่งที่ผิดธรรมดา" และ "จุดที่เปราะบาง" ที่ต้องรู้ก่อนจะแตะโค้ด

ไม่ใช่รายการ bug ครบถ้วน — รายละเอียดเชิงลึกอยู่ใน Volume 10 Security และ Volume 15 Tech Debt — บทนี้เป็นแค่ "things you should know ตอนเริ่มงาน"


ข้อจำกัดเชิงโครงสร้าง

1. ไม่มี API gateway จริง

Nginx ทำแค่ TLS + static — ไม่มี rate limit, ไม่มี auth, ไม่มี routing logic ทุกอย่างผ่าน vtrc-api ที่เป็น Node.js ตัวเดียว

ผล — ถ้า vtrc-api ล่ม ทั้งระบบล่ม ไม่มี fallback

2. ไม่มี bounded context

vtrc-api มี GraphQL schema เดียวที่รวม 43 typeDef (vtrc-api/api/src/typeDefs/) — ไม่ได้แบ่งตาม domain (Identity, Payroll, Leave, Welfare, Fund)

ผล — แก้อะไรครั้งหนึ่งกระทบได้หลายส่วน, code review ยาก

3. 2 transport surfaces ที่ทับซ้อน

GraphQL (vtrc-api/api/src/typeDefs) + REST (vtrc-api/api/src/routes.js 1302 บรรทัด) คู่กัน บาง logic เข้าถึงได้ 2 ทาง

ผล — auth policy ต้องรักษา 2 ที่ให้ sync กัน ซึ่งทำไม่ครบ (REST บาง route ไม่มี auth)

4. ไม่มี migration runner

centralize-api ใช้ TypeORM synchronize: false แต่ไม่มี migration runner vtrc-api ใช้ syncModelToTable() (vtrc-api/api/src/lib/model.js) ที่ auto-sync Sequelize model → DB schema

ผล — schema drift เกิดอย่างเงียบ เวลาแก้ model แล้ว deploy ไม่ครบทุก env


จุดที่เปราะบางที่สุด (severity ranking)

Severity: Critical

S1 · execSync('qpdf --encrypt ${password}...') — command injection

ที่ — vtrc-api/api/src/routes.js:73, 120, 227, 277, 355

password ที่ interpolate เข้า shell มาจาก user (passwordPDF, identityCard) — ถ้าค่ามี shell metacharacter ก็ execute ได้

วิธีแกะ — ถ้าเจอ PDF endpoint ที่ generate ด้วย qpdf → assume vulnerable จนกว่าจะพิสูจน์เป็นอย่างอื่น

S2 · /webdeployment รัน shell binary โดยไม่มี auth

ที่ — vtrc-api/api/src/routes.js:975-984

execSync("vtrc-update-uat-loadtest")

ไม่มี auth ไม่มี parameter check — ใครยิงปุ๊บรันปุ๊บ

S3 · /file/:id/download ไม่ตรวจ apiKey

ที่ — vtrc-api/api/src/routes.js:464-495

sibling endpoint /file/:id มี apiKey gate (:389-462) แต่ตัว download ไม่มี → ใครมี file id ก็ download ได้

S4 · phpMyAdmin เปิดสู่สาธารณะ

ที่ — vtrc-api/docker-compose.yml:35-46

PMA_ABSOLUTE_URI: http://vtrcapi.redcross.or.th/db-management/ — DB admin เปิดที่ public URL

S5 · JWT_SECRET default เป็น 'secret'

ที่ — vtrc-api/api/src/config.js:12

ถ้า env ไม่ตั้ง → ใครก็ forge JWT ได้


Severity: High

H1 · signInByPass — mint JWT โดยไม่ตรวจ password

ที่ — cu-central-api/main-api/src/lib/auth/auth.js:173-231 + expose ที่ routes.js:31-37

POST /api/auth/signinbypass
  body: { username, org, apiKey }
  → mint JWT ทันที ไม่ต้อง password

H2 · Secret ใน git

  • centralize-api/.env:10,16 — DB password + JWT secret (plaintext)
  • cu-central-api/docker-compose-dev.yml:56,70 — MSSQL SA password + LDAP admin password
  • vtrc-api/vtrc-gcp-sa-key.json — GCP service account key
  • vtrc-api/api/src/config.js:41-149 — prod device keys

H3 · LDAP TLS verification ปิดอยู่

ที่ — cu-central-api/main-api/src/lib/auth/activeDirectory.connector.js:21

rejectUnauthorized: false → MITM ได้

H4 · Node 12 EOL

ที่ — cu-central-api/main-api/Dockerfile:1

Node 12 EOL เมื่อ April 2022 — ไม่มี security patch แล้ว

H5 · Redis cache ใช้ไม่ได้จริง

ดู บท 2.5 — call site ส่วนใหญ่ comment ไว้ → ระบบช้ากว่าที่ควร


Severity: Medium

M1 · cron leader election ผ่าน env var

ที่ — vtrc-api/api/src/crons.js:14

ถ้า replica #1 down → cron หายทั้งระบบ ไม่มี fallback

M2 · ไม่มี health check endpoint

ไม่มี /health หรือ /readiness → Docker หรือ load balancer ไม่รู้สภาพจริง

M3 · Nginx network_mode: host

ที่ — vtrc-api/docker-compose.yml:35

container share host network → ไม่มี port isolation

M4 · Frontend ปิด console.log ทั้งหมด

ที่ — vtrc-web/src/containers/App/index.js:15

debug ต้องเปิดเองทุกครั้ง แล้วอย่าลืมปิดกลับ

M5 · _bk files จำนวนมาก

vtrc-web/src/graphql/queries_bk*.js, routes_bk*.js ฯลฯ — ประมาณ 50% ของ LOC เป็น dead code

ผล — code search ช้า, reviewer สับสน


Severity: Low (nuisance)

L1 · Typo ที่ต้อง maintain

  • PEDDING_CANCLE (ผิดจาก PENDING_CANCEL) — เป็น enum ที่ใช้ในหลายที่แล้ว ต้อง maintain ต่อไป
  • CONINCLE_USERNAME (ผิดจาก CONICLE) — vtrc-api/api/src/config.js:34
  • nameEN vs nameEn (case ไม่เหมือนกัน) — vtrc-api/api/src/config.js:323, 335
  • querries_prd.js (ผิดจาก queries) — vtrc-web/src/graphql/
  • finanace.js resolver (ผิดจาก finance) — cu-central-api/main-api/src/resolvers/

L2 · "carpark-api" log filename ผิด project

ที่ — cu-central-api/ecosystem.config.js:7-8

copy-paste จากอีก project ไม่ได้แก้

L3 · StimulSoft ติดตั้ง 2 ทาง

vtrc-rc-backoffice/public/index.html:29-33 โหลดเป็น script tag + package.json มีเป็น npm dep — ติดตั้งซ้อนทำกัน


ตาราง Health Scorecard

จาก Audit Doc 02 (docs/audit/02-architecture-map.md)

  • 🟢 เขียว — ผ่านเกณฑ์ ไม่มีปัญหา
  • 🟡 เหลือง — ใช้ได้ แต่มีจุดอ่อน ต้องระวังตอนแก้
  • 🔴 แดง — มีปัญหาจริง ควร refactor
Repo🟢🟡🔴n/a
vtrc-api0530
vtrc-web2132
backoffice1142
centralize-api5030
cu-central-api1520
Dimensionvtrc-apivtrc-webbackofficecentralize-apicu-central-api
Layering clarity🟡3-layer + REST bypass🔴folder-by-type🔴folder-by-type + 16 axios🟢NestJS modules🟡mirror of vtrc-api
Bounded-context🔴1 schema, 43 modules🔴1 queries.js🔴1 queries.js🟢per-module entity🔴1 schema, 30 modules
Dependency direction🟡🟡🟡🟢DI🟡
Transport coupling🔴GraphQL + REST + execSync🟢🔴GraphQL + 16 axios🟢REST + Swagger🟢
Auth boundary🟡directive + bypass🟢🟢🟢JwtAuthGuard + @Public🟡directive
Reproducible deploy🟡🔴no lockfile🟡
HA / scaling🟡manual replicas🔴single instance🔴single instance
Testability🔴no tests, execSync inline🔴🔴🟢jest + supertest🟡mocha/chai

อ่านตารางอย่างไร — มองสีก่อน (🟢🟡🔴) แล้วค่อยอ่านหมายเหตุใต้สี ตารางสรุปด้านบนช่วยเห็นภาพรวมว่า repo ไหนแดงมากที่สุด


สิ่งที่ต้อง maintain ต่อไป (แม้จะผิด)

รายการต่อไปนี้ดูผิด/แปลกตา แต่แท้จริงแล้วถูกใช้อยู่ทั่วระบบและ "มีคนพึ่งพาอยู่" — ห้ามมาแก้ทีเดียวจบ เพราะกระทบ production หลายจุดพร้อมกัน การแก้แต่ละอันต้องประสานงานและทำควบคู่กันหลายที่ (DB, config, code) มิฉะนั้นระบบจะพังแบบเงียบ

  • PEDDING_CANCLE enum — สะกดผิดมาตั้งแต่ต้น (ที่ถูกคือ PENDING_CANCEL) แต่ค่านี้เก็บอยู่ใน หลายตาราง ของ DB แล้ว ถ้าจะแก้ชื่อ ต้องเขียน migration ครบทุกตารางพร้อมกัน ไม่งั้น query เทียบ enum จะ match ไม่ติด
  • KEY_PLASMA, KEY_CU_H, KEY_HR (UUID) — UUID คงที่ที่ฝังตรงใน business rule ของแต่ละระบบ (PLASMA / CU-H / HR) ถ้าจะเปลี่ยน ต้องเปลี่ยนให้ตรงกันทุกที่ที่อ้างถึง ไม่งั้นกฎทางธุรกิจจะตีความผิด
  • END_POINT_CENTRALIZE URL pattern — env var ที่ใช้ในทุก environment config (local / UAT / prod) เปลี่ยน pattern ที่เดียวไม่ได้ ต้อง sync ครบทุก env มิฉะนั้น service เรียกข้ามไม่ถึงกัน
  • APP_NO values 1, 2, 3, 5 (ไม่มี 4) — ลำดับนี้ข้ามเลข 4 โดยเจตนา/มรดกตกทอด และถูกใช้ใน cron leader election เปลี่ยนลำดับหรือเติม 4 เข้าไปแล้วลำดับการเลือก leader จะสับสน → cron รันซ้อนหรือไม่รันเลย
  • CORS allowlist (5 origins) — รายการ origin ที่อนุญาตฝังอยู่ใน config ถ้าลบหรือเพิ่มโดยไม่ครบ → frontend บางตัวจะถูก browser block ที่หน้าโพสต์

กฎเหล็ก — เจอแล้วไม่แน่ใจอย่าเดาเอง ให้ถามทีม HR/i3 ก่อนเสมอ


เคล็ดลับตอนทำงานจริง

ถ้าทำ modernization — เริ่มที่ไหน

จากประสบการณ์ — ลำดับความคุ้มค่า (ROI)

  1. เพิ่ม health check + observability — investment เล็ก คุณค่ามหาศาล (เห็นปัญหาก่อน user แจ้ง)
  2. แทนที่ execSync qpdf — เปลี่ยนเป็น library pure JS หรือ worker thread
  3. แยก REST ออกเป็น service ต่างหาก — PDF/Excel generation เป็น workload คนละแบบ ไม่ควรอยู่ใน GraphQL server
  4. เพิ่ม migration runner — goose, atlas, หรือ TypeORM migration
  5. แทนที่ cron ด้วย BullMQ + Redis lock — leader election ที่เชื่อถือได้

ส่วน React 16 → 18, Apollo v2 → v3+, Sequelize → TypeORM — เป็นเรื่องใหญ่ ไม่คุ้มถ้าไม่มี feature ใหม่ที่ต้องการ

อย่า refactor พร้อมกันหลายอย่าง

เพราะทุก change มี risk — ถ้า refactor 3 อย่างพร้อมกันแล้วพัง → ไม่รู้ว่าสาเหตุมาจากอะไร

กฎ — หนึ่ง change ต่อ PR เสมอ

"มันใช้งานได้" ≠ "มันปลอดภัย"

VTRC ใช้งานมาหลายปีแล้ว แต่มี hole หลายจุด (ดู S1-S5 ข้างบน) — ถ้าทำ penetration testing จะพบจำนวนมาก

ก่อนจะ expose endpoint ใหม่สู่ internet → ผ่าน security review เสมอ


จบ Volume 2

ถ้าอ่านมาถึงตรงนี้ — คุณน่าจะเข้าใจภาพใหญ่ของ VTRC พอที่จะเริ่มแตะโค้ดได้

บทถัดไปขอแนะนำ

ขอบคุณที่อ่านมาถึงตรงนี้