Skip to content

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-00docs/volume-16) · docs/audits/docs-verify-2026-07-11-delta.md · docs/volume-17-appendices/08-changelog-errata.md

Known stale claims ในไฟล์นี้: NestJS เป็น live END_POINT_CENTRALIZE target · 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 frontend apollo.js configs. 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).

mermaid
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_CENTRALIZE target = cu-central-api, not NestJS centralize-api. NestJS is a separate REST service (caller from vtrc-api not confirmed in workspace). cu-central :9000 below = Dockerfile EXPOSE, not listen.

Key context facts (evidence):

  • Both frontends talk to vtrc-api only — never directly to centralize-api or cu-central-api. The API is a hard gateway. (vtrc-web/src/config/apollo.js:17-34checkLinkURL() branches on window.location.href.)
  • vtrc-api exposes two Sequelize connections: db (schema vtrc) and db2 (schema vtrc-centralize). See vtrc-api/api/src/connector.js:8-48.
  • centralize-api opens two MSSQL connections (center + nbc) on the same host but different databases — centralize-api/src/app.module.ts:23-60.
  • cu-central-api opens db + dbHr and binds to LDAP — cu-central-api/main-api/src/index.js:8,98-117.
  • END_POINT_CENTRALIZEcu-central-api (GraphQL), not NestJS — see vtrc-api/api/.env.example and Volume 16 errata.

2.2 Container View — C4 Level 2

mermaid
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 data

Architecture 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.yml

Architecture 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.js

Architecture 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.config

Architecture 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

ZoneRepo / ModulePatternStatusModernization Target
🟢 Modernizedcentralize-api (entire repo)NestJS modules + DI + DTOs + JWT guard + Swagger + global pipes/filtersReference implKeep as the template; lift to Go (Hex/DDD) last
🟢 Modernizedvtrc-api/centralize/ bridgeForwarding controllers + GraphQL queries/mutationsActive adapterBecomes a Go anti-corruption layer (ACL)
🟡 Hybridvtrc-api/lib/{controllers,repositories}Two-layer (controller→repository)InconsistentHexagonal: use-case → port → adapter
🔴 Legacyvtrc-api/routes.js (REST)Procedural Express handlers + execSync(qpdf)HazardousMove PDF/Excel to a dedicated document-renderer microservice
🔴 Legacyvtrc-api/directives/auth.jsApollo v2 SchemaDirectiveVisitorDead-end APIMove to graphql-shield (interim) → Go middleware
🔴 Legacyvtrc-api/crons.jsnode-schedule in-processNo HAReplace with BullMQ + Redis → Go asynq/robfig/cron
🔴 Legacycu-central-api (entire repo)Mirror of vtrc-api on Node 12 + LDAPCritical-EOLDecompose into identity-service (Go) + hr-data-service (Go)
🔴 Legacyvtrc-web (entire repo)React 16 + WC4 + Apollo v2 + AntD v3Critical-EOLNuxt 3 + Feature-Sliced Design
🔴 Legacyvtrc-rc-backoffice (entire repo)React 16 + CRA + Apollo v2 + 4 UI libs + 16 axiosCritical-EOLNuxt 3 + FSD, one design system
🟡 Hybridcentralize-api/libs/log/I3GatewayLoggerCustom NestJS loggerActiveAlready 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.

mermaid
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:#cb4335

Deviations from Clean / Hexagonal (evidence-based)

