Skip to content

2.1 · ภาพรวมสถาปัตยกรรม

บทนี้เล่าว่า VTRC มีกี่ส่วน แต่ละส่วนทำหน้าที่อะไร และทำไมถึงออกแบบเป็นแบบนี้

ถ้าอ่านบทเดียวในเล่ม 2 — อ่านบทนี้


กฎเหล็กข้อที่ 1 — ทุก request ผ่าน vtrc-api

vtrc-web ─┐
          │  GraphQL over HTTPS

       vtrc-api ──► MariaDB (vtrc)
          │      ──► MariaDB (vtrc-centralize)

          │  GraphQL (internal) — END_POINT_CENTRALIZE
          ├──► cu-central-api  ──► MSSQL (HRMI Center + NBC)
          │                     ──► LDAP/AD
          │                     ──► SMTP / SFTP

          │  (caller ของ NestJS centralize-api ยังไม่ยืนยัน — ดูบท 5.9)
          │  NestJS centralize-api ──► MSSQL (HRMI Center + NBC)

vtrc-rc-backoffice ─┘ (เหมือน vtrc-web)

สำคัญEND_POINT_CENTRALIZE ของ vtrc-api ชี้ไป cu-central-api GraphQL ไม่ใช่ NestJS centralize-api (port 3000 REST) — พอร์ตต้องแยกชั้น: CODE_DEFAULT ของ cu-central คือ APP_PORT=8080; CLIENT_TARGET ใน vtrc-api/api/.env.example อาจเป็น 8082; compose มัก map 8000; Dockerfile EXPOSE 9000 ≠ listen ดู บท 3.4 และ caller ใน บท 5.9

frontends (vtrc-web, vtrc-rc-backoffice) ไม่เคย เรียก centralize-api หรือ cu-central-api โดยตรง — เป็นกฎเหล็กที่บังคับใช้ด้วย network topology ไม่ใช่แค่ code convention

ดังนั้น vtrc-api จึงเป็นทั้ง API gateway + business logic + aggregation layer ไปพร้อมกัน ไม่มี API gateway แยกต่างหาก ไม่มี service mesh — Nginx ทำแค่ TLS termination กับ static hosting (vtrc-api/docker-compose.yml:14-34)


5 บริการหลัก

บริการStackหน้าที่อายุ
vtrc-webReact 16 + Webpack 4 + AntD v3 + Apollo v2frontend ฝั่งพนักงานเก่า (Critical-EOL)
vtrc-rc-backofficeReact 16 + CRA + AntD v4 + MUI v5 + Recoil + Apollo v2frontend ฝั่ง HRเก่า (Critical-EOL)
vtrc-apiNode.js + Apollo GraphQL v2 + Sequelize 6 + ExpressAPI แกนกลาง ทุกอย่างผ่านที่นี่เก่า (load-bearing)
centralize-apiNestJS 9 + TypeORM + MSSQLfacade สำหรับ HR data สมัยใหม่ใหม่ (modernization reference)
cu-central-apiNode 12 + Apollo GraphQL v2 + Sequelize + LDAPidentity service + raw HR data (เดิม)เก่าที่สุด (Critical-EOL)

ภาพใหญ่ — แบ่งตามชั้นความรับผิดชอบ

┌──────────────────────────────────────────────────────────────┐
│ Tier 1 — Edge (Nginx)                                          │
│   • TLS termination                                            │
│   • static hosting (frontend build)                            │
│   • reverse proxy → vtrc-api                                   │
└──────────────────────────────────────────────────────────────┘


┌──────────────────────────────────────────────────────────────┐
│ Tier 2 — Presentation (frontends)                              │
│   • vtrc-web (end-user)                                        │
│   • vtrc-rc-backoffice (HR)                                    │
│   • เลือก GraphQL endpoint ตอน runtime ด้วย window.location    │
└──────────────────────────────────────────────────────────────┘

                              │  HTTPS GraphQL/REST + JWT + apiKey

┌──────────────────────────────────────────────────────────────┐
│ Tier 3 — API Gateway + Business Logic (vtrc-api)               │
│   • 4 replicas (APP_NO = 1, 2, 3, 5)                           │
│   • Apollo GraphQL + Express REST (สอง surface คู่กัน)         │
│   • 3-layer internal: typeDefs → resolvers → controllers       │
│   • @auth directive บังคับ RBAC + ABAC                          │
└──────────────────────────────────────────────────────────────┘


┌──────────────────────────────────────────────────────────────┐
│ Tier 4 — Domain / Bounded Services                             │
│   • centralize-api (NestJS, modern)                            │
│   • cu-central-api (Node 12, legacy)                           │
└──────────────────────────────────────────────────────────────┘


┌──────────────────────────────────────────────────────────────┐
│ Tier 5 — Persistence + Infra                                   │
│   • MariaDB (2 schemas: vtrc + vtrc-centralize)                │
│   • MSSQL (HRMI: dbHRMI_Center + dbHRMI_Center_NBC)            │
│   • Redis (cache/session/OTP — แต่ใช้น้อย)                       │
│   • LDAP/AD (identity)                                          │
│   • GCP (Drive + FCM)                                          │
│   • SMTP, SFTP                                                  │
└──────────────────────────────────────────────────────────────┘

ทำไมถึงเป็นแบบนี้ — บริบททางประวัติศาสตร์

