Skip to content

9.9 · Identity across databases

ปัญหาใหญ่อันดับต้นของ VTRC data layer — "User" มีหลายนิยาม ข้าม 3 services และ 3 databases ไม่มี canonical employee ID เดียวที่ใช้ได้ทุกที่

บทนี้ map ความสัมพันธ์ของ identity ทั้งหมด + อธิบายว่าทำไม join ข้าม service ถึงยาก


3 model สำหรับแนวคิด "User"

#ServiceDatabaseTablePKTypeFormat
1vtrc-apivtrc-centralize (MariaDB)UsersuserIdUUID550e8400-e29b-41d4-a716-446655440000
2centralize-apidbHRMI_Center (MSSQL)auth_useruserIdUUIDsame format
3centralize-apidbHRMI_Center (MSSQL)emEmplGrup_reStructure และ siblingsEmplGrupID / EmpIDSTRING(36)string UUID-as-string
4cu-central-apiMSSQLEmployeeEmpID (PK) + EmpCodeSTRING(36) / STRING(50)EmpID = stable PK; EmpCode = business code

(ไม่ใช่ 3 จริง — มีทั้ง EmpID stable PK และ EmpCode business key)


Identity graph

   ┌─────────────────────────────────────────────────────────────┐
   │ vtrc-centralize.Users (MariaDB)                              │
   │ ├── userId (UUID)                                            │
   │ ├── personId (UUID — secondary identity)                     │
   │ ├── param (STRING — cache ของ EmpCode)                       │
   │ ├── refId (TEXT — link ไป HRMI EmpCode)                      │
   │ └── sourceDb (STRING — HRMI SourceDB เช่น `dbHRMI_*_rep`; ไม่ใช่ `'vtrc'\|'nbc'`) │
   └────────────────┬───────────────────────────────────────────┘

                    │ refId === EmpCode (string match)

   ┌─────────────────────────────────────────────────────────────┐
   │ dbHRMI_Center (MSSQL) — owned by HRMI/Chula                  │
   │                                                              │
   │ emEmplGrup_reStructure                                       │
   │ ├── EmplGrupID (PK, STRING 36)                               │
   │ ├── EmplGrupCode (STRING 50 — business code)                 │
   │ ├── EmplGrupName (STRING 50)                                 │
   │ ├── IsDeleted / IsInactive (BOOLEAN)                         │
   │ └── SourceDB (STRING 50)                                     │
   │                                                              │
   │ emEmplLevel_reStructure                                      │
   │ ├── EmplLevelID (PK, STRING 36)                              │
   │ └── EmplLevelCode / EmplLevelName                            │
   │                                                              │
   │ (Employee + slip + time attendance tables)                   │
   │                                                              │
   └─────────────────────────────────────────────────────────────┘

                    │ userId === auth_user.userId (UUID match)

   ┌────────────────┴───────────────────────────────────────────┐
   │ dbHRMI_Center.auth_user (MSSQL) — owned by centralize-api    │
   │ ├── userId (PK, UUID)                                        │
   │ ├── userName (STRING — login name)                           │
   │ ├── password (STRING — hashed)                               │
   │ ├── email / firstName / lastName                             │
   │ ├── userLevel (UUID — link ไป EmplLevel)                      │
   │ └── forgetPassword (UUID — token)                            │
   └─────────────────────────────────────────────────────────────┘

Mapping — ทำไมถึงยาก

vtrc-api → HRMI

vtrc-centralize.Users.refId (TEXT) เก็บค่า EmpCode (string) ของ HRMI

45:48:vtrc-api/api/src/models/centralize/user/user.js
    refId: {
      type: Sequelize.TEXT,
      allowNull: true
    },

ใช้ตอน query "user คนนี้มีเงินเดือนเท่าไหร่":

1. vtrc-api อ่าน User.refId → ได้ EmpCode
2. vtrc-api เรียก cu-central-api (ผ่าน centralize bridge) → "fetch payslip by EmpCode"
3. cu-central-api query HRMI WHERE EmpCode = ?
4. ได้ slip data กลับมา

