Skip to content

SaaS Template Baseline Checklist

Single, repo-agnostic checklist every ConnectSoft.Saas.<Context>Template repo under C:\Git\ConnectSoft\ConnectSoft.Saas.*Template must satisfy. Use it as the PR gate and the template installer CI gate.

This checklist is derived from:

The five SaaS bounded-context repos this checklist applies to:

Repo Aggregate root Short name
ConnectSoft.Saas.TenantsTemplate Tenant connectsoft-saas-tenants
ConnectSoft.Saas.ProductsCatalogTemplate Product connectsoft-saas-productscatalog
ConnectSoft.Saas.EntitlementsTemplate Entitlement connectsoft-saas-entitlements
ConnectSoft.Saas.BillingTemplate Subscription connectsoft-saas-billing
ConnectSoft.Saas.MeteringTemplate UsageMeter connectsoft-saas-metering

1. Layer-3 mechanics (non-negotiable)

  • Repo is under C:\Git\ConnectSoft\ConnectSoft.Saas.*Template and has its own git remote.
  • .gitmodules maps base-template/ to ConnectSoft.BaseTemplate (relative URL ../ConnectSoft.BaseTemplate, branch master).
  • Clean clone + git submodule update --init --recursive succeeds.
  • Root Directory.Build.props imports ConnectSoft.TemplateRepositoryDirectory.Build.props and then base-template/Directory.Build.props.
  • Root Directory.Packages.props imports base-template/Directory.Packages.props.
  • ConnectSoft.TemplateRepositoryDirectory.Build.props sets ConnectSoftBaseTemplateDirectoryPostImport to build/DisableMicrosoftExtensionsStackForMinimalHost.props.
  • build/DisableMicrosoftExtensionsStackForMinimalHost.props imports ExtendedHost.BaseTemplateSatelliteDefaults.props for satellite paths and DisableMicrosoftExtensionsStackForMinimalHost.<Context>Host.props for everything else.
  • <Context>Host.props explicitly sets the canonical SaaS feature-flag matrix below.
  • No build/CentralPackageVersions.MinimalHost.props and no ConnectSoftCentralPackageVersionOverrides in ConnectSoft.TemplateRepositoryDirectory.Build.props unless an ADR documents an exception; rely on ConnectSoft.BaseTemplate Option A (conditional satellite PackageReference / PackageVersion alignment).
  • base-template/ submodule pointer references a ConnectSoft.BaseTemplate commit that includes Option A CPM satellite fixes (or newer).
  • nuget.config includes nuget.org + ConnectSoft + optional CoreWCF (package source mapping respected).
  • <Context>.slnx enumerates the subset of base-template/src/ConnectSoft.BaseTemplate.* projects this repo compiles plus the repo's own projects.

2. Canonical SaaS feature-flag matrix (<Context>Host.props)

Locked defaults for every SaaS repo (overridable per repo only via an ADR):

