14.2 · Cross-service business flows
บทนี้ trace 6 flow ที่ business logic กระจายอยู่มากกว่า 1 repo จริง — ไม่ใช่แค่ frontend เรียก backend เดียว (นั่นคือ pattern ปกติที่ไม่ถือว่า "cross-service" ในความหมายของบทนี้) แต่เป็นกรณีที่ side-effect ของ transaction เดียวกระจายไปเขียน/อ่านข้าม repo ที่เป็นเจ้าของข้อมูลคนละชุด
14.2.1 · เบิกเงินช่วยเหลือภัยพิบัติ — cancel เขียนกลับเข้า schema vtrc ของ legacy core
Employee ──POST /disaster/create_disaster──► benefit-api (schema `benefit`)
│
Approver ──POST /approveDisaster/saveApprove──► benefit-api
├─ axios ──► centralize server (identity ไม่ยืนยัน 100%, port 3100)
└─ paymentService.addPayments() (schema `benefit`)
│
เมื่อ cancel เอกสาร docType===1 (WdHospitals) ──► PaymentsService.cancelWelfare()/cashPayment()
└─ เปิด transaction ที่สอง บน connection `vtrc`
└─ raw SQL insert เข้า vtrc.WDHospitals + vtrc.WDHistories
(คนละ transaction จาก schema `benefit` — ไม่มี 2-phase commit)เจ้าของข้อมูลที่แท้จริงกลายเป็นสองที่พร้อมกัน — benefit.wdHospitals (schema ใหม่ ที่ benefit-api ดูแล) และ vtrc.WDHospitals (schema เดิม ที่ vtrc-api ดูแล) มีข้อมูลทับซ้อนกัน การ sync ทำผ่าน raw SQL แบบ manual เฉพาะ flow cancel/update bank detail เท่านั้น ไม่มี job หรือ mechanism ที่ reconcile สอง schema แบบ periodic
หลักฐาน — benefit-api/02-core-pipeline §Step 4, benefit-api/06-scorecard CORR-BENEFIT-01
14.2.2 · ค่าธรรมเนียมการศึกษา (StudyLeave) — cancel ยิง HTTP ตรงไป vtrc-api
Employee ──ยื่นเรื่อง──► benefit-api (docType===3, StudyLeave)
│
เมื่อ cancel ──► PaymentsService ──axios.post──► https://vtrcapi.redcross.or.th/ endpoint cancelStudyEducation
│
▼
vtrc-api อัปเดตสถานะฝั่งตัวเอง (schema vtrc)ต่างจาก flow 14.2.1 ที่เขียนข้าม schema ตรง — flow นี้ผ่าน HTTP call จริง (coupling แบบ API ไม่ใช่ database) แต่เป็น HTTP call แบบ fire-without-compensation เดียวกับที่พบใน approveDisaster — ไม่มี timeout/retry/circuit breaker กำหนดเอง ถ้า vtrc-api รับ request แล้ว crash ก่อนตอบกลับ ฝั่ง benefit-api ไม่มีทางรู้ว่าฝั่ง vtrc-api cancel สำเร็จจริงหรือไม่
หลักฐาน — benefit-api/02-core-pipeline §Flow อื่นที่ pattern คล้ายกัน
14.2.3 · สร้างภาพประกาศรับสมัครงานด้วย AI — recruitment-api ↔ ai-recruitment-api แชร์ database
เจ้าหน้าที่ (vtrc-backoffice) ──เปิดฟอร์มสร้างประกาศ──► ai-recruitment-api
│
├─ SELECT ... FROM recruitment_ai.dbo.jobReqPosition
│ (database เดียวกับที่ recruitment-api เขียนผ่าน
│ connection ชื่อ `ai` — DB_AI_NAME=recruitment_ai)
├─ OpenAI GPT-4o (เนื้อหา) + DALL-E/gpt-image-1 (ภาพ)
├─ remove.bg (ลบพื้นหลัง)
└─ puppeteer render → base64 image กลับไปที่ back-office
│
เจ้าหน้าที่ตัดสินใจใช้ผลลัพธ์ ──► บันทึกกลับเข้า recruitment-api (JobReqAdvertisement)นี่คือ coupling แบบ shared-database ที่ยืนยันได้ 100% — ไม่มี network call ตรงระหว่าง recruitment-api กับ ai-recruitment-api (ไม่พบ axios/HTTP call ระหว่างสองตัวในการสำรวจ) ทั้งสองอ่าน/เขียนตาราง jobReqPosition ในฐานข้อมูลเดียวกันโดยตรง ต่างฝั่งต่าง ORM (recruitment-api ใช้ TypeORM, ai-recruitment-api ใช้ mssql driver ตรงไม่มี ORM) — ถ้า recruitment-api เปลี่ยน schema ของ jobReqPosition (เช่น rename column) โดยไม่แจ้ง ai-recruitment-api จะเกิด runtime error ที่ทีมสองฝั่งไม่รู้ตัวล่วงหน้า เพราะไม่มี API contract หรือ shared type ระหว่างกัน
หลักฐาน — ai-recruitment-api/index §Database, recruitment-api/index §Database topology
14.2.4 · ออกสัญญาจ้าง — recruitment-api → workforce-api (identity ไม่ยืนยัน) → HRMI
ผู้สมัครผ่านคัดเลือก ──► recruitment-api ContractService
│
├─ axios.get(`${workforceEndPoint}/fetchEmployeeCandidateRC`)
├─ axios.get(`${workforceEndPoint}/fetchMappingTableRc`)
│ (workforceEndPoint = WORKFORCE_SERVER/workforce-api/api/restful
│ WORKFORCE_SERVER=https://vtrcapi.redcross.or.th — mount บน domain เดียวกับ vtrc-api
│ แต่ไม่ยืนยันว่าเป็นส่วนหนึ่งของ vtrc-api หรือ service แยกที่ share domain)
├─ getWorkforceHRMiMapping() — map รหัสภายในเป็นรหัส HRMI (fallback หลายชั้น)
├─ save ContractHd/ContractEmployment/ContractHistory (schema recruitment)
└─ ส่ง email แจ้ง HRMI (ไม่ใช่ API call อัตโนมัติ — ทีม HR ต้องบันทึกเข้า HRMI ด้วยมือ)จุดอ่อนสำคัญ — การ "export ไป HRMI" ในความเป็นจริงคือการส่งอีเมล ไม่มี API contract ที่ยืนยันว่าข้อมูลถูกบันทึกเข้า HRMI จริง — ถ้าเจ้าหน้าที่ HRMI พลาดไม่ได้บันทึกตามอีเมล ข้อมูลพนักงานใหม่จะขาดหายจาก HRMI โดยที่ recruitment-api มองว่า flow เสร็จสมบูรณ์แล้ว (status เปลี่ยนเป็น hired)
หลักฐาน — recruitment-api/02-core-pipeline §Contract
14.2.5 · การประเมินผลงาน — pms-api sync จาก dbWorkforce แบบ cron ไม่ใช่ real-time
ทุก 1 นาที: pms-api UsagePermissionService.handleCron (@Cron('*/1 * * * *'))
│
├─ ดึง employee trigger queue จาก connection `workforce` (dbWorkforce)
└─ อัปเดตข้อมูลองค์กร/ตำแหน่งใน schema `pms`
│
▼
EvaluateFormModule/ConfirmEvaluationModule/SalaryAdjustment
ใช้ข้อมูลองค์กร/ตำแหน่ง/salary range จาก schema `pms`
(freshness ขึ้นกับ cron ข้างบนทำงานถูกต้องต่อเนื่อง)
│
▼
SalaryAdjustToHRMIModule ──export Excel──► ทีม HR นำเข้า HRMI ด้วยมือ (เหมือน 14.2.4 — ไม่ใช่ API sync)ความเสี่ยง silent drift — ถ้า cron UsagePermissionService.handleCron หยุดทำงาน (crash, deploy ผิด, DB connection หลุด) ไม่มี alert/health-check ใดแจ้งเตือน ข้อมูลองค์กรใน pms-api จะ drift จาก dbWorkforce โดยไม่มีใครรู้จนกว่าจะมีคนสังเกตความผิดปกติของผลประเมิน/เงินเดือนที่คำนวณผิด — เป็นความเสี่ยงเดียวกันในเชิงโครงสร้างกับ 14.2.4 (export สุดท้ายอาศัยมนุษย์นำเข้าด้วยมือ)
หลักฐาน — pms-api/02-core-pipeline §workforce sync
14.2.6 · จองประชุม — meeting-vtrc-api เป็นตัวกลางที่ต้องพึ่ง vtrc-api และ cu-central-api พร้อมกัน
พนักงาน ──สร้างการประชุม──► meeting-vtrc-api addNewMeeting
│ (auth bridge: decode JWT local → GraphQL fetchEmployeeProfileForOtherService ไปที่ vtrc-api)
▼
insert Meeting+Guest+OtherGuest+Attached (4 bulkCreate แยกกัน ไม่มี transaction ครอบ)
│
ทุก 15 นาที: cron email.corn.ts ──► ดึง ScheduleJob ACTIVE
├─ sendMail(): PDF (puppeteer) + GraphQL fetchListEmployeeForTwoWay ไปที่ cu-central-api
│ (แปลง empCode เป็นชื่อผู้เข้าร่วม)
└─ sendNoti(): GraphQL mutation sendNotificationFrom3Party ไปที่ vtrc-api
(ฝากให้ vtrc-api เป็นคนยิง push notification จริง — meeting-vtrc-api ไม่ยิง FCM เอง)นี่คือ flow เดียวใน 6 flow ที่พึ่งพา legacy core 2 ทางพร้อมกันในรอบเดียว (vtrc-api สำหรับ auth+notification, cu-central-api สำหรับ employee directory) — ถ้า vtrc-api ล่ม การประชุมจะสร้างได้ (auth bridge อาจ fail แต่ insert หลักไม่ผ่าน vtrc-api) แต่การส่งอีเมล/notification ผ่าน cron จะค้าง เพราะ sendNoti() ต้องพึ่ง vtrc-api ตอบกลับ
หลักฐาน — meeting-vtrc-api/02-core-pipeline
สรุปรูปแบบ coupling ที่พบซ้ำในทั้ง 6 flow
| รูปแบบ | พบใน flow | ความเสี่ยงร่วม |
|---|---|---|
| เขียนข้าม schema แบบ raw SQL ไม่มี 2-phase commit | 14.2.1 | data drift ถ้า transaction หนึ่งสำเร็จอีกอันไม่สำเร็จ |
| HTTP call แบบ fire-without-compensation (ไม่มี retry/timeout เอง) | 14.2.1, 14.2.2, 14.2.4, 14.2.6 | caller ไม่รู้ผลจริงถ้า network ล้มกลางทาง |
| Export สุดท้ายอาศัยมนุษย์ทำต่อ (email → นำเข้าด้วยมือ) | 14.2.4, 14.2.5 | human error ทำให้ HRMI ไม่ sync กับระบบต้นทาง โดยระบบต้นทางคิดว่า "เสร็จแล้ว" |
| Shared database ไม่มี API contract คุ้มกัน | 14.2.3 | schema change ฝั่งหนึ่งพังอีกฝั่งแบบไม่มี compile-time check |
| Cron sync ที่ไม่มี health-check/alert | 14.2.5 | silent drift ที่ตรวจจับได้แค่ตอนผลลัพธ์ผิดปกติ |
ดู debt ID ที่เกี่ยวข้องทั้งหมดใน 14.3 Scorecard