#DeviationEvidenceHex/DDD Fix
D1Two entry surfaces (GraphQL + REST) sharing controllersvtrc-api/api/src/index.js:27,95 mounts Apollo + routes(app)Define driving adapters (GraphQL, REST, gRPC) that call the same usecase ports
D2Schema-directive auth tied to Apollo v2 APIdirectives/auth.js:1-10 uses SchemaDirectiveVisitor (removed in Apollo v3+)Move to middleware / interceptor pattern; auth = port adapter
D3Domain logic leaking into the transport layerroutes.js calls execSync('qpdf --encrypt ${password}...') inlinePDF/encryption = outbound adapter; never inline in transport
D4Distributed monolith seam (centralize/ bridge)vtrc-api/api/src/centralize/controllers/*.js proxies every HR callDefine an HRDataService port; centralize-api is one adapter implementation
D5Schema-first but model-coupled resolversmodels/ returns Sequelize instances directly to resolversIntroduce domain entities (pure TS structs) + mappers at repository boundary
D6No bounded-context isolation — 43 typeDefs in one schematypeDefs/index.js:45-88 aggregates 43 modulesSplit into bounded contexts (Identity, Payroll, Leave, Welfare, Fund, TimeAttendance) each its own Go module / Nuxt layer
D7Frontend folder-by-type, not folder-by-featurevtrc-web/src/{pages,components,graphql,config}Feature-Sliced Design: entities/, features/, widgets/, pages/, shared/ per bounded context
D8One 4.5K-LOC GraphQL operations filevtrc-web/src/graphql/queries.js (4,493 LOC)Co-locate operations with their feature; one .graphql/gql.ts per feature
D916+ axios instances in backofficevtrc-rc-backoffice/src/config/axios-*.js (16 files)One typed API client per bounded context; Nuxt 3 $fetch + useFetch composable
D10Role-based route duplication (4 LeaveStudies variants)pages/LeaveStudies{,Admin,Payments,PaymentsAdmin,Scholar,ScholarAdmin}Single feature with RBAC-driven UI variants
D11synchronize: false in TypeORM but syncModelToTable() in Sequelizecentralize-api/src/app.module.ts:33,52 vs vtrc-api/api/src/lib/model.jsAdopt explicit migrations (goose / atlas / prisma-style) in all services
D12strictNullChecks: false in centralize-apicentralize-api/tsconfig.json:15Strict TS is the floor; Go is strict by default
D13synchronize: false but logging: ['query'] in productioncentralize-api/src/app.module.ts:34,53Production query-logging is a perf/PII risk; route to debug sink only

2.6 Cross-Service Contract Map

mermaid
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 dbHr

Contract details (evidence)

EdgeProtocolAuthEvidence
Browser → vtrc-api GraphQLHTTPS POST application/jsonapiKey: <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 RESTHTTPS GET (PDF/Excel/file)URL path includes :device/:timestamp/:hash (1-hour validity)vtrc-api/api/src/routes.js:51-99
vtrc-apicu-central-apiGraphQL via END_POINT_CENTRALIZE (not NestJS)API_CENTRALIZE_KEYvtrc-api/api/.env.example; src/centralize/{controllers,graphql}/
centralize-api exposedREST under /api, Swagger at /docs (separate service; caller unconfirmed)NestJS JWT guard; @Public() for unauthenticatedcentralize-api/src/main.ts:21-38
cu-central-api exposedApollo GraphQL (Dockerfile EXPOSE 9000 ≠ listen; see CODE_DEFAULT/CLIENT_TARGET)apiKey + JWT; AD bind for logincu-central-api/main-api/src/index.js:32-84
vtrc-api → MariaDBSequelize (MariaDB dialect)env DB creds; pool max=100vtrc-api/api/src/connector.js:8-27
centralize-api → MSSQLTypeORM mssql (tedious)env creds; trustServerCertificate: truecentralize-api/src/app.module.ts:36-38

2.7 Deployment Topology

mermaid
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 --> MARIA

Deployment facts (evidence — vtrc-api/docker-compose.yml):

  • 4 vtrc-api replicas (APP_NO=1,2,3,5; note #4 is skipped) — manual horizontal scaling.
  • network_mode: host on the Nginx container — host-network = no port isolation.
  • phpMyAdmin exposed on :8093 with PMA_ABSOLUTE_URI: http://vtrcapi.redcross.or.th/db-management/DB admin surface publicly reachable.
  • vtrc-api containers mount ./api-storage and ./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

Dimensionvtrc-apivtrc-webvtrc-rc-backofficecentralize-apicu-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/an/a🔴 (no lockfile)🟡
HA / scaling story🟡 (manual replicas, no LB config)n/an/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

mermaid
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.-> BE

Mapping 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
identityuser, authentication, verification, role, roleAccess, consententities/employee, entities/session
payrollslip, taxDraft, bank, bankAccountentities/payslip, entities/tax
leavestudyLeave, studyLeaveRequest, studyEducation, delegate, childTuitionentities/leave
welfarewdHospital, providentFund, conicle, policyentities/welfare
provident-fundprovidentFund, fund (cu-central)entities/fund
time-attendancetimeAttendance, hrmi, statLogentities/attendance
organizationorganization, organizationGroup, career, masterData, referenceDataentities/org
communicationnotification, post, contactUs, sendEmail, createNotificationentities/notification
documentdocument, documentGroup, file, uploadExcel, downloadExcelentities/document
shared-kernelapplicationSetting, version, address, courseshared/

Phase 2 complete. Proceed to 03-data-and-api.md for ERDs and the top-15 API endpoints.