Architecture Overview

June 10, 2026 · View on GitHub

DataSurface is organized as a set of modular NuGet packages, each with a clear responsibility. At the center is the ResourceContract — a normalized metadata object that describes everything about a CRUD resource.


Module Structure

┌─────────────────────────────────────────────────────────────────┐
│                        HTTP Layer                               │
│  DataSurface.Http                                               │
│  Minimal API mapping, query parsing, ETags, error mapping       │
├─────────────────────────────────────────────────────────────────┤
│                    IDataSurfaceCrudService                       │
│  ListAsync · GetAsync · CreateAsync · UpdateAsync · DeleteAsync  │
├──────────────────────────┬──────────────────────────────────────┤
│  EfDataSurfaceCrudService│  DynamicDataSurfaceCrudService       │
│  DataSurface.EFCore      │  DataSurface.Dynamic                 │
│  Static EF Core entities │  JSON / EAV dynamic records          │
├──────────────────────────┴──────────────────────────────────────┤
│                    ResourceContract                              │
│  DataSurface.Core                                               │
│  Fields · Relations · Operations · Query · Security             │
├──────────────────────────┬──────────────────────────────────────┤
│  ContractBuilder         │  DynamicContractBuilder              │
│  C# attributes → Contract│  DB metadata → Contract              │
└──────────────────────────┴──────────────────────────────────────┘

Supporting Modules

ModuleRole
DataSurface.AdminREST API for managing dynamic entity definitions at runtime
DataSurface.OpenApiSwashbuckle operation filters and typed schema generation
DataSurface.GeneratorRoslyn source generator for typed DTOs and minimal-API endpoint mappers

Key Abstractions

InterfacePackagePurpose
IDataSurfaceCrudServiceEFCoreExecutes CRUD operations against a backend
IResourceContractProviderEFCoreResolves ResourceContract by resource key or route
ICrudHook / ICrudHook<T>EFCoreGlobal and entity-specific lifecycle hooks
CrudOverrideRegistryEFCoreReplaces any CRUD operation with custom logic
IResourceFilter<T>EFCoreRow-level security — filters queryables per user context
IResourceAuthorizer<T>EFCoreInstance-level authorization — "can this user access entity X?"
IFieldAuthorizerEFCoreField-level read/write access control
ITenantResolverEFCoreResolves the current tenant ID from request context
IAuditLoggerEFCoreLogs all CRUD operations for audit trails
IQueryResultCacheEFCoreCaches query results via IDistributedCache
IWebhookPublisherCorePublishes events when CRUD operations occur
ISoftDeleteEFCoreConvention interface for soft-delete entities
ITimestampedEFCoreConvention interface for auto-timestamp entities
IApiKeyValidatorHttpCustom API key validation logic

Contract as Single Source of Truth

Every feature in DataSurface reads from the ResourceContract. There is no secondary configuration — the contract is the complete description of a resource.

[CrudResource] + [CrudField] + [CrudRelation] + ...


              ContractBuilder


             ResourceContract ◄── DynamicContractBuilder (from DB metadata)

        ┌───────────┼───────────────────────┐
        ▼           ▼                       ▼
   Query Engine  Validation Engine    Security Pipeline
   (filter/sort) (required/range/regex) (auth/tenant/field)
        │           │                       │
        └───────────┼───────────────────────┘

            CRUD Service Output

Two paths produce the same contract:

  1. Static: C# attributes → ContractBuilderResourceContract
  2. Dynamic: EntityDef / PropertyDef database rows → DynamicContractBuilderResourceContract

Once built, the contract is consumed identically by all downstream features. This means static and dynamic resources share the same validation, security, querying, and hook pipeline.


Backend Routing

When both static and dynamic resources coexist, a DataSurfaceCrudRouter dispatches operations to the correct backend service based on the contract's StorageBackend field:

BackendServiceStorage
EfCoreEfDataSurfaceCrudServiceEF Core DbContext
DynamicJsonDynamicDataSurfaceCrudServiceJSON records in metadata tables
DynamicEavDynamicDataSurfaceCrudServiceEntity-Attribute-Value storage
DynamicHybridDynamicDataSurfaceCrudServiceHybrid approach

The CompositeResourceContractProvider merges contracts from both static and dynamic sources, ensuring unified route resolution and discovery.


Next