VTRC ไม่ได้ออกแบบจากกระดานชอล์ก — มันโตมาจากการเติมทีละชิ้นตามความต้องการจริง

  1. ยุคที่ 1 (cu-central-api) — เริ่มจากระบบ HR ของจุฬาลงกรณ์มหาวิทยาลัย ที่สภากาชาดใช้ร่วมด้วย ตอนนั้นมีแค่ identity + LDAP + HR raw data จึงเป็น Node 12 + Sequelize + Apollo เก่า
  2. ยุคที่ 2 (vtrc-api + frontends) — สภากาชาดต้องการระบบ HR/welfare ของตัวเอง จึงสร้าง vtrc-api เป็น API ใหม่ที่เชื่อมไปยัง cu-central-api ผ่าน END_POINT_CENTRALIZE (GraphQL; CLIENT_TARGET ตัวอย่างอาจใช้พอร์ต 8082 ในขณะที่ CODE_DEFAULT ของ cu-central เป็น 8080) แล้วเพิ่ม business logic ฝั่งตัวเอง
  3. ยุคที่ 3 (NestJS centralize-api) — ทีมเริ่มปรับปรุงระบบสมัยใหม่ จึงตัดสินใจยกตัวอย่างใหม่ให้เป็น NestJS + TypeScript + Swagger ตั้งใจให้เป็น template สำหรับ service ใหม่ในอนาคต — I3GatewayLogger (centralize-api/src/libs/log/logs.middleware.ts:8) เป็นลายเซ็นของทีม modernization

ข้อควรระวัง — caller ที่แท้จริงของ NestJS centralize-api (port 3000) ยังไม่ยืนยันจาก code ใน workspace นี้ END_POINT_CENTRALIZE ของ vtrc-api ชี้ไป cu-central-api ไม่ใช่ NestJS centralize-api ดูรายละเอียดใน บท 5.9

ผลคือระบบมี 2 generation อยู่ข้างกัน — legacy ที่ใช้งานจริง และ modernization island ที่ยังไม่ได้แทนที่อะไรเดิม


"Distributed monolith" — ลักษณะเด่นที่ต้องเข้าใจ

VTRC มีหลายบริการ แต่ ไม่ใช่ microservices ในความหมายปกติ

  • ทุกบริการต้อง deploy พร้อมกัน — ไม่มี versioning แยก
  • ไม่มี service discovery — ใช้ hardcoded env var (END_POINT_CENTRALIZE ชี้ไป cu-central-api)
  • ถ้า cu-central-api ล่ม → vtrc-api ไม่สามารถให้บริการฟีเจอร์ HR ได้ — ไม่มี fallback ไม่มี circuit breaker
  • ทุก schema อยู่ใน database เดียวกัน (MariaDB host เดียว 2 schema)

ตอนอ่านโค้ด ให้คิดว่ามันเป็น "monolith ตัวเดียวที่แบ่ง process" จะเข้าใจง่ายกว่าคิดแบบ microservices


contract ระหว่างบริการ

EdgeProtocolAuthตัวอย่าง
Browser → vtrc-api GraphQLHTTPS POSTapiKey: <device> + Authorization: Bearer <jwt>vtrc-api/api/src/index.js:33-44
Browser → vtrc-api RESTHTTPS GETURL signed (1 ชม.) — :device/:timestamp/:hashvtrc-api/api/src/routes.js:51-99
vtrc-apicu-central-apiGraphQL (END_POINT_CENTRALIZE; CLIENT_TARGET ตัวอย่าง :8082, CODE_DEFAULT cu-central APP_PORT=8080)header apiKey เมื่อส่งค่ามา (ไม่ default ใน client)graphql.js:17-23 (GraphQLClient + optional apiKey)
vtrc-apicu-central-api (REST auth)REST POST (END_POINT_CENTRALIZE_AUTH; ตัวอย่าง .env ใช้ :8082/auth)form / header ตาม auth.jsauth.js:28 (/signin)
NestJS centralize-api → MSSQLTypeORM mssqlenv DB credscentralize-api/src/app.module.ts:36-38
vtrc-api → MariaDBSequelizeenv DB creds (pool max 100)vtrc-api/api/src/connector.js:8-27
cu-central-api → LDAP/ADldapjs bindservice accountcu-central-api/main-api/src/lib/auth/activeDirectory.connector.js:26-36

รายละเอียดเชิงลึกของแต่ละ edge — ดู บท 2.3 request lifecycle และ บท 2.4 external integrations


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

อย่าตกใจที่เห็น "หลายบริการ"

เวลา debug ให้คิดว่าทั้งหมดคือ monolith ตัวเดียวที่แบ่ง process — vtrc-apicu-central-api → MSSQL มองเป็น function call หลายชั้นก็ได้ ถ้าทำใจแบบนั้นจะ debug ง่ายขึ้นมาก

ระวัง "ดูเหมือน API gateway แต่ไม่ใช่"

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

"Modernization reference" ≠ production-ready

centralize-api เป็น NestJS + TypeScript สวยงาม แต่อย่าลืมว่า

  • JWT guard ลงทะเบียนไว้ แต่ทุก route ปัจจุบันใช้ @Public() (centralize-api/src/modules/hrPaySlip/hrpayslip.controller.ts:17)
  • ไม่มี health check ไม่มี migration runner
  • ลำพัง centralize-api จึงไม่พร้อมเป็น production service แบบ standalone

Volume 1 อธิบาย "ทำไม" Volume 2 อธิบาย "อย่างไร"

ถ้าเจอ policy ที่ดูผิดปกติ ในโค้ด — กลับไปอ่าน Volume 1 ก่อน เพราะส่วนใหญ่ไม่ใช่ technical decision แต่เป็น business rule ของ HR


ขั้นตอนถัดไป

ไป บท 2.2 Topology จริงใน production