ปัญหา:

  • refId เป็น TEXT ไม่ enforce format → อาจจะมีค่าผิดได้
  • ถ้า EmpCode เปลี่ยน (พนักงานย้ายสังกัด) refId ค้างที่ค่าเดิม → slip query ไม่เจอ
  • ถ้า EmpCode ซ้ำใน HRMI (ข้อมูลผิด) → vtrc จะ merge คนละคนเป็นคนเดียวโดยไม่ได้ตั้งใจ

vtrc-api → cu-central-api auth

vtrc-centralize.Users.userId (UUID) อาจจะตรงกับ identity ฝั่ง cu-central — แต่ไม่ guarantee

ปัญหา — UUID generate ฝั่ง vtrc ตอน register, ฝั่ง HR/cu-central มี identity ของตัวเอง ไม่มี mechanism ที่ sync ทั้งสอง → UUID อาจจะไม่ตรงกัน

NestJS centralize-api ก็มี auth_user entity แต่เป็น dead code และ caller จาก vtrc-api ยังไม่ยืนยัน — ดู Volume 5.9

HRMI UUID เป็น string ไม่ใช่ native UUID

8:9:centralize-api/src/entities/employeeGroup.entity.ts
  @PrimaryColumn({ name: 'EmplGrupID', length: 36 })
  emplGrupID: string;

TypeORM declare เป็น string length 36 — เก็บ UUID ในรูปแบบ string ไม่ใช่ native UUID type ของ MSSQL (uniqueidentifier)

ผลกระทบ:

  • index size ใหญ่กว่า native UUID
  • performance ตอน join ช้ากว่า
  • ถ้าย้ายไปใช้ framework อื่น (เช่น Go + uuid.UUID) ต้อง cast string → UUID

sourceDb field — multi-tenant flag

49:52:vtrc-api/api/src/models/centralize/user/user.js
    sourceDb: {
      type: Sequelize.STRING,
      allowNull: true,
    }

sourceDb เป็น STRING ว่างได้ — ไม่ใช่ enum ปิด 'vtrc' | 'nbc'

ค่าจริงที่โค้ดอ้างเป็น ชื่อ SourceDB ของ HRMI เช่น:

  • dbHRMI_CU_SC_rep — ดูการเปรียบเทียบใน cu-central-api/main-api/src/resolvers/payrollSlip.js (workingProfile.sourceDb === 'dbHRMI_CU_SC_rep')
  • dbHRMI_RC_B — ตัวอย่างที่ frontend ส่งในบางหน้า (เช่น recruit dashboard)

ค่านี้มาจากโปรไฟล์ HR (currentProfile มักเป็นรูปแบบ empId:sourceDb) แล้วถูกเก็บลง Users.sourceDb / ใช้ตอน query ข้ามระบบ

41:42:centralize-api/src/entities/employeeGroup.entity.ts
  @Column({ name: 'SourceDB', length: 50, nullable: true })
  sourceDB?: string;

ฝั่ง HRMI / TypeORM ก็มีฟิลด์ SourceDB เป็น string ความยาวจำกัด — sync กันในเชิงความหมาย (tenant/source ของระเบียน) ไม่ใช่รหัสสั้น vtrc/nbc

ผลกระทบ — query HR data ต้องส่ง sourceDb ที่ถูกต้องตามเรคอร์ดพนักงาน (เช่น dbHRMI_*_rep) ไม่ใช่สมมติแค่สองค่า vtrc/nbc

3 PK strategies

Strategyใช้ที่ไหนข้อดีข้อเสีย
Native UUID (Sequelize.UUID)vtrc-api MariaDBindex เล็ก, fast compareไม่ human-readable
UUID-as-string (STRING 36)centralize-api MSSQLportable ข้าม engineindex ใหญ่, slower
Business code (EmpCode string)HRMI MSSQLhuman-readableเปลี่ยนได้ตาม org policy

