11.2 · Cross-service coupling — the "distributed monolith" pattern
ข้อค้นพบเชิง architecture ที่สำคัญที่สุดของ manual ฉบับนี้ — VTRC ไม่ได้เป็น microservices ในความหมายที่ตำรานิยาม แต่เป็น distributed monolith: หลาย service ที่ deploy แยกกัน แต่แชร์ physical database กันตรง ๆ โดยไม่ผ่าน API ของ service เจ้าของข้อมูล
นิยามที่ใช้ในบทนี้ — "cross-service coupling" หมายถึงกรณีที่ service A เปิด connection (TypeORM/Sequelize/raw driver) ไปที่ database ที่ service B เป็นเจ้าของ/เขียนหลัก แทนที่จะเรียก API/GraphQL ของ B
ตารางรวมทุกเส้น coupling ที่พบ
| # | จาก (caller) | ไปที่ (database ที่ไม่ได้เป็นเจ้าของ) | วิธีเข้าถึง | ระดับความเสี่ยง | อ้างอิง |
|---|---|---|---|---|---|
| 1 | benefit-api | vtrc schema (MSSQL) | raw SQL string interpolation | สูง — SQL injection surface, ไม่มี parameterization | SEC-BENEFIT-02 (Critical), Volume 6 benefit-api/03-persistence |
| 2 | benefit-api | hrmi (MSSQL) | raw SQL cross-database query | กลาง — read-mostly แต่ไม่มี access control ที่ layer ตัวเอง | Volume 6 benefit-api/03-persistence |
| 3 | workflow-api | vtrc (MSSQL, TypeORM connection vtrc) | ORM connection ตรง | กลาง-สูง — service ที่ไม่ใช่เจ้าของ schema เขียน/อ่านผ่าน entity ของตัวเอง | CORR-WORKFLOW-01 (High), Volume 6 workflow-api/03-persistence-integrations |
| 4 | workflow-api | hrmi (dbHRMI_Center, read) | ORM connection ตรง | กลาง — read-only ลด blast radius แต่ยังเป็นการข้าม HR data owner boundary | เดียวกันกับ #3 |
| 5 | workflow-api | hrmi_nbc (dbHRMI_Center_NBC, read) | ORM connection ตรง | กลาง | เดียวกันกับ #3 |
| 6 | workflow-api | benefit (read) + benefit_write | ORM connection ตรง สองทาง (อ่าน + เขียน) | สูง — เขียนเข้า database ของ benefit-api ตรง โดย benefit-api ไม่รู้ตัว (audit log เขียนเข้า benefit_write.healthCheckLogs) | เดียวกันกับ #3 |
| 7 | pms-api | dbWorkforce | ORM connection ตรง จาก 10 module | สูง — จำนวน module ที่พึ่งพา DB นอกทำให้ refactor/ย้าย schema ยากมาก | Volume 6 pms-api/03-persistence |
| 8 | 2way-api | pms schema (cron NotificationPmsTaskService) | cross-schema SQL query pms.dbo.tbnotify ↔ [2way].dbo.userline | กลาง — cron job ข้าม platform group (2way → Domain Microservices) โดยไม่ผ่าน API ของ pms-api | Volume 5 2way-api/03-persistence-integrations |
| 9 | chat-center-api | 2way database (connection main) | TypeORM connection ตรง พร้อม synchronize: true | สูงสุดในบทนี้ — auto-schema-alter ไปกระทบ database ที่ 2way-api เป็นเจ้าของ ถ้า chat-center-api deploy entity ใหม่ TypeORM จะพยายามแก้ schema ของ database ที่ตัวเองไม่ได้เป็นเจ้าของ | CORR-CHAT-02, Volume 6 chat-center-api/03-persistence-integrations |
| 10 | chat-center-api | dbHRMI_Center (connection center) | raw SQL cross-schema ไปหา dbHRMI_Center.dbo.temp_pee_na_table | กลาง | เดียวกันกับ #9 |
| 11 | sso-api | dbHRMI_Center.dbo.temp_pee_na_table | raw SQL cross-schema (ตารางเดียวกันกับที่ chat-center-api อ่าน) | กลาง — สอง repo คนละกลุ่มพึ่งพา temp table ตัวเดียวกันที่ชื่อขึ้นต้นด้วย temp_ (มักหมายถึง scratch table ที่ไม่ควรถูก treat เป็น stable interface) | Volume 6 sso-api/03-persistence-integrations |
| 12 | recruitment-api (connection ai) + ai-recruitment-api (raw mssql driver) | recruitment_ai | สอง repo เขียนคนละ column set ของ jobReqPosition แบบคู่ขนานกับ recruitment.dbo.jobReqPosition | สูง — ไม่มี sync mechanism ที่ยืนยันได้ ข้อมูลสองชุดอาจ drift | Volume 6 ai-recruitment-api/03-persistence |
| 13 | centralize-api | dbHRMI_Center + dbHRMI_Center_NBC | TypeORM connection ตรง (center/nbc) | ต่ำ — ตัวนี้คือ "official" bridge ที่ตั้งใจให้เข้าถึง HR data โดยตรง (pure read facade ไม่มี mutation) แต่ยัง caller จาก vtrc-api ที่ระบบใช้จริง (END_POINT_CENTRALIZE → cu-central-api) ไม่ใช่ centralize-api — UNCONFIRMED ว่า production เรียก centralize-api เส้นนี้จริงหรือเปล่า | Volume 3 centralize-api/02-rest-service-entities, canonical-truths.md debt E-08 |
| 14 | pms-api | vtrc-api (env VTRC_SERVER ประกาศไว้) | UNVERIFIED — เจอ env var แต่ไม่เจอ caller จริงในโค้ด | ต่ำ (ถ้าไม่ได้ใช้จริง) | Volume 6 pms-api/04-auth-integrations |
Pattern ที่ "สุขภาพดี" — เทียบไว้เป็น baseline
ไม่ใช่ทุก integration เป็น distributed monolith — มีบาง service ที่เรียกกันผ่าน API ตามหลัก microservices จริง:
| จาก | ไปที่ | วิธี |
|---|---|---|
meeting-vtrc-api | vtrc-api | REST/GraphQL call สำหรับ token verification + push notification (ไม่แตะ MariaDB ตรง) |
2way-meeting-backoffice | 2way-vtrc-api | REST call (ไม่แตะ MSSQL ตรง) |
benefit-api | vtrc-common | API call (ไม่แตะ vtrc_common DB ตรง) |
recruitment-api/ai-recruitment-api/benefit-api/pms-api auth | ใช้ shared JWT secret แทน DB call ข้าม service — ยังเป็นความเสี่ยงด้าน security (ดู Volume 12) แต่ไม่ใช่ data coupling |
Contrast นี้แสดงว่าทีมงานรู้จัก pattern API-mediated call อยู่แล้ว — cross-service DB coupling ที่พบใน #1-13 ด้านบนน่าจะมาจาก deadline pressure หรือ perf shortcut (เลี่ยง network round-trip) มากกว่าไม่รู้ pattern ที่ถูกต้อง
ทำไม pattern นี้อันตราย
1. ไม่มี migration ใดที่ปลอดภัยฝ่ายเดียว
ถ้า benefit-api ต้องการเปลี่ยน schema ตาราง WdHospitals (benefit DB) ต้องตรวจสอบก่อนว่า workflow-api (ผ่าน benefit_write) อ่าน/เขียน column ไหนอยู่ — ไม่มี contract ที่บังคับ backward compatibility เพราะ workflow-api ไม่ได้ผ่าน API ของ benefit-api
2. Access control กระจุกอยู่ที่ layer เดียวที่ bypass ได้
benefit-api มี Passport JWT strategy คุม endpoint ของตัวเอง แต่ workflow-api ที่เขียนตรงเข้า benefit_write ไม่ผ่าน guard นั้นเลย — ถ้า workflow-api ถูก compromise (และตัวมันเองไม่มี JWT guard ทั่วระบบ — SEC-WORKFLOW-01) ผู้โจมตีเขียนข้อมูลเข้า benefit DB ได้โดยไม่ผ่าน validation ของ benefit-api เลย
3. synchronize: true ข้าม service boundary คือความเสี่ยงเชิงโครงสร้างที่รุนแรงสุด
กรณี chat-center-api → 2way database (#9) ไม่ใช่แค่ "อ่าน/เขียนข้อมูลของคนอื่น" — TypeORM synchronize: true หมายความว่าทุกครั้งที่ deploy chat-center-api และมี entity เปลี่ยน TypeORM จะรัน ALTER TABLE/CREATE TABLE บน database ที่ 2way-api เป็นเจ้าของ โดย 2way-api ไม่มีทางรู้หรือ approve การเปลี่ยนแปลงนั้นล่วงหน้า
4. Cascade delete/update ไม่มีทางทำงานข้าม engine
pms-api → dbWorkforce (#7), workflow-api → 4 connection ข้าม engine (#3-6) — ไม่มี foreign key ข้าม physical database ในทั้ง MSSQL/MariaDB ดังนั้นทุก "delete" ต้อง manual cascade เอง (เหมือนที่ v1 Volume 8.9 บันทึกไว้สำหรับ legacy 3 service — v2 พบว่า pattern นี้ขยายไปทั่วทั้ง 25 repo ไม่ใช่แค่ legacy)
สรุป blast radius ของ pattern นี้
┌─────────────┐
┌─────────►│ benefit │◄────────┐
│ └─────────────┘ │
┌────────┴────┐ ┌───────┴──────┐
│ workflow-api │──────┬───────────────►│ vtrc (MSSQL) │
└────────┬─────┘ │ └──────────────┘
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ dbHRMI_Center│ │dbHRMI_Center│
│ │ │_NBC │
└─────────────┘ └─────────────┘
┌─────────────┐ ┌─────────────┐
│ 2way-api │◄──────►│chat-center-api│ (synchronize:true ⚠)
└──────┬──────┘ └─────────────┘
│
▼
┌─────────────┐
│ pms (MSSQL)│
└─────────────┘
┌─────────────┐ ┌─────────────┐
│ pms-api │───────►│ dbWorkforce │ (10 modules)
└─────────────┘ └─────────────┘
┌──────────────┐ ┌──────────────┐
│recruitment-api│◄►│ai-recruitment-│ (jobReqPosition คู่ขนาน)
└──────────────┘ │ api │
└──────────────┘workflow-api เป็น service ที่มี blast radius กว้างที่สุด — connection เดียวเปิดไปได้ถึง 4 database ของ 3 owner ต่างกัน (vtrc-api, HRMI, benefit-api) จาก TypeORM config เดียว
ผลกระทบต่อ identity model จาก coupling เหล่านี้ ดู 11.3 Identity across systems →