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:
- The base table is intentionally "wide" (has spare columns reserved for customization).
- A coordinated manual schema migration adds the column.
- 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.