request-lifecycle.md
12.5 KB
The metadata-driven request lifecycle
The diagram you'll come back to. Every metadata-driven page in xly follows this same flow; once you've internalised it, the rest of the framework is variations on a theme.
The flow
┌──────────────────────────────────────────────────────────────────────┐
│ Browser │
│ 1. Loads SPA shell at any URL (the server returns the same shell │
│ for every path; gdsroute is a *client-side* sidebar/deep-link │
│ whitelist, not a server-side 404 gate) │
│ 2. User clicks a sidebar item or navigates within the SPA │
│ 3. SPA decides which module to load → calls /business/... │
└──────────────────────────────────────────────────────────────────────┘
│
│ GET /xlyEntry/business/getModelBysId/{sModelsId}
│ ?sModelsId={sModelsId}
│ (the module's id appears in BOTH path and query —
│ the controller binds the path variable, but the
│ service reads sModelsId from the @RequestParam map,
│ so the SPA must include it in the query string too)
▼
┌──────────────────────────────────────────────────────────────────────┐
│ xlyEntry — BusinessBaseController.getModelBysId() │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ RequestAddParamUtil.addParams(params, userInfo) │ │
│ │ → sBrandsId, sSubsidiaryId, sUserId, sLanguage, … │ │
│ │ (16 keys total — see runtime.md) │ │
│ │ → tenant scope is now AVAILABLE for any downstream query │ │
│ │ that wants it. Framework-metadata reads filter by │ │
│ │ form-id only; per-tenant overlays + business data │ │
│ │ reads filter by sBrandsId/sSubsidiaryId. │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ BusinessBaseService.getModelBysId(map) │
│ │ │
│ ├── 1. formData │
│ │ └── gdsconfigformmaster (filtered by │
│ │ sParentId = sModelsId; gdsmodule itself │
│ │ is *not* SELECT-ed, only referenced by id) │
│ │ + LEFT JOIN gdsconfigformpersonalize │
│ │ (per-tenant overlay) │
│ │ + per-master gdsconfigformslave │
│ │ + per-master gdsconfigformcustomslave │
│ │ (per-tenant overlay) │
│ ├── 2. gdsformconst (filtered by sParentId only; │
│ │ NOT tenant-scoped; sLanguage selects which │
│ │ label column to return) │
│ ├── 3. sysjurisdiction (per-user/group grants joined │
│ │ to sftlogininfojurisdictiongroup + │
│ │ sisjurisdictionclassify; skipped for ADMIN. │
│ │ Returned under map key `gdsjurisdiction` — │
│ │ misleading name, the gdsjurisdiction table is │
│ │ the builder-side action catalogue, not what's │
│ │ read here) │
│ ├── 4. sysbillnosettings (per-tenant, per-form) │
│ └── 5. sysreport (per-tenant, per-form) │
└──────────────────────────────────────────────────────────────────────┘
│
│ Returns one composite map:
│ { formData, gdsformconst, gdsjurisdiction,
│ billnosetting, report }
▼
┌──────────────────────────────────────────────────────────────────────┐
│ Browser SPA │
│ Renders the form using formData; populates dropdowns from │
│ gdsformconst and per-control SQL fetched separately │
│ │
│ Calls /business/getBusinessDataByFormcustomId/{formId} │
│ with sModelsId={moduleId} to load the actual rows │
└──────────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────────┐
│ xlyEntry — BusinessBaseController.getBusinessDataByFormcustomId() │
│ │
│ Composes parameterised SQL from formMaster.sSqlStr / sWhere / │
│ sOrder, with sBrandsId / sSubsidiaryId injected; │
│ runs against the form's backing object (table | view | proc). │
└──────────────────────────────────────────────────────────────────────┘
│
▼
User sees the grid
The same flow as a sequence
The ASCII above shows the order of operations; the sequence diagram below shows who calls whom, which is what matters when you're tracing a real request through the runtime.
sequenceDiagram
autonumber
participant SPA as Browser SPA
participant CTRL as BusinessBaseController
participant SVC as BusinessBaseServiceImpl
participant FORMS as BusinessGdsconfigformsServiceImpl
participant DB as MySQL
participant REDIS as Redis (RedisCacheManager)
SPA->>CTRL: GET /business/getModelBysId/{sModelsId}<br/>?sModelsId=...&Authorization=<bearer>
Note over CTRL: AuthorizationInterceptor.preHandle<br/>resolves UserInfo from Redis<br/>RequestAddParamUtil.addParams (16 keys)
CTRL->>SVC: getModelBysId(map)
Note over SVC: getModelConfigByModleId (inherited from BaseServiceImpl)<br/>orchestrates the per-master form-master + slave loads
SVC->>FORMS: getFormmasterData / getGdsconfigformslaveShow<br/>(form-master + slaves + overlays)
REDIS-->>FORMS: cache hit?
FORMS->>DB: SELECT ... gdsconfigformmaster ⋈ personalize; per master row, gdsconfigformslave + gdsconfigformcustomslave
DB-->>FORMS: rows
FORMS-->>SVC: formData
SVC->>FORMS: getFormconstData (form-id only, NOT tenant-scoped)
FORMS->>DB: SELECT ... gdsformconst WHERE sParentId=...
DB-->>FORMS: rows
FORMS-->>SVC: gdsformconst
alt sUserType != ADMIN
SVC->>FORMS: getJurisdictionData (per-user grants)
FORMS->>DB: SELECT ... sysjurisdiction ⋈ sftlogininfojurisdictiongroup
DB-->>FORMS: rows
FORMS-->>SVC: gdsjurisdiction (map-key; source table is sysjurisdiction)
else ADMIN
Note over SVC: skip jurisdiction load
end
SVC->>FORMS: getBillnosettingData
FORMS->>DB: SELECT ... sysbillnosettings WHERE sFormId=... AND tenant
DB-->>FORMS: row
FORMS-->>SVC: billnosetting
SVC->>DB: SELECT ... sysreport WHERE sFormId=... AND tenant
DB-->>SVC: report rows
SVC-->>CTRL: composite Map (5 keys)
CTRL-->>SPA: AjaxResult{code:1, dataset:{...}}
SPA->>CTRL: POST /business/getBusinessDataByFormcustomId/{formId}<br/>?sModelsId=...
Note over CTRL,SVC: same RequestAddParamUtil pass<br/>then per-form sSqlStr / sWhere / sOrder
CTRL->>DB: parameterised SELECT against the form's backing table/view/proc
DB-->>CTRL: rows
CTRL-->>SPA: dataset
The two HTTP round-trips are visible at lines 1 and 22 in the diagram. Everything between is server-side work the SPA never sees.
The five-key composite
getModelBysId returns one Java Map with these keys, in this order:
| Key | Source | Used by the SPA for |
|---|---|---|
formData |
gdsconfigformmaster (filtered by sParentId = sModelsId) ⋈ gdsconfigformpersonalize (per-tenant overlay); per master row, gdsconfigformslave + gdsconfigformcustomslave overlays. gdsmodule is referenced only by id. |
The form layout itself — every field, control, label, validation rule |
gdsformconst |
gdsformconst rows filtered by sParentId only — NOT tenant-scoped; sLanguage selects which label column to return |
Form-level constants — labels, defaults, dropdown text |
gdsjurisdiction |
sysjurisdiction rows for the user (or for the user's group via sftlogininfojurisdictiongroup ⋈ sisjurisdictionclassify); skipped for ADMIN. The map-key name gdsjurisdiction is misleading — that table is the builder-side action catalogue, not what's read here. |
Per-button and per-data permissions |
billnosetting |
sysbillnosettings row for this module (per-tenant) |
Document-numbering rules (work-order numbers, quotation numbers) |
report |
sysreport rows linked to this form (per-tenant) |
Print templates (Excel via jxls, PDF via iText) |
What's not in this lifecycle
A few things readers expect to find here but don't:
-
The "save" path. Save is its own endpoint
(
POST /business/addUpdateDelBusinessData), bundling add+update+delete in one request. See Slice 1. -
The "open existing row for edit" fetch. Same endpoint as the grid
load (
getBusinessDataByFormcustomId) but with the row'ssIdin the body — the handler branches on whether the body asks for one row or many. -
Workflow steps. When a module has an active approval workflow
(
bCheck = 1,gdsmoduleflowconfigured, deployed Activiti process, andConstantUtils.bCheckflowCheck = true), additional steps interleave. None of those tables are populated in this dev DB; see Slice 7 (deferred). -
Cache invalidation. When BACK changes a metadata row, the save path
synchronously calls
BusinessCleanRedisData/CleanRedisServiceImpl, which evicts Spring cache regions from shared Redis. The JMSConsumerChangeGdsModuleThreadpath is a separate base-data merge channel, not cache invalidation.
Variations covered by other slices
- Slice 1 — the canonical instance, with observed network traffic.
- Slice 2 — how the multi-tenant filter threads through every step.
- Slice 3 — view-backed instead of table-backed.
-
Slice 4 — the
gdsconfigformcustomslavemerge step. - Slice 5 — when a stored procedure body has been replaced for a specific tenant.
Read this page once and keep it open
If a future chapter uses a term you don't recognise, it almost certainly refers to one of the boxes in the diagram above. Come back here.