Generated SaaS Storage¶
Target Architecture — Final-State Design
This page describes the storage topology of a Generated SaaS Product. Each store has a single owning service (per the documentation standards storage-owner rule); no service reads another service's primary store directly. Stores are provisioned by Pulumi IaC and accessed through generated data-access code.
A generated product follows database-per-service with shared infrastructure for cross-cutting concerns (cache, secrets, blobs). Relational data uses NHibernate over Azure SQL or PostgreSQL; large artifacts use Blob Storage; hot read paths use Redis; secrets use Key Vault — and nothing else holds secret material.
Storage map¶
| Data | Store | Owner Service | Access Pattern | Retention | Notes |
|---|---|---|---|---|---|
| Tenant registry | Azure SQL / PostgreSQL | Tenant Management | Read-heavy lookups, low write | Tenant lifetime | Source of the tenantId discriminator |
| Users, roles, permissions | Azure SQL / PostgreSQL | Identity Service | Read-heavy (authz), moderate write | Tenant lifetime | Permissions are a platform-level catalog |
| OpenIddict tokens/clients | Azure SQL / PostgreSQL | Authorization Server | High read on validation | Token lifetime | Refresh/auth-code records pruned on expiry |
| Subscriptions, editions, billing | Azure SQL / PostgreSQL | Subscription & Billing | Transactional | Tenant lifetime + financial retention | Editions are a platform-level catalog |
| Feature flags, configuration | Azure SQL / PostgreSQL | Configuration Service | Read-very-heavy | Tenant lifetime | Hot values cached in Redis |
| Domain data | Azure SQL / PostgreSQL | Each domain microservice | Mixed | Tenant lifetime | Product-specific schemas |
| Audit entries | Append-only Azure SQL / PostgreSQL | Audit Trail Service | Append + query | Long-term (e.g. 7 yr) | Exported to Blob archive |
| Notifications | Azure SQL / PostgreSQL + Blob | Notification Service | Write + delivery state | 90 days hot, then archive | Large bodies/attachments in Blob |
| Integration connections | Azure SQL / PostgreSQL | Integration Service | Mixed | Tenant lifetime | Secrets as Key Vault references only |
| Report definitions, runs | Azure SQL / PostgreSQL | Reporting & Analytics | Mixed | Tenant lifetime | Generated artifacts in Blob |
| Report / export artifacts | Blob Storage | Reporting & Analytics | Write-once, read-many | Configurable (e.g. 1 yr) | Lifecycle policy enforced |
| Audit archive | Blob Storage (immutable) | Audit Trail Service | Write-once | Long-term | Immutable/WORM container |
| Notification attachments | Blob Storage | Notification Service | Write-once, read-many | 90 days then archive | — |
| Session / hot read cache | Redis | API Gateway, Configuration, Identity | Low-latency read/write | Ephemeral / TTL | Cache keys prefixed by tenantId |
| Rate-limit counters | Redis | API Gateway | High-frequency increment | Sliding window TTL | Per-tenant + per-route limits |
| Outbox messages | Azure SQL / PostgreSQL (per service) | Each publishing service | Write + drain | Until published | Drained by OutboxWorker |
| Secrets & connection strings | Key Vault | All services (read) | Read on startup / rotation | Until rotated | Referenced via SecretReference, never stored elsewhere |
Store ownership rules¶
- Single owner. Each store is owned by exactly one service; other services obtain data through that service's API or by subscribing to its events.
- Tenant scoping. Every tenant-owned store enforces the
tenantIddiscriminator (or schema/database-per-tenant) described in the data model. - No secrets outside Key Vault. Connection strings, signing keys, and integration credentials live only in Key Vault; aggregates store
SecretReferencepointers. - Cache is derived, never authoritative. Redis holds derived/hot copies with TTLs; the relational store is always the source of truth.
- Blob is for artifacts. Reports, audit archives, and attachments go to Blob with lifecycle policies; relational stores hold metadata and references.
Provisioning¶
All stores are provisioned by Pulumi IaC (see Deployment) per environment, with private networking, encryption at rest, and managed identities for access. No store is created or mutated outside the IaC pipeline.
How storage contributes to the pillars¶
- Traceability — outbox tables tie state changes to published events; audit archive preserves immutable history with
traceId. - Reusability — the storage topology is generated identically across products from shared templates and IaC modules.
- Autonomy — schemas, migrations, and IaC are generated and applied by agents and pipelines.
- Governance — immutable audit, Key Vault-only secrets, and retention policies enforce compliance at the storage layer.
- Observability — per-store metrics (latency, throughput, size) are emitted and tagged by tenant.
- Multi-tenant scale — discriminator-based isolation, per-tenant cache keys, and the option of schema/database-per-tenant scale to many tenants.