Skip to content

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.

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 ops
ObjectController ─▶ IObjectService ──┼─▶ ObjectFormService structure · values · submit
(the facade) └─▶ IViewResolver ──┬─▶ ViewStore (ObjectView JSON)
└─▶ IViewRegistry (system views)
Prefer a picture?

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.

  1. The React client calls GET /object/{name}/metadata once to get the SQL-free schema, then decides whether to render a table, an editable form, or a read-only view.
  2. For a grid it calls POST /object/{name}/table with filters/paging; for a record it calls POST /object/{name}/form or /view.
  3. The controller delegates to IObjectService, which resolves the ObjectView, runs the appropriate SQL block through the dual-DB query path, and applies RBAC + parameter sanitisation.
  4. 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.

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, submit requires Create/Update, delete-row requires Delete.

Any permissions / canSubmit flags in a DTO are UI hints only — the server re-validates on every operation. See The disclosure gate.

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.

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.