SaaS Aggregate Root Assignment (Canonical)¶
Authoritative map of the one aggregate root that each ConnectSoft.Saas.<Context>Template repo owns. Pairs with:
- SaaS Template Baseline Checklist
- SaaS Bounded Contexts & Templates
- saas-platform-ddd-blueprint.md
- ArchitectureDocumentation/SAAS/Saas Platform.md
Rule¶
Every
ConnectSoft.Saas.<Context>Templaterepo owns exactly one aggregate root. Related concepts are either (1) child entities / value objects of that root, (2) other aggregates in other repos referenced by opaque ID and integrated via events, or (3) explicitly deferred (seedocs/out-of-scope.md).
The rule is enforced in CI by <Context>.ArchitectureTests.
Canonical map¶
flowchart LR
subgraph saas [ConnectSoft SAAS workspace]
T["TenantsTemplate (Tenant)"]
P["ProductsCatalogTemplate (Product)"]
E["EntitlementsTemplate (Entitlement)"]
B["BillingTemplate (Subscription)"]
M["MeteringTemplate (UsageMeter)"]
end
T -->|"tenant.created"| E
P -->|"catalog.entitlements.changed"| E
P -->|"catalog.entitlements.changed"| B
E -->|"entitlements.updated"| B
B -->|"subscription.changed"| E
B -->|"subscription.period.rating-window"| M
M -->|"quota.exceeded"| B
T -.->|"TenantId by ID"| P
T -.->|"TenantId by ID"| B
T -.->|"TenantId by ID"| M
Hold "Alt" / "Option" to enable pan & zoom
ConnectSoft.Saas.TenantsTemplate — aggregate root Tenant¶
- Repo path:
C:\Git\ConnectSoft\SAAS\ConnectSoft.Saas.TenantsTemplate - Short name:
connectsoft-saas-tenants - In-aggregate:
TenantProfile(VO) — display name, slug, locale.TenantRegionResidency(VO) — residency tier +DataSiloId.Contact(entity, local identity) — owner / billing / technical.TenantConfiguration(entity) — feature overrides + config bundle.LifecycleStatus(smart enum) —Pending,Active,Suspended,Deleted.- References by ID only:
EditionRef(ProductsCatalog),BillingAccountRef(Billing). - Published events:
tenants.domain.v1.tenant-created,…-activated,…-suspended,…-deleted,…-residency-changed. - Deferred: multi-tenant organization trees, tenant-directory identity federation (see
docs/out-of-scope.md).
ConnectSoft.Saas.ProductsCatalogTemplate — aggregate root Product¶
- Repo path:
C:\Git\ConnectSoft\SAAS\ConnectSoft.Saas.ProductsCatalogTemplate - Short name:
connectsoft-saas-productscatalog - In-aggregate:
Edition(entity) — stableEditionId; child ofProduct.EditionFeature(entity) — feature mapping per edition.Feature(entity) — catalog feature keys + descriptors.ServiceLevelAgreement(entity) — SLA per product/edition.BusinessModel(entity),EditionBusinessModel(entity) — monetization model per edition.PricingModel(entity),EditionPricing(entity) — pricing declarations (structural only; enforcement lives in Billing).BillingRule(VO),AccessRule(VO),UsageLimit(VO) — rule declarations.- References by ID only:
TenantId,SubscriptionId(Billing). - Published events:
catalog.domain.v1.product-created,…-edition-added,…-feature-activated,catalog.entitlements.v1.entitlements-changed. - ADR
0002-edition-inside-product-aggregate.md(mandatory): althoughEditionhas a strong identity, it stays an in-aggregate child ofProductfor this wave; a future split intoEditionsTemplateis an additive change (theEditionIdidentity stays stable and the published event surface is already addressed byEditionId). - Seed vocabulary is derived from
ConnectSoft.Saas..ProductCatalogDemo— see itsConnectSoft.Saas.ProductsCatalog.jsondescriptor for the reference shape. The demo repo is not cloned as solution structure.
ConnectSoft.Saas.EntitlementsTemplate — aggregate root Entitlement¶
- Repo path:
C:\Git\ConnectSoft\SAAS\ConnectSoft.Saas.EntitlementsTemplate - Short name:
connectsoft-saas-entitlements - In-aggregate:
EntitlementAssignment(entity) — whichEditionRefis assigned to whichTenantId.TenantFeatureOverride(entity) — per-tenant feature on/off overrides.EffectiveEntitlementDescriptor(VO) — materialized effective entitlements snapshot.- References by ID only:
TenantId,EditionRef,FeatureKey. - Published events:
entitlements.v1.effective-entitlements-updated,entitlements.v1.feature-overridden,entitlements.v1.assignment-changed. - Deferred: pay-per-use entitlements, entitlement rollouts per geo — out of scope this wave.
ConnectSoft.Saas.BillingTemplate — aggregate root Subscription¶
- Repo path:
C:\Git\ConnectSoft\SAAS\ConnectSoft.Saas.BillingTemplate - Short name:
connectsoft-saas-billing - In-aggregate:
BillingCycle(smart enum) —Monthly,Annual,Custom.PromotionApplication(entity) — promotion usage per subscription.SeatPolicy(VO) — seats min/max/current.ProrationPolicy(VO) — how upgrades / downgrades prorate.- References by ID only:
TenantId,EditionRef,PricingModelId(ProductsCatalog). - Published events:
billing.subscriptions.v1.subscription-created,…-upgraded,…-canceled,billing.invoices.v1.invoice-issued,billing.payments.v1.payment-captured. - Not roots here (and so NOT in this repo as roots):
Invoice— read-model projection this wave; promote toInvoicingTemplatelater.PricingModel/BillingRule— owned by ProductsCatalog (structural) + Billing (enforcement). Billing never writes them; it reads the ServiceModel NuGet.- External ACL: payments / tax providers via
BillingTemplate.FlowModel.MassTransitadapters.
ConnectSoft.Saas.MeteringTemplate — aggregate root UsageMeter¶
- Repo path:
C:\Git\ConnectSoft\SAAS\ConnectSoft.Saas.MeteringTemplate - Short name:
connectsoft-saas-metering - Grain key:
{tenantId, dimension}. Orleans is the canonical runtime for this aggregate (per-tenant stateful counters). - In-aggregate:
Counter(entity) — running totals per window.Window(VO) —{from, to, granularity}.UsageRecord(VO) — individual event (with idempotency key + integrity hash).- References by ID only:
TenantId,Dimension,SubscriptionId(Billing). - Published events:
metering.usage.v1.usage-recorded,metering.counters.v1.counter-rolled,metering.quota.v1.quota-threshold-crossed,metering.quota.v1.quota-exceeded. - Deferred: billing-grade rating / invoicing integration (Billing consumes events; rating is Billing's).
Enforcement¶
<Context>.ArchitectureTestsincludes a test asserting exactly one aggregate-root marker in<Context>.DomainModel.<Context>.jsonschema enforces a singleentityModel.aggregateRootproperty.docs/adr/0001-one-aggregate-root-per-repo.mdis mandatory; ProductsCatalog additionally shipsdocs/adr/0002-edition-inside-product-aggregate.md.- The installer CI gate (
azure-pipelines-template.yml) fails when either the descriptor or architecture tests violate the rule.