# The data-driven thesis xly is sold to many printing-industry customers, each of whom wants the ERP to behave a little differently — different forms, different reports, different approval rules, sometimes different stored procedures. The naive solution is a fork per customer: copy the codebase, modify, deploy. That is unmaintainable past two or three customers. xly's solution is the opposite: **a single codebase, a single deployment, and per-customer behaviour expressed as data**. The application's modules, forms, fields, dropdowns, permissions, document numbering, even the URL slugs are all rows in metadata tables (`gdsmodule`, `gdsconfigformmaster`, `gdsconfigformslave`, `gdsroute`, `gdsjurisdiction`, `gdsformconst`, …). The runtime is an *interpreter*. When a request comes in, the framework loads the relevant rows, joins the user's tenant context onto them, and renders the resulting form / list / report on demand. The Java code is generic; the application's behaviour is in the database. PMs (not engineers) own the metadata and therefore own the application. ## The cost Three costs are baked into this design and worth being explicit about: 1. **Per-request metadata reads.** Every page load runs five queries on cache miss: `gdsconfigformmaster` (with personalize/customslave overlays for the matching slave rows), `gdsformconst`, `sysjurisdiction` (per-user grants — the map key is named `gdsjurisdiction` but the actual table read is `sysjurisdiction`; skipped for ADMIN), `sysbillnosettings`, `sysreport`. The runtime caches aggressively, but those reads are unavoidable on cache miss. 2. **A schema that won't stop growing.** New module = a row in `gdsmodule` plus 1-50 rows in `gdsconfigformslave` plus a backing physical table (often per-document-type). The base-table count climbs as more business modules are introduced; production tenants typically carry more tables than a clean dev schema, since every customer- bespoke module survives in the shared schema. 3. **Relationships are conventions, not constraints.** With FKs disabled for performance and migration agility, every join from `gdsconfigformmaster.sParentId` to `gdsmodule.sId` (and a hundred similar joins) is a [semantic FK](semantic-fk.md). Orphan rows are possible. ## The reward In exchange xly gets: - **One codebase serves dozens of customers.** Each customer's tenant has its own metadata rows; the Java is identical. - **PMs evolve the application without engineering time.** They open BACK, add a module, define a form, set permissions, and the next user load shows the change. - **Customizations are layered cleanly** ([Slice 4](../slices/04-custom-field.md)): per-tenant overrides sit *on top of* the shared base without forking. ## When it breaks down Data-driven works until a customer needs behaviour that can't be expressed as metadata — different SQL, different procedure body, an aggregation rule that doesn't fit the framework's vocabulary. xly's escape hatch for that case is the [per-customer SQL override channel](../slices/05-customer-sql-override.md): hand-written SQL committed to `script/客户//` and applied directly to that customer's schema, bypassing the framework entirely. That channel is real and used. It is also the most expensive form of customization to maintain. ## What this means for reading the wiki Every slice in this wiki documents one *application of the thesis*. Slice 1 is the metadata read on a CRUD module — the canonical instance. Slice 2 is multi-tenant scoping through every layer. Slice 3 is the read-only / view-backed variant. Slice 4 is the customization overlay. Slice 5 is the escape hatch when the overlay isn't enough. Together they cover the data-driven design from its centre out.