System architecture
Understand the host, Vue client, form runtime, data access, modules, migrations and deployment boundaries.
FormPlatform System Architecture
中文:SYSTEM_ARCHITECTURE.zh-CN.md
1. Purpose
FormPlatform is a form-driven .NET 10 web platform. The server supplies identity, authorization, form storage, a dynamic ORM, submission/trigger processing, surveys, commerce, and trusted extensions. The Vue 3/Vite client supplies the Designer, Viewer, runtime controls, and the system shell. Business UI should normally be assembled from forms; write a Vue control or standalone client module only for complex or reusable interaction.
Four assets evolve independently:
- the closed Host runtime and official services;
- `FormPlatform.Sdk`, containing ORM, forms, actions/triggers, migrations, and error contracts;
- editable form schemas, metadata, action code, and global form CSS;
- customer modules containing server DLLs, configuration, migrations, APIs, and browser extensions.
2. Runtime topology
flowchart TB
Browser["Browser: Vue 3 SPA"] --> Router["Vue Router + system layout forms"]
Router --> Runtime["FormReader / FormRenderer / Form Runtime"]
Runtime --> Http["Unified HTTP client"]
Http --> Host["ASP.NET Core .NET 10 Host"]
Host --> Endpoints["Hosting/Endpoints domain route groups"]
Endpoints --> Services["Domain services"]
Services --> ORM["FormPlatform.Sdk DataAccess ORM"]
Services --> Store["IFormStore"]
ORM --> DB[("PostgreSQL / MySQL / SQL Server")]
Store --> Memory["Memory"]
Store --> File["File"]
Store --> DB
Modules["Trusted modules"] --> Host
Modules --> ORM
Modules --> ClientExt["Client extension modules"]
ClientExt --> Runtime
The browser never receives connection strings, constructs SQL, or owns database identifiers. Endpoints enforce authentication and request boundaries, domain services own business rules, and the ORM/store owns persistence.
3. Composition root and startup
`src/FormPlatform.Host/Program.cs` only composes configuration, modules, official services, authentication/authorization, middleware, endpoint groups, and the SPA fallback. Business routes live in `src/FormPlatform.Host/Hosting/Endpoints/*Endpoints.cs`; authentication registration lives in `src/FormPlatform.Host/Hosting/FormPlatformAuthenticationRegistration.cs`.
Startup proceeds as follows:
1. Create the builder and read base configuration.
2. Discover `src/FormPlatform.Host/Modules/**/module.json` and validate manifests and assembly compatibility.
3. Allow modules to contribute configuration sources.
4. Register Core, Survey, Commerce, Feature, authentication, and module services.
5. Build the application and install exception, status-code, authentication, and authorization middleware.
6. Map official endpoints, then module endpoints, static files, and the SPA fallback.
7. Hosted services run migrations first, then identity, data-model, system-form, and data seeders.
Fixed-schema ordering is deliberate:
ModuleDatabaseMigrationRunner
-> FormPlatform.Core migrations
-> FormPlatform.Commerce migrations
-> customer module migrations
ManagementDatabaseInitializer (identity/role seed only)
other idempotent seeders and system-form initializers
4. Client architecture
`src/FormPlatform.Host/ClientApp` is a Vue 3 SPA. Vue Router owns Designer, Viewer, administration, respondent, and module routes. Header, left pane, right pane, and footer may themselves be system forms.
Important layers are:
- `FormReader`: loads schema and creates data, validation, and Runtime API state;
- `FormRenderer`: recursively selects and renders controls;
- `formComponentRegistry`: lazy registry for built-in and extension controls;
- `actionRuntime`: stable Application Context plus temporary Form Action Context;
- `httpClient.js`: JSON/blob transport, locale headers, and common error parsing;
- `i18n.js`: client catalogs and `@key` system content;
- `globalFormStyles`: loads merged `/api/assets/forms.css`.
Designer mode must not request real business APIs. Collection controls render deterministic preview data so an unauthorized or empty preview does not look deletable.
5. Form assets and lifecycle
A form consists of:
- `FormDefinition`: component tree, properties, events, and `usedCssClasses`;
- `FormMetadata`: type, anonymity, data mapping, actions/triggers, and entity information;
- a client action module;
- a persisted global CSS artifact generated in the background from Tailwind classes extracted at save time.
`IFormStore` supports Memory, File, and relational providers. Management users, security, data models, ACL, and Survey/Commerce management data always use the Management Database regardless of FormStorage.
A typical submission is:
Viewer/Pagination submit
-> client validation
-> form or survey endpoint
-> ACL/deployment checks
-> FormSubmissionDispatcher
-> validation/before triggers
-> ORM transaction and entity/collection work
-> after triggers
-> commit
-> platform response, toast, and navigation
Hidden controls are omitted. Empty nullable inputs are omitted or normalized to null. The server always validates again.
6. Data and ORM
Data belongs to three domains: platform management data, form assets, and business records. A Data Model maps a logical entity to a table/view. Attributes map columns, References describe cross-entity relationships, and Collections describe one-to-many relationships. A control's `propertyName` maps through Form/Data Mapping to an attribute; the control does not name a database column directly.
The ORM supplies provider dialects, parameterized query and projection, filters, sorting, pagination, reference joins, mutations, transactions, schema inspection, and migration planning. Queries project only requested fields. Multiple foreign keys to one table use separate reference names such as `createdBy` and `updatedBy`.
Keep handwritten SQL only for provider metadata, locks, DDL, deployment-specific dynamic tables, or capabilities not yet represented by the ORM. Never turn business input into an identifier or SQL fragment.
7. Migrations and dynamic tables
Fixed tables are declared through `IDatabaseMigrationModule`. Each immutable migration provides ordered PostgreSQL, MySQL, and SQL Server statements. The runner uses a database lock, `app_schema_migrations`, and a SHA-256 checksum. Never edit a released migration; add a higher ID.
Deployment-generated `data_*` response tables, replaceable relational FormStore schema, and seed data are not fixed platform migrations. `DatabaseMigrations:Enabled=false` is valid only when deployment automation has already migrated the database.
8. Modules and SDK boundary
A trusted module implements the SDK-owned `IFormPlatformSdkModule` and can participate in configuration, service registration, middleware, and endpoint mapping. It consumes ORM, forms, actions/triggers, migrations, errors, and the module entry contract from `FormPlatform.Sdk.dll`; `FormPlatform.Extension.Abstractions.dll` remains the low-level shared loader contract.
`module.json` fixes the module name, version, entry assembly/type, and compatible SDK/Host ranges. The Host rejects duplicates, path escape, incompatible versions, Host assembly references, and private SDK copies. Modules execute in the Host process and are trusted extensions, not a sandbox.
Browser extensions load before Vue mounts and may register controls, events, routes, Action APIs, Runtime APIs, and i18n catalogs. `/extension-assets/{module}/...` serves packaged static assets.
9. Identity and authorization
System users use the default cookie. Respondents use an isolated cookie and may establish an external identity through Google/Facebook OIDC. Administrative survey assistance keeps the system-user identity separate from the acting respondent context.
Form capabilities include read, edit, delete, and survey assistance. Deny wins. Administrator has platform defaults; FormDesigner and SurveyAssistant receive type-specific defaults. Client visibility is UX only—every server endpoint authorizes again.
Form Center performs database-side ACL candidate filtering with cursor pagination instead of loading all forms into the browser.
10. Errors, logging, and i18n
Every API failure uses `PlatformErrorResponse`: `code`, `messageKey`, `fallback`, `parameters`, `fieldErrors`, `status`, and `traceId`. Endpoints use `PlatformResults`; reusable domain/module code throws `PlatformApiException`; `PlatformExceptionHandler` logs unhandled failures and hides internal 500 details.
The client parses failures only through `httpClient.js`. Stable message keys support localization, fallback text preserves readability, and field errors remain attached to the correct FormReader or inline editor.
Logging supports Console, Debug, and optional rolling files. `DataAccess:LogQuerySql` and `LogMutationSql` control SQL diagnostics. Parameter values should normally remain disabled in production.
11. Official subsystems
- Survey: respondents, lists, deployments, generated response tables, files, progress/completion, assistance, anonymous and external identities.
- Commerce: products, carts, orders, payments, PayPal/Stripe/WeChat providers, and the geographical tree.
- System Forms: layout, Form Center, Data Models, Mapping, ACL, and Survey administration.
These are first-party modular services in the Host repository. Customer functionality should normally be an independent module.
12. Deployment
Development commonly uses Vite on 5173 proxying .NET on 5080. Published deployments serve `wwwroot` from ASP.NET Core. Linux/Raspberry Pi uses systemd plus Nginx/HTTPS; Windows uses IIS reverse proxy. Secrets belong in User Secrets, environment variables, systemd credentials, or protected external JSON—not committed `appsettings.json`.
13. Directory map
| Path | Responsibility |
|---|---|
| `src/FormPlatform.Host/Program.cs`, `src/FormPlatform.Host/Hosting/` | composition, middleware, endpoints, module loading |
| `src/FormPlatform.Host/Data/` | official services, stores, system forms, migrations, seeders |
| `src/FormPlatform.Sdk/` | public SDK: ORM, metadata, queries, transactions, triggers and shared contracts |
| `src/FormPlatform.Extension.Abstractions/` | module lifecycle/manifest contracts |
| `src/FormPlatform.Licensing/` | signed/encrypted license primitives |
| `src/FormPlatform.Host/ClientApp/src/` | Vue SPA, Designer, Viewer, controls, Runtime |
| `src/FormPlatform.Host/Modules/` | deployed trusted packages during development |
| `samples/` | self-contained extension examples |
| `tests/` | PostgreSQL integration and Playwright E2E tests |
| `docs/` | handbooks, focused references, change log |
14. Architectural invariants
1. The server is the final authority for authorization and validation.
2. Vue and form JSON never construct SQL or receive connection strings.
3. Fixed DDL is a migration; initializers only seed.
4. Modules reference SDK/Abstractions, never the Host.
5. API failures use the common contract; system text uses message keys.
6. Designer mode never reads runtime data.
7. Form saves pass through `PublishingFormStore` to keep CSS/cache consistent.
8. Reuse forms and controls before creating a new control or standalone Vue route.