<Project>
  <PropertyGroup Label="SaaS: messaging and actors">
    <UseMassTransit>true</UseMassTransit>
    <MessagingModelTypeNone>false</MessagingModelTypeNone>
    <UseNServiceBus>false</UseNServiceBus>
    <UseOrleans>true</UseOrleans>
    <UseDapr>false</UseDapr>
    <UseAkka>false</UseAkka>
  </PropertyGroup>
  <PropertyGroup Label="SaaS: service model (REST + gRPC only)">
    <UseRestApi>true</UseRestApi>
    <UseGrpc>true</UseGrpc>
    <UseSignalR>false</UseSignalR>
    <UseGraphQL>false</UseGraphQL>
    <UseCoreWCF>false</UseCoreWCF>
    <UseServiceFabric>false</UseServiceFabric>
    <UseAzureFunction>false</UseAzureFunction>
  </PropertyGroup>
  <PropertyGroup Label="SaaS: persistence (NHibernate only, multi-dialect)">
    <UseNHibernate>true</UseNHibernate>
    <UseMongoDb>false</UseMongoDb>
    <Migrations>true</Migrations>
  </PropertyGroup>
  <PropertyGroup Label="SaaS: scheduler / cache / telemetry">
    <UseHangFire>true</UseHangFire>
    <DistributedCache>Redis</DistributedCache>
    <OpenTelemetry>true</OpenTelemetry>
    <UseOtelCollector>true</UseOtelCollector>
    <Serilog>true</Serilog>
    <Log4Net>false</Log4Net>
  </PropertyGroup>
  <PropertyGroup Label="SaaS: AI / vector / agents OFF by default">
    <UseMicrosoftExtensionsAI>false</UseMicrosoftExtensionsAI>
    <UseMicrosoftAgentFramework>false</UseMicrosoftAgentFramework>
    <UseVectorStore>false</UseVectorStore>
    <UseAgentSkills>false</UseAgentSkills>
    <UseModelContextProtocol>false</UseModelContextProtocol>
    <UseBotModel>false</UseBotModel>
    <UseAIModel>false</UseAIModel>
  </PropertyGroup>
</Project>
  • All of the above set exactly as shown (or overridden via ADR + changelog).
  • The minimal-host props DefineConstants strip removes all disabled preprocessor symbols so base-template/src projects compile for this repo.

3. One aggregate root per repo

  • <Context>.json declares exactly one entityModel.aggregateRoot.
  • <Context>.ArchitectureTests contains a test that fails if more than one EntityModel aggregate-root interface extends IAggregateRoot<>.
  • docs/adr/0001-one-aggregate-root-per-repo.md is present and up to date.
  • docs/out-of-scope.md lists deferred entities (if any) with a pointer to their future repo.

Current guardrail status (verified 2026-06-18):

Repo One aggregate root guardrail Cross-repo published-language guardrail
ConnectSoft.Saas.TenantsTemplate Implemented Implemented
ConnectSoft.Saas.ProductsCatalogTemplate Implemented Implemented
ConnectSoft.Saas.EntitlementsTemplate Implemented Implemented
ConnectSoft.Saas.BillingTemplate Implemented Implemented
ConnectSoft.Saas.MeteringTemplate Implemented Implemented; Orleans grain partition test still pending/skipped

4. Public contracts (ServiceModel + MessagingModel + ActorModel)

  • <Context>.ServiceModel contains DTOs + typed client facades.
  • <Context>.ServiceModel.RestApi contains controllers / minimal endpoints; published as a NuGet package.
  • <Context>.ServiceModel.Grpc contains code-first Grpc<Name>Service adapter classes implementing the C# [ServiceContract] interfaces from <Context>.ServiceModel (ServiceModel.Grpc — no .proto, no Grpc.Tools, no Protos/ folder); published as a NuGet package.
  • <Context>.MessagingModel contains integration event contracts (tenantId, aggregateId, aggregateVersion, schemaVersion, correlationId, causationId) and is published as a NuGet package.
  • <Context>.ActorModel (grain contracts) is published as a NuGet package when UseOrleans=true.
  • No external repo may reference <Context>.DomainModel, <Context>.EntityModel, or <Context>.PersistenceModel.*. Enforced in ArchitectureTests via concrete CrossRepoPublishedLanguageTests in all five repos.
  • Events version additively (.v1 topics); breaking changes require a .v2 topic.

5. Persistence and outbox

  • <Context>.PersistenceModel.NHibernate is the only persistence implementation; multi-dialect via Persistence:Dialect option (sqlserver, postgres, mysql, oracle, sqlite).
  • <Context>.DatabaseModel.Migrations uses FluentMigrator expressions; dialect branches only via IfDatabase(...) when an expression is impossible.
  • No custom outbox tables in migrations. MassTransit's built-in outbox (via BaseTemplate AddMassTransitOutbox extension) owns the schema.
  • Sample*Seed.cs constants align with saas-cross-repo-published-language.md sample seed IDs (7f4c2b9e-3d1a-4f8e-9c6b-5a0e183d42f1 tenant; catalog product/edition GUIDs where applicable).
  • docs/examples/sample-*-database.md documents seeded rows for acceptance tests.
  • Cross-repo reactions use MassTransit saga state machines (not standalone consumers).
  • Consumers are idempotent; inbox dedupe key is (sourceContext, eventId).

