# 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 allowed by gdsroute (URL whitelist) │ │ 2. User clicks a sidebar item or navigates within the SPA │ │ 3. SPA decides which module to load → calls /business/... │ └──────────────────────────────────────────────────────────────────────┘ │ │ GET /xlyEntry/business/getModelBysId/{moduleId} │ ?sModelsId={moduleId} ▼ ┌──────────────────────────────────────────────────────────────────────┐ │ xlyEntry — BusinessBaseController.getModelBysId() │ │ │ │ ┌──────────────────────────────────────────────────────────────┐ │ │ │ RequestAddParamUtil.addParams(params, userInfo) │ │ │ │ → sBrandsId, sSubsidiaryId, sUserId, sLanguage, … │ │ │ │ → tenant scope is now baked into every downstream query │ │ │ └──────────────────────────────────────────────────────────────┘ │ │ │ │ BusinessBaseService.getModelBysId(map) │ │ │ │ │ ├── load gdsmodule row (the module) │ │ ├── load gdsconfigformmaster row(s) │ │ │ (joined to module via sParentId) │ │ ├── load gdsconfigformslave rows │ │ │ (joined to form-master via sParentId) │ │ ├── merge gdsconfigformpersonalize (per tenant) │ │ ├── merge gdsconfigformcustomslave (per tenant) │ │ ├── load gdsjurisdiction (skipped for ADMIN) │ │ ├── load gdsformconst (form-level constants) │ │ ├── load sysbillnosettings (document-numbering) │ │ └── load sysreport rows linked to this 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 five-key composite `getModelBysId` returns one Java `Map` with these keys, in this order: | Key | Source | Used by the SPA for | |---|---|---| | `formData` | `gdsmodule` ⋈ `gdsconfigformmaster` ⋈ `gdsconfigformslave` (+ overlays) | The form layout itself — every field, control, label, validation rule | | `gdsformconst` | `gdsformconst` rows scoped by tenant + language | Form-level constants — labels, defaults, dropdown text | | `gdsjurisdiction` | `gdsjurisdiction` rows for the user's role | Per-button and per-data permissions | | `billnosetting` | `sysbillnosettings` row for this module | Document-numbering rules (work-order numbers, quotation numbers) | | `report` | `sysreport` rows linked to this form | 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](../slices/01-hello-world.md#4-user-edits-a-row-clicks-save). - **The "open existing row for edit" fetch.** Same endpoint as the grid load (`getBusinessDataByFormcustomId`) but with the row's `sId` in 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`, populated `sVersionFlowId`, deployed Activiti process), additional steps interleave. None of those tables are populated in this dev DB; see [Slice 7 (deferred)](../slices/07-workflow.md). - **Cache invalidation.** When BACK changes a metadata row, a JMS message invalidates cached copies on every running node — `ConsumerChangeGdsModuleThread` in `xlyErpJmsConsumer`. Outside the request flow but adjacent to it. ## Variations covered by other slices - [Slice 1](../slices/01-hello-world.md) — the canonical instance, with observed network traffic. - [Slice 2](../slices/02-multi-tenancy.md) — how the multi-tenant filter threads through every step. - [Slice 3](../slices/03-report.md) — view-backed instead of table-backed. - [Slice 4](../slices/04-custom-field.md) — the `gdsconfigformcustomslave` merge step. - [Slice 5](../slices/05-customer-sql-override.md) — 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.