customization-layers.md 3.96 KB

Customization layers

Within Channel 1 (metadata-driven customization, see customization channels), xly supports a layered overlay model. A base form is defined system-wide; tenants and users overlay changes on top without modifying the base. The framework merges the layers at request time and returns one merged form to the SPA.

For the worked example of how this plays out for a custom field, see Slice 4. This page is the canonical overview.

The layers

flowchart TB
    classDef sys fill:#e8f0fe,stroke:#4285f4
    classDef tenant fill:#fef7e0,stroke:#fbbc04
    classDef user fill:#f3e8fd,stroke:#a142f4

    M["gdsconfigformmaster<br/>system default — the form<br/>(sSqlStr · sWhere · sOrder)"]:::sys
    P["gdsconfigformpersonalize<br/>per-tenant whole-form override<br/>(replaces sSqlStr / sWhere / sOrder)"]:::tenant
    S["gdsconfigformslave<br/>system default fields"]:::sys
    CS["gdsconfigformcustomslave<br/>per-tenant fields<br/>(add · hide · override by sName)"]:::tenant
    US["gdsconfigformuserslave<br/>per-user view tweaks<br/>(column order · hidden columns)"]:::user

    OUT["Merged form<br/>delivered to SPA"]

    M --> P
    P --> S
    S --> CS
    CS --> US
    US --> OUT

    M -. "always loaded" .-> OUT
    P -. "loaded if tenant has overlay" .-> OUT
    CS -. "loaded if tenant has overlay" .-> OUT
    US -. "loaded if user has prefs" .-> OUT

Read the chain top-to-bottom: system → tenant → user. Each layer is keyed by sParentId linking up to the layer above. None of the links are FK-enforced — see no-FK reality.

What each layer answers

Layer Scope Answer to
gdsconfigformmaster system-wide "What does this form look like by default?"
gdsconfigformpersonalize per-tenant "Does this tenant want a different SQL/where/order?"
gdsconfigformslave system-wide "What fields does the form have by default?"
gdsconfigformcustomslave per-tenant "Does this tenant want extra fields, or a hidden field, or a relabelled field?"
gdsconfigformuserslave per-user "Has this user reordered or hidden columns in their grid?"

How the merge happens

The framework reads each layer in order and merges by sName (the field name). For a custom slave row with the same sName as a base slave: override. For a new sName: append. For a base slave with no corresponding custom row: pass through unchanged. Entry point is BusinessBaseServiceImpl.getModelBysId (line 181), which calls BaseServiceImpl.getModelConfigByModleId (line 55); the actual slave-+-customslave merge runs in BusinessGdsconfigformsServiceImpl.getGdsconfigformslaveShow (line 392), combining getFormSlaveData (line 87) and getFormCustomSlaveData (line 121), then optionally layering getUserFormSlaveData (line 156).

Two database views support the merge by joining the form-master with the relevant slave table:

  • gdsconfigformslavemasterview — base layer
  • gdsconfigformcustomslavemasterview — overlay layer

These views are read directly by the service code; the merge logic is in Java rather than SQL.

What the layers can't do

The overlay tables let a tenant add fields to the form, but they do not (and cannot) ALTER TABLE the underlying physical table. A custom field beyond what the base table provides has nothing to bind to on save unless one of the following is true:

  1. The base table is intentionally "wide" (has spare columns reserved for customization).
  2. A coordinated manual schema migration adds the column.
  3. The customization is purely view-related (the field is computed or displayed, not stored).

Case (2) is the typical engineer-led path. Case (1) explains some of the schema's mysteriously over-broad tables. When the customization needs structural change beyond what overlays support, Slice 5 (per-customer SQL overrides) is the next channel up.