Tenant Provisioning & Management

Struktural is inherently multi-tenant. Each application (Tenant) acts as a completely isolated environment with its own database schema, UI definitions, and business logic.

The File System Structure

Tenant definitions are stored in the Apps/ directory at the root of the hosting environment.

Apps/
├── default/                  # The fallback tenant
│   └── Definitions/
│       ├── app-config.json   # Infrastructure settings (DB, Storage)
│       ├── app-schema.json   # Entity and External Service definitions
│       ├── app-ui.json       # View and Menu definitions
│       ├── Scripts/          # C# .cs.script files
│       └── i18n/             # Translation JSONs
├── erp-client-a/
│   └── Definitions/ ...
└── crm-client-b/
    └── Definitions/ ...

AppId Routing & Resolution

The AppIdentificationMiddleware resolves which tenant context to load using the following priority hierarchy:

  1. URL Path: Explicit API routing (e.g., /api/apps/erp-client-a/customer).
  2. Apex Domain / Host Header: The platform maps incoming hostnames to AppIds via the TenantDomains section in appsettings.json.
    • Example: portal.ejemplo.com maps to crm-client-b.
  3. Subdomain Routing: If the host is erp-client-a.struktural.demo, the middleware automatically extracts erp-client-a as the AppId.
  4. Custom Header: In local development, the X-Struktural-App-Id header can dictate the context.
  5. Fallback: If all else fails, it reads Apps/default-tenant.txt to find the default application, or defaults to default.

Configuration Overrides

While app-config.json stores tenant-specific settings, DevOps can override these securely via environment variables or appsettings.json using the Tenants section. This is crucial for CI/CD pipelines where you do not want connection strings stored in the file system definition.

"Tenants": {
  "erp-client-a": {
    "Database": {
      "ConnectionString": "Server=prod-db;..."
    }
  }
}