Architecture
This page explains how the engine is put together — enough to know where a feature lives and how a request turns into JSON. For the day-to-day authoring surface, jump to Model Authoring.
The unified Object model
Section titled “The unified Object model”A View used to be two parallel types (TableView / FormView). It is now one ObjectView
(defined in Genie.Source), with capabilities inferred from which SQL blocks are present. The
runtime surface is a facade over focused chunk services (interface segregation):
┌─▶ ObjectGridService grid / table opsObjectController ─▶ IObjectService ──┼─▶ ObjectFormService structure · values · submit (the facade) └─▶ IViewResolver ──┬─▶ ViewStore (ObjectView JSON) └─▶ IViewRegistry (system views)ObjectService is a thin delegator — the real logic lives in the chunk services and their helpers
(FormServiceHelper, TableServiceHelper, ParameterSanitizer) under Services/Object.
Views are resolved either from the ViewStore (the EF table holding polymorphic ObjectView
JSON, populated by model migration) or from a hardcoded IViewRegistry for system views (Auth,
Workflow, Wizard) — each shipping both SQL Server and PostgreSQL variants.
How a request flows
Section titled “How a request flows”- The React client calls
GET /object/{name}/metadataonce to get the SQL-free schema, then decides whether to render a table, an editable form, or a read-only view. - For a grid it calls
POST /object/{name}/tablewith filters/paging; for a record it callsPOST /object/{name}/formor/view. - The controller delegates to
IObjectService, which resolves theObjectView, runs the appropriate SQL block through the dual-DB query path, and applies RBAC + parameter sanitisation. - The result is wrapped in a
SuccessDataResult<T>envelope. The UI merges structure (from metadata) with values (from form/view) client-side.
See the API Reference for the exact endpoint contracts.
Metadata vs data: the disclosure boundary
Section titled “Metadata vs data: the disclosure boundary”This split is central to the security model — respect it when adding features:
- Metadata (
GET /object/{name}/metadata) is SQL-free structure. It never returns SQL or record values, and is ungated beyond authentication. - Data (
POST /object/{name}/{table,form,view}and the mutation endpoints) is gated by the RBAC data-disclosure gate: loading record values requires View/Update,submitrequires Create/Update,delete-rowrequires Delete.
Any permissions / canSubmit flags in a DTO are UI hints only — the server re-validates on
every operation. See The disclosure gate.
Errors
Section titled “Errors”A global exception handler turns any unhandled exception into a consistent JSON envelope
{ success: false, error, traceId }, with a status code mapped from the exception type
(UnauthorizedException → 401, NotFoundException → 404,
ArgumentException/RuntimeException/GenieException/InvalidOperationException → 400, else 500).
Throw the right exception type from services and let the handler shape the response. See
Errors & exception handling.
Persistence
Section titled “Persistence”GenieContext (EF Core) runs over SQL Server and PostgreSQL, selected by
AppSettings:Datasource. The ViewStore table holds polymorphic ObjectView JSON. Traits add
audit, tenant (CompanyId) filtering, and soft-delete. Redis backs the cache (refresh tokens, MFA
gates) and the DataProtection key ring.