6. Multitenancy (hybrid default)

  • ConnectSoft.Extensions.Saas.* packages referenced (not ad hoc copies); MicroserviceRegistration / SaaS registration seam calls AddConnectSoftSaas* (or equivalent) from BaseTemplate/SaaS template.
  • DedicatedSingleTenant + appsettings path documented for single installation; matches multitenancy-configuration-schema.md.
  • ITenantContext populated from composite resolver (config → JWT tid → header X-Tenant-Id → transport); rejects missing tenant on tenant-scoped endpoints when required.
  • gRPC: server/client use ConnectSoft.Extensions.Saas.AspNetCore.Grpc—metadata key tenant-id per ADR-0100.
  • NHibernate: EnableFilter + parameter binding for shared-DB tenant filter in session scope (not only ApplyFilter on mappings).
  • NServiceBus: when UseNServiceBus=true, tenant propagation behaviors match MassTransit header tenant-id semantics.
  • Background jobs and MassTransit / NServiceBus consumers propagate the same tenant context (envelope/header parity).
  • Optional switches documented in docs/configuration.md:
  • Multitenancy:DeploymentKind = SharedDb (default row scoping)
  • Multitenancy:DeploymentKind = DatabasePerTenant
  • Multitenancy:DeploymentKind = ResidencySilo (per Residency / DataSiloId).

7. Actor / runtime coordination

  • Orleans silo co-hosted in <Context>.Application when UseOrleans=true.
  • Grain keys include tenantId (composite primary key).
  • Orleans streams used only for intra-silo fan-out. Cross-service integration goes through MassTransit.

8. Observability

  • OTel traces + metrics + logs via UseOtelCollector=true.
  • observability/otel-collector/ + observability/prometheus/ + observability/grafana/ + observability/loki/ + observability/tempo/ present and shared between DockerCompose and Deployment.
  • Grafana dashboards provisioned for: latency (p50/p95/p99), RED, errors, Orleans grain metrics (when on), MassTransit consume/publish, NHibernate query timings.
  • Prometheus alerting rules for: p99 latency SLO breach, error-ratio breach, saga dead-letter growth, outbox delivery lag.

9. Deployment assets

  • DockerCompose/ runs the service + chosen NHibernate DB + Redis + RabbitMQ + OTel Collector + Prometheus + Grafana + Loki + Tempo (or Jaeger).
  • Deployment/kubernetes/ contains raw manifests (Deployment/Service/HPA/PDB/NetworkPolicy/ServiceAccount/Ingress).
  • Deployment/helm/ contains a Helm chart with values for every supported dialect and a Values-driven observability block (ServiceMonitor/PodMonitor/PrometheusRule).
  • <Context>.InfrastructureModel provides Bicep/Terraform/Pulumi modules validated by bicep what-if / terraform plan / pulumi preview in the pipeline.

10. Template installer composition

  • .template.config/template.json is a minimal stub; real metadata is composed from base-template/.template.config/template.json + template/<context>.template.extend.json during CI.
  • Short name matches the canonical matrix in the table above.
  • azure-pipelines-template.yml calls the centralized submodule scripts base-template/build/Prepare-ExtendedTemplatePack.ps1 -PackProfile Simple and base-template/build/Invoke-TemplateCompose.ps1 -ComposeProfile Simple (no repo-root build/prepare-* or build/template-compose* shims).
  • The CI compose step strips base-template/.template.config from the final composed package after composition (single-installer model).
  • CI gate: dotnet new install + dotnet new connectsoft-saas-<context> -n Smoke + dotnet build on the generated solution.

