# Modules, forms, virtual tables The three core nouns of xly's metadata model. A reader who internalises these can navigate the rest of the framework. The vocabulary is not always consistent in the codebase — same concept, different names in different files — so part of this page is reconciling the synonyms. ## Module The unit of "a configurable thing in the system". - **One row per module** in [`gdsmodule`](../auto-catalog/tables/gdsmodule.md). - Carries the URL fragment (`sName`), display names (`sChinese`, `sEnglish`, `sBig5`), tree position (`sParentId`, `sAllId`), product edition (`sVersionFlowId`, see [Slice 2](../slices/02-multi-tenancy.md)), optional CRUD procedure names (`sSaveProName`, `sDeleteProName`, `sProcName`, `sCalcProName`, `sSaveProNameBefore`). - 1,341 rows in this dev DB. The Java codebase calls this concept "module", "model", "models", and "modle" (typo, common). The variable `sModelsId` and method `getModelConfigByModleId` both take a *module* `sId`, despite the naming. See the naming caution in [Slice 1](../slices/01-hello-world.md). ## Form The screen layout for a module — header + N field definitions. - **Master:** one row per form in [`gdsconfigformmaster`](../auto-catalog/tables/gdsconfigformmaster.md). Carries the backing object (`sTbName`), backing-object type (`sType` ∈ `{table, view, proc}`), default SQL fragments (`sSqlStr`, `sWhere`, `sOrder`), grid behaviour (`bGrd`, `iPageSize`), jurisdiction column. - **Slave:** one row per *field* in [`gdsconfigformslave`](../auto-catalog/tables/gdsconfigformslave.md). Carries the field name (`sName` — matches a column in the backing object), control type (`sControlName`), display labels, validation rules, default value, dropdown SQL, button instructions. - A module is linked to its form via `gdsconfigformmaster.sParentId = gdsmodule.sId` — semantic FK, no enforcement. (Note: `gdsmodule.sFormId` exists but is empty for most modules and is *not* the canonical link.) - 1,995 master rows, 165,858 slave rows in this dev DB. The form is the user-visible artefact: when a PM "creates a screen" in BACK, what they're really doing is INSERTing a `gdsconfigformmaster` row plus 5-50 `gdsconfigformslave` rows. ## Virtual table xly's term (and table prefix) for a "table" that is itself defined as metadata, not as a `CREATE TABLE` statement. - One row per virtual table in [`gdsconfigtbmaster`](../auto-catalog/tables/gdsconfigtbmaster.md). - One row per virtual column in [`gdsconfigtbslave`](../auto-catalog/tables/gdsconfigtbslave.md). - 307 master rows, 14,322 slave rows in this dev DB. Virtual tables are how PMs declare "I want this kind of thing" without asking an engineer. Most virtual tables back to a real physical table (the framework supports the migration step, or it pre-creates a generic "wide" table the PM populates), but the *abstraction* the runtime sees is the metadata declaration, not the underlying storage. This is distinct from a database **view**: a virtual table is metadata; a view is a `CREATE VIEW` SQL object. Both can back a form (via `gdsconfigformmaster.sType = 'table'` or `'view'`). See [Slice 3](../slices/03-report.md) for the view-backed case. ## How they fit together ``` gdsmodule (module, 1 row) └── (joined via sParentId) gdsconfigformmaster (form-master, 1 row per form) ├── gdsconfigformslave (fields, N rows) ├── gdsconfigformcustomslave (per-tenant fields, N or 0) └── gdsconfigformuserslave (per-user tweaks, N or 0) gdsmodule.sName → registered route in gdsroute (URL whitelist) gdsmodule.sFormId → (mostly empty — historical) ``` Every form has exactly one module. Every module *should* have at most one form, but a few have several `gdsconfigformmaster` rows pointing at the same `sParentId` — those represent screens with multiple panels or sub-tabs. ## Three nouns, one engine The runtime — `BusinessBaseController` and `BusinessBaseServiceImpl`, documented in [Slice 1](../slices/01-hello-world.md) — knows how to render any module / form / virtual-table combination. There is no per-module Java code. PMs creating new modules are creating new rows; they are not creating new code paths.