ผลกระทบตอน query

"ดูข้อมูล user คนนี้ทั้งหมด"

ต้อง join ข้าม 3 database ที่ไม่มี cross-DB relation:

Step 1: vtrc-centralize.Users → refId (EmpCode)
Step 2: cu-central-api (live bridge) → fetch Employee by EmpCode
Step 3: cu-central-api → fetch PaySlip by EmpCode
Step 4: cu-central-api → fetch TimeAttendance by EmpCode
Step 5: vtrc.LeaveDocument → WHERE empCode = ?
Step 6: vtrc.WDHospital → WHERE empCode = ?

หมายเหตุ: NestJS centralize-api มี REST ที่อ่าน HRMI ได้เช่นกัน แต่ caller จาก vtrc-api ใน workspace นี้ยังไม่ยืนยัน — live path คือ END_POINT_CENTRALIZE → cu-central-api

ทุก step เป็น network call → latency สะสม

"audit ว่า user คนนี้เคยทำอะไรบ้าง"

เพราะทุก domain เก็บ empCode แยก → audit log กระจายอยู่ 6+ table

ถ้าพนักงานย้ายกอง EmpCode เปลี่ยน → historical data ยังอยู่ใน code เดิม แต่ user ปัจจุบันไม่เห็น

"ลบ user แล้ว data ตามหรือเปล่า"

ไม่มี cascade — ถ้าลบ Users row ที่ vtrc-centralize:

  • Session.userId ค้าง (orphan)
  • LeaveDocument.empCode ยังอยู่ (แต่ user ไม่มา login ได้แล้ว)
  • HRMI ไม่กระทบ (VTRC ไม่เคยลบ HRMI data)

canonical employee ID — ไม่มี

VTRC ไม่มี canonical employee ID ที่ stable ข้าม service — ทุก reference เป็น "best effort mapping":

FieldStable?เปลี่ยนได้ไหม
vtrc.User.userId (UUID)stableไม่เปลี่ยน
vtrc-centralize.Users.refId (EmpCode)semi-stableเปลี่ยนตอนย้ายกอง
HRMI EmpID (STRING(36) PK)stableไม่เปลี่ยน
HRMI EmpCode (business STRING(50))unstableเปลี่ยนตาม org policy

modernization target

สิ่งที่ควรจะเป็น:

ปัจจุบันควรจะเป็น
3 PK strategies1 canonical employee ID (แนะนำ employeeId UUID ที่ stable)
refId TEXT mappingcanonical ID relation ที่ enforce
EmpCode ใน domain tablesemployeeId FK ที่ cascade
ไม่มี audit trail ข้าม domainevent sourcing / outbox pattern

รายละเอียดอยู่ใน บท 8.12 และ Volume 15 (Modernization)


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

ถ้าได้ "user not found" ตอน query HRMI data

  • เช็ก vtrc.User.refId — ถ้า null/empty → map ไม่สำเร็จ
  • เช็ก sourceDb — ใช้ค่า SourceDB จริงจากโปรไฟล์ (เช่น dbHRMI_CU_SC_rep) เมื่อ query/branch ตามต้นทาง HRMI; NestJS connection ชื่อ nbc ชี้ dbHRMI_Center_NBC เป็นคนละชั้นกับสตริง sourceDb บน Users

ถ้า slip ไม่ตรงกับ user

  • EmpCode อาจจะเปลี่ยน → query HRMI ด้วย EmpID (stable) แทน
  • refId อาจจะเก่า → manual sync

อย่า trust UUID match ข้าม vtrc ↔ centralize auth_user

UUID อาจจะไม่ตรงกัน → ใช้ flow verify ผ่าน login แทน

ถ้าจะ implement "ลบ user"

ต้อง manual cascade — Session, LeaveDocument, WDHospital, Notification, etc. ที่ reference user ต้อง update/cleanup ทีละอัน


ไป 8.10 Indexing, transactions, performance