request-lifecycle.md 9.88 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 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 sftlogininfojurisdictiongroupsisjurisdictionclassify); 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'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).
  • 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 — 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 gdsconfigformcustomslave merge 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.