11. Pipelines

  • azure-pipelines.yml — app build / test / docker.
  • azure-pipelines-service-model.yml — REST + gRPC NuGet publish.
  • azure-pipelines-messaging-model.yml — event contracts NuGet publish.
  • azure-pipelines-actor-model.yml — grain contracts NuGet publish (Orleans).
  • azure-pipelines-infrastructure-model.yml — IaC packaging + what-if.
  • azure-pipelines-documentation.ymlmkdocs build --strict + publish.
  • azure-pipelines-template.yml — single-installer composition + dotnet new gate.

12. Documentation

  • docs/ + mkdocs.yml (MkDocs Material + mermaid + plantuml).
  • Sections: Overview, Bounded Context, Aggregate Root, REST API, gRPC API, Events, Configuration, Multitenancy, Deployment (compose + k8s + IaC), Observability, ADRs, Getting Started, Out of Scope.
  • <Context>.DiagramAsCodeModel renders C4 + sequence + deployment diagrams in CI and embeds them in the docs site.
  • README.md at repo root with clone + git submodule update --init --recursive and a pointer to the docs site.
  • AGENTS.md at repo root with agent-oriented guidance aligned with the factory-generator.

13. AI Software Factory wiring

  • <Context>.json is the single source of truth for the aggregate.
  • <Context>.schema.json and <Context>.entitymodel.schema.json validate the descriptor.
  • build/regenerate.ps1 invokes the microservice-generator-agent (idempotent) to (re)generate EntityModel + PersistenceModel.NHibernate + DatabaseModel.Migrations + ServiceModel scaffolding.
  • Domain logic (invariants, policies, sagas) lives in <Context>.DomainModel.Impl and <Context>.FlowModel.MassTransit and is not touched by regeneration.

14. Gap traceability (wave 2)

Map baseline checklist sections to gap backlog Features in saas-gap-implementation-backlog.md (CompanyDocumentation).

Checklist section Gap Features (representative)
§3 Aggregate root / entity model saas-TEN-F01, saas-ENT-F01, saas-MET-F01, saas-BIL-F05
§4 ServiceModel surfaces saas-BIL-F08, saas-CAT-F06
§5 Actor / Orleans saas-INTEG-F06, saas-TEN-F08, saas-CAT-F01, saas-ENT-F07, saas-BIL-F10, saas-MET-F05
§6 Messaging / events saas-INTEG-F01F05, saas-TEN-F05, saas-BIL-F02, saas-MET-F03, saas-MET-F04
§7 Persistence / outbox saas-CAT-F05, saas-INTEG-F05
§8 Architecture tests saas-TEN-F06, saas-ENT-F06, saas-BIL-F09, saas-MET-F09
§9 Tests / coverage saas-TEN-F07, saas-ENT-F05, saas-CAT-F07, saas-MET-F07
§12 Documentation Per-repo docs/backlog/*-gap-analysis.md mirrors

Per-repo gap analysis: docs/backlog/<context>-gap-analysis.md in each template repository.

15. Domain implementation patterns (wave 2)

Cross-repo patterns documented in SaaS Domain Implementation Patterns.

  • Owned GrpcRichErrorInterceptor per repo in src/ConnectSoft.Saas.<Context>.ApplicationModel/ (not base-template generic).
  • FluentValidation — one validator per processor/retriever input in DomainModel.Impl/Validators/ with matching *ValidatorUnitTests.cs.
  • Domain <Context>MetricsRegisterTemplateMetrics singleton, wired in Default*Processor and Default*Retriever with *MetricsUnitTests.cs.
  • Entitlements-style loggingBeginScopeWithFlow, logger.Here().LogInformation, try/catch with error logs + metrics failure recorders in all domain handlers.
  • Per-repo docs/domain-implementation.md mirrors this checklist for the bounded context.

16. Acceptance

A repo is "SaaS-template baseline" only when every box above is checked. Gate both PR reviews and azure-pipelines-template.yml on this checklist.