Phase 2 — Architectural Anatomy & Module Map
⚠️ SUPERSEDED / ถูกแทนที่แล้ว — This audit snapshot is not authoritative.
อย่าใช้ตัดสินใจ operational โดยไม่ verify โค้ด / Do not use for operational decisions without verifying code.Authoritative docs: VitePress volumes (
docs/volume-00…docs/volume-16) ·docs/audits/docs-verify-2026-07-11-delta.md·docs/volume-17-appendices/08-changelog-errata.mdKnown stale claims ในไฟล์นี้: NestJS เป็น live
END_POINT_CENTRALIZEtarget · cu-central port 9000 เป็น listen (จริงคือEXPOSEเท่านั้น) ·dbHRMI_NBCแทนdbHRMI_Center_NBC· KBANK bank name errors อาจปรากฏ
i3 Gateway Knowledge Base · VTRC Platform Evidence basis: entry-point files (
index.js/main.ts),connector.js,app.module.ts,directives/auth.js,routes.js(head), directory enumerations across all 5 repos, and frontendapollo.jsconfigs. Modernization target framing: Go (Hexagonal / DDD) for backends · Nuxt 3 (Feature-Sliced Design) for frontends.
2.1 System Context — C4 Level 1 (Context)
The VTRC platform is the HR/welfare IT backbone of the Thai Red Cross Society (สภากาชาดแห่งประเทศไทย). It serves employees (salary slip, tax, leave, welfare, provident fund, time-attendance) and back-office administrators (HR operations, approvals, reports, master data).
C4Context
title VTRC Platform — System Context (Level 1)
Person(user, "Red Cross Employee", "End-user: views payslip, tax,\nleave, welfare, provident fund,\ntime-attendance")
Person(admin, "HR / Backoffice Admin", "Approves leave, manages\nmaster data, runs reports,\nmanages users")
System_Boundary(s1, "VTRC Platform") {
System(web, "vtrc-web", "End-user React 16 SPA\n(AntD v3, Apollo v2, WC4)")
System(bo, "vtrc-rc-backoffice", "Admin React 16 SPA\n(AntD v4, MUI v5, CRA,\nApollo v2, Recoil)")
System(api, "vtrc-api", "Core Node 12 GraphQL API\n(Apollo 2, Sequelize 6,\nMariaDB, Redis)")
System(cent, "centralize-api", "NestJS 9 HR Data Service\n(TypeORM, MSSQL, Swagger)")
System(cu, "cu-central-api", "Legacy CU Auth/HR Service\n(Node 12, Apollo 2, LDAP,\nMSSQL, Redis)")
}
System_Ext(mariadb, "MariaDB", "vtrc + vtrc-centralize schemas")
System_Ext(mssql, "MSSQL (HR)", "CU/HRMI raw HR data\n(Chula / Red Cross HR)")
System_Ext(redis, "Redis", "Cache, session, OTP")
System_Ext(ad, "Active Directory / LDAP", "Chula AD identity")
System_Ext(gcp, "GCP (Drive, FCM)", "File storage & push")
System_Ext(smtp, "SMTP / Email", "nodemailer relay")
System_Ext(sftp, "SFTP", "Inter-org file transfer")
Rel(user, web, "Uses (browser)")
Rel(admin, bo, "Uses (browser)")
Rel(web, api, "GraphQL + REST\n(apiKey + JWT Bearer)")
Rel(bo, api, "GraphQL + REST\n(apiKey + JWT Bearer)")
Rel(api, cu, "GraphQL via END_POINT_CENTRALIZE\n(live bridge — NOT NestJS)")
Rel(api, mariadb, "Sequelize (db + db2)")
Rel(api, redis, "ioredis-style v3")
Rel(api, gcp, "googleapis / firebase-admin")
Rel(api, smtp, "nodemailer")
Rel(cent, mssql, "TypeORM mssql/tedious\n(2 conns: center + nbc)\n[separate service; caller unconfirmed]")
Rel(cu, mssql, "tedious (db + dbHr)")
Rel(cu, ad, "ldapjs bind/search")
Rel(cu, redis, "redis v3")
Rel(cu, sftp, "ssh2-sftp-client")
Rel(cu, smtp, "nodemailer")
UpdateRelStyle(user, web, $offsetX="-30", $offsetY="-15")
UpdateRelStyle(web, api, $offsetX="-40", $offsetY="-10")
UpdateRelStyle(api, cu, $offsetX="0", $offsetY="0")⚠️ Errata (supersedes diagram if it still misleads): Live
END_POINT_CENTRALIZEtarget =cu-central-api, not NestJScentralize-api. NestJS is a separate REST service (caller from vtrc-api not confirmed in workspace). cu-central:9000below = Dockerfile EXPOSE, not listen.
Key context facts (evidence):
- Both frontends talk to
vtrc-apionly — never directly tocentralize-apiorcu-central-api. The API is a hard gateway. (vtrc-web/src/config/apollo.js:17-34—checkLinkURL()branches onwindow.location.href.) vtrc-apiexposes two Sequelize connections:db(schemavtrc) anddb2(schemavtrc-centralize). Seevtrc-api/api/src/connector.js:8-48.centralize-apiopens two MSSQL connections (center+nbc) on the same host but different databases —centralize-api/src/app.module.ts:23-60.cu-central-apiopensdb+dbHrand binds to LDAP —cu-central-api/main-api/src/index.js:8,98-117.END_POINT_CENTRALIZE→cu-central-api(GraphQL), not NestJS — seevtrc-api/api/.env.exampleand Volume 16 errata.
2.2 Container View — C4 Level 2
flowchart LR
subgraph CLIENTS["Browser Clients"]
direction TB
WEB["🖥️ vtrc-web<br/>React 16 · WC4 · AntD v3<br/>:3000"]
BO["🖥️ vtrc-rc-backoffice<br/>React 16 · CRA · AntD v4 + MUI v5<br/>:3000"]
end
subgraph GATEWAY["API Gateway Layer"]
direction TB
NGINX["🌐 Nginx reverse proxy<br/>(TLS termination, static hosting)"]
API1["vtrc-api #1 :8081<br/>Apollo GraphQL + Express REST"]
API2["vtrc-api #2 :8082<br/>(horizontal replica)"]
API3["vtrc-api #3 :8083<br/>(horizontal replica)"]
API5["vtrc-api #5 :8085<br/>(horizontal replica)"]
end
subgraph DOMAIN["Domain / Bounded Services"]
direction TB
CENT["centralize-api<br/>NestJS 9 · :3000/api<br/>Swagger /docs"]
CU["cu-central-api<br/>Node 12 · :9000<br/>Apollo + LDAP"]
end
subgraph DATA["Persistence & Infra"]
direction TB
MARIA[("MariaDB<br/>schema: vtrc<br/>schema: vtrc-centralize")]
MSSQL[("MSSQL<br/>CU / HRMI HR data<br/>(center + nbc DBs)")]
REDIS[("Redis<br/>cache/session/OTP")]
AD(("LDAP / AD<br/>(Chula directory)"))
GCP(("GCP<br/>Drive + FCM"))
SMTP(("SMTP"))
SFTP(("SFTP"))
end
WEB --> NGINX
BO --> NGINX
NGINX --> API1 & API2 & API3 & API5
API1 --> MARIA
API1 --> REDIS
API1 --> CENT
API1 --> GCP
API1 --> SMTP
CENT --> MSSQL
CU --> MSSQL
CU --> AD
CU --> REDIS
CU --> SFTP
CU --> SMTP
classDef legacy fill:#fde2e1,stroke:#c0392b,stroke-width:2px,color:#7b1a16
classDef modern fill:#d6eaf8,stroke:#2471a3,stroke-width:2px,color:#1b4f72
classDef data fill:#fcf3cf,stroke:#b7950b,stroke-width:2px,color:#7d6608
classDef client fill:#e8daef,stroke:#7d3c98,stroke-width:2px,color:#4a235a
class WEB,BO,API1,API2,API3,API5,CU client,legacy
class CENT modern
class MARIA,MSSQL,REDIS,AD,GCP,SMTP,SFTP dataArchitecture pattern read: Horizontal-scaling monolith + legacy satellite services + one modernized island.vtrc-api is a modular monolith deployed as 4 replicas (APP_NO=1,2,3,5) behind Nginx — vtrc-api/docker-compose.yml:47-100. There is no API gateway logic beyond Nginx TLS termination; load balancing is implicit via DNS/ports.
2.3 Repository Anatomy — Directory Maps
2.3.1 vtrc-api/api/src/ — Core API (the load-bearing monolith)
vtrc-api/api/src/
├── index.js ← Apollo + Express bootstrap (121 LOC)
├── connector.js ← Sequelize db + db2 (MariaDB)
├── config.js ⚠️ INLINED PROD DEVICE KEYS, JWT secrets, env
├── routes.js ← Express REST layer (1,301 LOC) — PDF/Excel/file/crypto/qpdf
├── crons.js ← node-schedule in-process jobs (72 LOC)
├── defaultData.js ← Seed data
├── directives/
│ └── auth.js ← @auth(accessRole, accessMenu) directive
├── typeDefs/ ← 43 hand-authored GraphQL schema files
│ └── index.js ← Aggregates 43 typeDef modules
├── resolvers/ ← 40 resolver modules (mirror of typeDefs)
│ └── index.js
├── models/
│ ├── core/ ← 25 Sequelize model dirs (vtrc schema)
│ ├── centralize/user/ ← cross-schema User model (vtrc-centralize)
│ └── index.js ← syncModelToTable()
├── lib/
│ ├── controllers/ ← 31 controller dirs (business logic)
│ ├── repositories/ ← 31 repository dirs (data access)
│ ├── auth/ encrypt/ firebase/ ← cross-cutting infra
│ ├── createslip/ createtax/ genSlip/ ← PDF generation
│ ├── createfailpayment/ ← fail-payment PDF
│ ├── logEvent/ wlog.js logging.js ← custom logging
│ ├── redis.js responErrors.js model.js date.js file.js
├── centralize/ ← 🔄 BRIDGE to centralize-api
│ ├── controllers/ ← 17 controllers proxying HR calls
│ ├── graphql/{queries,mutations,index}.js
├── keys/ ← Per-app device keys (web/mobile/backoffice)
├── *.ejs ← PDF templates (slip, tax, payment)
├── dist/ ⚠️ committed compiled output
├── fonts/ stamp.png pdfTemplate/Architecture verdict: Three layers (typeDefs → resolvers → lib/{controllers, repositories}) — almost clean, but the centralize/ bridge is a distributed monolith seam: every HR-domain call is tunneled through vtrc-api rather than called directly by frontends. The routes.js REST layer is a parallel RPC surface grafted onto the GraphQL API (PDF/Excel/qpdf/encrypt).
2.3.2 centralize-api/src/ — The Modernized Island (NestJS 9)
centralize-api/
├── src/
│ ├── main.ts ← NestJS bootstrap (Swagger, JwtAuthGuard, ValidationPipe)
│ ├── app.module.ts ← TypeOrmModule ×2 (center + nbc MSSQL conns)
│ ├── app.{controller,service}.ts
│ ├── modules/
│ │ ├── employee/ ← Module = controller + service
│ │ ├── employeeGroup/ ← Module + repository.ts (no controller) — pure data
│ │ ├── employeeLevel/ ← Module + repository.ts
│ │ ├── hrPaySlip/ ← Module + controller + service
│ │ ├── hrTimeTemp/ ← Module + controller + service
│ │ └── technicalPosition/← Module + repository.ts
│ ├── entities/ ← TypeORM entities (*.entity.ts + *.nbc.entity.ts)
│ └── dto/ ← class-validator DTOs
├── libs/
│ ├── common/ exception/ jwt/ log/ ← Cross-cutting (custom I3GatewayLogger!)
├── config/configuration.ts
├── docs/document.config.ts ← Swagger setup
├── nest-cli.json tsconfig.json Dockerfile docker-compose.ymlArchitecture verdict: This is the only repo that follows a real architectural pattern (NestJS modules + controllers + services + repositories + DTOs + entities + JWT guard + global exception filter + global validation pipe). It is the de-facto modernization reference implementation for the platform. libs/log/logs.middleware.ts defines I3GatewayLogger — a clear author signature of the i3 Gateway modernization initiative already in flight.
2.3.3 cu-central-api/main-api/src/ — Legacy CU Auth/HR Satellite
cu-central-api/main-api/src/
├── index.js ← Apollo + Express (118 LOC)
├── connector.js ← Sequelize db + dbHr (MSSQL)
├── config.js cron.js testScript.js
├── directives/auth.js ← @auth directive (parallel to vtrc-api)
├── typeDefs/ ← 30 typeDef files (incl. payrollSlip_removed.js)
├── resolvers/ ← 28 resolver files (incl. finanace.js typo)
├── models/
│ ├── core/ hrRawData/ hrmi/ hrmiHis/ hrmi_test/ masterData/
├── lib/ ← 50+ utility modules (auth, otp, sms, sso, mailer,
│ cache, his, transferFile, twoWay, userIdGenerator,
│ userIdsMapping, userVerification, ...)
└── routes.js (64 LOC) + email-templates/Architecture verdict: Mirror structure to vtrc-api but for the CU/HRMI HR domain. Contains the LDAP integration, OTP/SMS, two-way sync, SFTP transfer, and Sha512-crypt password verification. This is the legacy authentication + raw HR data spine that vtrc-api reaches into via the centralize/ bridge.
2.3.4 vtrc-web/src/ — End-User Frontend
vtrc-web/src/
├── index.js / index_bk.js ← bootstrap (bk = backup)
├── config/
│ ├── apollo.js ← Apollo Client v2 + token refresh + error link
│ ├── axios-{bo,i3,API}.js ← multiple axios instances
│ ├── datasets*.js ← form/option data
│ ├── routes.js + routes_bk*.js + routes_metting.js ← 5 route files, 3 are _bk
│ ├── refreshToken.js
│ └── custom-antd.css styles.css
├── graphql/
│ ├── queries.js ← 🚨 4,493 LOC single file
│ ├── mutations.js
│ └── queries_bk*.js mutations_bk*.js queries_metting.js queries_pentest.js querries_prd.js
├── pages/user/ ← all pages under a single user/ folder
├── containers/App/ ← top-level App shell
├── components/{element,user}/
├── assets/ utils/ serviceWorker.jsArchitecture verdict: Flat folder-by-type layout (pages vs components vs graphql vs config). All user pages collapsed under pages/user/. The graphql/queries.js 4.5K-line file is a monolith contract that defeats code-splitting and reviewer cognition. Heavy _bk (backup) and _pentest (pen-test) file accumulation signals absent git hygiene.
2.3.5 vtrc-rc-backoffice/src/ — Admin Frontend
vtrc-rc-backoffice/src/
├── index.js
├── config/
│ ├── apollo.js apollo-workforce.js ← TWO Apollo clients
│ ├── axios-{ai,benefit,benefit-i3,bo,common,i3,meeting,payments,pms,
│ │ recruitment,recruitment-upload,vtrc,workforce,workforce-upload}.js
│ │ ← 🚨 16+ axios instances
│ ├── routes.js + routes_bak.js + routes_bk-*.js (5 backup variants)
│ ├── datasets*.js refreshToken.js custom-antd.css styles.css
├── graphql/
│ ├── queries.js ← 🚨 4,108 LOC
│ ├── mutations.js ← 🚨 2,208 LOC
│ ├── WorkForce/ graphql-pingpong/
│ └── queries_bk*.js mutations_bk*.js
├── pages/ ← ~80 page folders (Attendance, Leave, Fund, Hospital*,
│ │ PMS, Recruitment, LeaveStudies, News, Organization, ...)
├── components/ ← ~50 components (Forms, Modals, Shared, SiderLayout)
├── recoil/states/ ← Recoil atoms
├── containers assets utils serviceWorker.js prepros.configArchitecture verdict: Same flat folder-by-type layout as vtrc-web, but with a staggering 16+ axios instances (one per subsystem — PMS, Recruitment, Meeting, WorkForce, Payments, Benefit, AI, etc.) — indicating the backoffice is effectively a federated portal glued into one CRA bundle. The presence of LeaveStudiesScholar, LeaveStudiesScholarAdmin, LeaveStudiesPayments, LeaveStudiesPaymentsAdmin shows role-based route duplication rather than RBAC-driven single views.
2.4 Module Category Matrix — Legacy vs Modernized Zones
| Zone | Repo / Module | Pattern | Status | Modernization Target |
|---|---|---|---|---|
| 🟢 Modernized | centralize-api (entire repo) | NestJS modules + DI + DTOs + JWT guard + Swagger + global pipes/filters | Reference impl | Keep as the template; lift to Go (Hex/DDD) last |
| 🟢 Modernized | vtrc-api/centralize/ bridge | Forwarding controllers + GraphQL queries/mutations | Active adapter | Becomes a Go anti-corruption layer (ACL) |
| 🟡 Hybrid | vtrc-api/lib/{controllers,repositories} | Two-layer (controller→repository) | Inconsistent | Hexagonal: use-case → port → adapter |
| 🔴 Legacy | vtrc-api/routes.js (REST) | Procedural Express handlers + execSync(qpdf) | Hazardous | Move PDF/Excel to a dedicated document-renderer microservice |
| 🔴 Legacy | vtrc-api/directives/auth.js | Apollo v2 SchemaDirectiveVisitor | Dead-end API | Move to graphql-shield (interim) → Go middleware |
| 🔴 Legacy | vtrc-api/crons.js | node-schedule in-process | No HA | Replace with BullMQ + Redis → Go asynq/robfig/cron |
| 🔴 Legacy | cu-central-api (entire repo) | Mirror of vtrc-api on Node 12 + LDAP | Critical-EOL | Decompose into identity-service (Go) + hr-data-service (Go) |
| 🔴 Legacy | vtrc-web (entire repo) | React 16 + WC4 + Apollo v2 + AntD v3 | Critical-EOL | Nuxt 3 + Feature-Sliced Design |
| 🔴 Legacy | vtrc-rc-backoffice (entire repo) | React 16 + CRA + Apollo v2 + 4 UI libs + 16 axios | Critical-EOL | Nuxt 3 + FSD, one design system |
| 🟡 Hybrid | centralize-api/libs/log/I3GatewayLogger | Custom NestJS logger | Active | Already aligned with i3 Gateway branding; port to Go zerolog/slog adapter |
2.5 Architectural Pattern & Deviations from Clean / Hexagonal
Current Pattern (observed)
Modular Monolith with Layered internals — but the layering is inconsistent across repos.
flowchart TD
subgraph IDEAL["Hexagonal / Clean (target)"]
direction LR
U1[Use Case / Port] --> A1[Adapter]
end
subgraph ACTUAL["VTRC Actual (vtrc-api)"]
direction TB
L1["GraphQL typeDefs<br/>(schema)"]
L2["Resolvers<br/>(thin orchestrators)"]
L3A["lib/controllers/<br/>(business logic)"]
L3B["lib/repositories/<br/>(data access)"]
L4["models/<br/>(Sequelize)"]
L5["centralize/<br/>(proxy to centralize-api)"]
L6["routes.js<br/>(parallel REST RPC)"]
L1 --> L2 --> L3A --> L3B --> L4
L2 -.-> L5
L6 -.bypasses GraphQL.-> L3A
L6 -.directly.-> L4
end
style IDEAL fill:#d5f5e3,stroke:#239b56
style ACTUAL fill:#fde2e1,stroke:#cb4335Deviations from Clean / Hexagonal (evidence-based)
| # | Deviation | Evidence | Hex/DDD Fix |
|---|---|---|---|
| D1 | Two entry surfaces (GraphQL + REST) sharing controllers | vtrc-api/api/src/index.js:27,95 mounts Apollo + routes(app) | Define driving adapters (GraphQL, REST, gRPC) that call the same usecase ports |
| D2 | Schema-directive auth tied to Apollo v2 API | directives/auth.js:1-10 uses SchemaDirectiveVisitor (removed in Apollo v3+) | Move to middleware / interceptor pattern; auth = port adapter |
| D3 | Domain logic leaking into the transport layer | routes.js calls execSync('qpdf --encrypt ${password}...') inline | PDF/encryption = outbound adapter; never inline in transport |
| D4 | Distributed monolith seam (centralize/ bridge) | vtrc-api/api/src/centralize/controllers/*.js proxies every HR call | Define an HRDataService port; centralize-api is one adapter implementation |
| D5 | Schema-first but model-coupled resolvers | models/ returns Sequelize instances directly to resolvers | Introduce domain entities (pure TS structs) + mappers at repository boundary |
| D6 | No bounded-context isolation — 43 typeDefs in one schema | typeDefs/index.js:45-88 aggregates 43 modules | Split into bounded contexts (Identity, Payroll, Leave, Welfare, Fund, TimeAttendance) each its own Go module / Nuxt layer |
| D7 | Frontend folder-by-type, not folder-by-feature | vtrc-web/src/{pages,components,graphql,config} | Feature-Sliced Design: entities/, features/, widgets/, pages/, shared/ per bounded context |
| D8 | One 4.5K-LOC GraphQL operations file | vtrc-web/src/graphql/queries.js (4,493 LOC) | Co-locate operations with their feature; one .graphql/gql.ts per feature |
| D9 | 16+ axios instances in backoffice | vtrc-rc-backoffice/src/config/axios-*.js (16 files) | One typed API client per bounded context; Nuxt 3 $fetch + useFetch composable |
| D10 | Role-based route duplication (4 LeaveStudies variants) | pages/LeaveStudies{,Admin,Payments,PaymentsAdmin,Scholar,ScholarAdmin} | Single feature with RBAC-driven UI variants |
| D11 | synchronize: false in TypeORM but syncModelToTable() in Sequelize | centralize-api/src/app.module.ts:33,52 vs vtrc-api/api/src/lib/model.js | Adopt explicit migrations (goose / atlas / prisma-style) in all services |
| D12 | strictNullChecks: false in centralize-api | centralize-api/tsconfig.json:15 | Strict TS is the floor; Go is strict by default |
| D13 | synchronize: false but logging: ['query'] in production | centralize-api/src/app.module.ts:34,53 | Production query-logging is a perf/PII risk; route to debug sink only |
2.6 Cross-Service Contract Map
sequenceDiagram
autonumber
participant U as Employee Browser
participant W as vtrc-web
participant N as Nginx
participant A as vtrc-api (Apollo+Express)
participant C as centralize-api (NestJS)
participant CU as cu-central-api (Apollo)
participant M as MariaDB
participant R as Redis
participant MS as MSSQL
U->>W: open payslip page
W->>N: POST /api/graphql (apiKey, JWT)
N->>A: forward (round-robin to #1..#5)
A->>A: context(): verify apiKey + JWT (getPayLoad)
A->>A: @auth directive: checkSession + checkMenuAuth
A->>R: getCacheData(payslipKey)
alt cache miss
A->>C: GET /api/hrpayslip (or via centralize/graphql)
C->>C: JwtAuthGuard + ValidationPipe
C->>MS: TypeORM query (center or nbc DB)
MS-->>C: payslip rows
C-->>A: JSON payload
A->>R: setCacheData(payload, TTL)
end
A-->>N: GraphQL response
N-->>W: JSON
W-->>U: render payslip
Note over A,CU: For auth (login, OTP, verify):<br/>A may reach CU via centralize/ bridge<br/>CU binds to LDAP/AD and MSSQL dbHrContract details (evidence)
| Edge | Protocol | Auth | Evidence |
|---|---|---|---|
Browser → vtrc-api GraphQL | HTTPS POST application/json | apiKey: <base64 device-key> + Authorization: Bearer <jwt> | vtrc-api/api/src/index.js:33-44; vtrc-web/src/config/apollo.js:40-67 |
Browser → vtrc-api REST | HTTPS GET (PDF/Excel/file) | URL path includes :device/:timestamp/:hash (1-hour validity) | vtrc-api/api/src/routes.js:51-99 |
vtrc-api → cu-central-api | GraphQL via END_POINT_CENTRALIZE (not NestJS) | API_CENTRALIZE_KEY | vtrc-api/api/.env.example; src/centralize/{controllers,graphql}/ |
centralize-api exposed | REST under /api, Swagger at /docs (separate service; caller unconfirmed) | NestJS JWT guard; @Public() for unauthenticated | centralize-api/src/main.ts:21-38 |
cu-central-api exposed | Apollo GraphQL (Dockerfile EXPOSE 9000 ≠ listen; see CODE_DEFAULT/CLIENT_TARGET) | apiKey + JWT; AD bind for login | cu-central-api/main-api/src/index.js:32-84 |
vtrc-api → MariaDB | Sequelize (MariaDB dialect) | env DB creds; pool max=100 | vtrc-api/api/src/connector.js:8-27 |
centralize-api → MSSQL | TypeORM mssql (tedious) | env creds; trustServerCertificate: true | centralize-api/src/app.module.ts:36-38 |
2.7 Deployment Topology
flowchart TB
subgraph INTERNET["Internet"]
USER[End Users]
end
subgraph PRODHOST["Production Host (single VM / docker-compose)"]
direction TB
NGINX["Nginx :80/:443<br/>vtrc.redcross.or.th<br/>vtrcbackoffice.redcross.or.th<br/>uat-* variants"]
subgraph APIS["vtrc-api replicas (docker-compose)"]
direction LR
A1[vtrc-api #1 :8081]
A2[vtrc-api #2 :8082]
A3[vtrc-api #3 :8083]
A5[vtrc-api #5 :8085]
end
PMA["phpMyAdmin :8093<br/>PMA_HOST=vtrc-database<br/>⚠️ exposed in compose"]
REDIS[(Redis :6379)]
MARIA[(MariaDB<br/>vtrc + vtrc-centralize)]
end
subgraph SATELLITE["Satellite services (separate hosts)"]
CENT[centralize-api :3000]
CU[cu-central-api :9000]
MSSQL[(MSSQL HR)]
AD((LDAP / AD))
end
USER --> NGINX
NGINX --> A1 & A2 & A3 & A5
NGINX -.serves static.-> WEBSTATIC["vtrc-web build (nginx html)"]
A1 & A2 & A3 & A5 --> MARIA
A1 & A2 & A3 & A5 --> REDIS
A1 & A2 & A3 & A5 --> CENT
CENT --> MSSQL
CU --> MSSQL
CU --> AD
PMA --> MARIADeployment facts (evidence — vtrc-api/docker-compose.yml):
- 4
vtrc-apireplicas (APP_NO=1,2,3,5; note #4 is skipped) — manual horizontal scaling. network_mode: hoston the Nginx container — host-network = no port isolation.- phpMyAdmin exposed on
:8093withPMA_ABSOLUTE_URI: http://vtrcapi.redcross.or.th/db-management/— DB admin surface publicly reachable. vtrc-apicontainers mount./api-storageand./api-logs— local-disk stateful storage (PDFs, logs).- No healthchecks, no resource limits, no restart-dep ordering beyond
restart: always.
2.8 Architectural Health Scorecard
| Dimension | vtrc-api | vtrc-web | vtrc-rc-backoffice | centralize-api | cu-central-api |
|---|---|---|---|---|---|
| Layering clarity | 🟡 (3-layer + REST bypass) | 🔴 (folder-by-type) | 🔴 (folder-by-type + 16 axios) | 🟢 (NestJS modules) | 🟡 (mirror of vtrc-api) |
| Bounded-context isolation | 🔴 (1 schema, 43 modules) | 🔴 (1 queries.js) | 🔴 (1 queries.js) | 🟢 (per-module entity) | 🔴 (1 schema, 30 modules) |
| Dependency direction | 🟡 (mostly inward) | 🟡 | 🟡 | 🟢 (DI + reflect-metadata) | 🟡 |
| Transport coupling | 🔴 (GraphQL + REST + execSync) | 🟢 (GraphQL only) | 🔴 (GraphQL + 16 axios) | 🟢 (REST + Swagger) | 🟢 (GraphQL only) |
| Auth boundary clarity | 🟡 (directive + bypass) | 🟢 | 🟢 | 🟢 (JwtAuthGuard + @Public) | 🟡 (directive) |
| Reproducible deploy | 🟡 (docker but no engines) | n/a | n/a | 🔴 (no lockfile) | 🟡 |
| HA / scaling story | 🟡 (manual replicas, no LB config) | n/a | n/a | 🔴 (single instance) | 🔴 (single instance) |
| Testability | 🔴 (no tests, execSync inline) | 🔴 (no tests) | 🔴 (no tests) | 🟢 (jest + supertest scaffolding) | 🟡 (mocha/chai present) |
2.9 i3 Gateway Modernization Target — Hexagonal + DDD + FSD
flowchart LR
subgraph FE["Frontend (Nuxt 3 · Feature-Sliced Design)"]
direction TB
FAPP[app/ global shell]
FPROC[processes/ routing strategies]
FPAGES[pages/ route composition]
FWIDG[widgets/ composite blocks]
FEAT[features/ user actions]
FENT[entities/ domain slices: employee, payslip, leave, fund, attendance]
FSHARED[shared/ ui-kit, api, lib, config]
FAPP --> FPROC --> FPAGES --> FWIDG --> FEAT --> FENT --> FSHARED
end
subgraph BE["Backend (Go · Hexagonal / DDD)"]
direction TB
CMD[cmd/api main]
INTERNAL[internal/]
subgraph DOMAINS["bounded contexts"]
ID[identity]
PAY[payroll]
LEAVE[leave]
WELF[welfare]
FUND[provident-fund]
TA[time-attendance]
DOC[document-renderer]
end
INTERNAL --> DOMAINS
DOMAINS --> PORTS[(ports/ use-case interfaces)]
PORTS --> ADAPTERS[(adapters/ db, http, redis, sftp, pdf)]
end
FE -.HTTPS GraphQL/REST.-> BEMapping from current modules to target bounded contexts (extracted from the 43 vtrc-api typeDefs):
| Bounded Context (Go domain) | Source modules (vtrc-api) | Frontend FSD entity |
|---|---|---|
identity | user, authentication, verification, role, roleAccess, consent | entities/employee, entities/session |
payroll | slip, taxDraft, bank, bankAccount | entities/payslip, entities/tax |
leave | studyLeave, studyLeaveRequest, studyEducation, delegate, childTuition | entities/leave |
welfare | wdHospital, providentFund, conicle, policy | entities/welfare |
provident-fund | providentFund, fund (cu-central) | entities/fund |
time-attendance | timeAttendance, hrmi, statLog | entities/attendance |
organization | organization, organizationGroup, career, masterData, referenceData | entities/org |
communication | notification, post, contactUs, sendEmail, createNotification | entities/notification |
document | document, documentGroup, file, uploadExcel, downloadExcel | entities/document |
shared-kernel | applicationSetting, version, address, course | shared/ |
Phase 2 complete. Proceed to 03-data-and-api.md for ERDs and the top-15 API endpoints.