Skip to main content

CommandCenter API architecture

The diagram below is derived from CommandCenterAPI.slnx, project references, and the current application composition root in src/CommandCenter.API.Web/Program.cs. Solid arrows represent project dependencies. Dashed arrows represent test coverage or runtime/external-system interactions.

Request flow​

  1. A client calls a FastEndpoints endpoint in CommandCenter.API.Web.
  2. Middleware applies exception handling, routing, CORS, rate limiting, and API-key authentication/authorization.
  3. The endpoint dispatches a command or query to CommandCenter.API.UseCases.
  4. Validation and transaction pipeline behaviors run around the handler.
  5. Handlers use domain types and interfaces from CommandCenter.API.Core and CommandCenter.API.SharedKernel.
  6. CommandCenter.API.Infrastructure supplies the registered implementations: EF Core repositories and unit of work, SQL Server persistence, and external integrations such as Stripe, SendGrid, Keystone, and RoundTrip.
  7. The result is mapped back to an HTTP response by the Web endpoint.

Architectural boundary​

The dependency direction points inward toward the domain:

Web -> UseCases -> Core
Web -> Infrastructure -> Core
Web / UseCases / Infrastructure -> SharedKernel

Infrastructure is the outer adapter for persistence and third-party services; UseCases should depend on Core/SharedKernel contracts rather than concrete infrastructure services.

CQRS and DDD view​

This view focuses on the application and domain model rather than deployment boundaries. Commands change state through aggregate behavior; queries read through repository abstractions and return projections or response models.

CQRS responsibilities​

AreaResponsibility in this solution
CommandsExpress an intention to change state. Handlers coordinate aggregates, domain ports, repositories, and transactions.
QueriesRetrieve data without changing domain state. Handlers use IReadRepository and specifications.
AggregatesEnforce domain invariants around tenants, signup attempts, subscriptions, products, pricing, and processed Stripe events.
Domain portsKeep the domain/application layers independent of SendGrid, Stripe, provisioning APIs, and password-protection implementations.
Repository portsDefine persistence contracts in the inner layers; EF Core implementations remain in Infrastructure.
Transaction behaviorProvides the command-side unit-of-work boundary around state-changing handlers.

The current implementation uses a shared SQL Server database and shared repository abstractions rather than separate physical read and write databases. The CQRS separation is therefore logical: commands and queries have different application models and responsibilities, while Infrastructure owns the storage technology.

Bounded context view​

The solution currently groups domain concepts into logical bounded contexts. These contexts are represented by namespaces, aggregates, features, and ports rather than separate deployable services or databases.

Context responsibilities​

Bounded contextOwnsCollaborates with
PlatformTenant identity, ownership, and platform-level catalog conceptsSignup, Subscription & Billing, Customer Engagement
SignupSignup attempts, idempotency, signup state, and the transition into an active tenant/customerPlatform, Subscription & Billing, Provisioning
Subscription & BillingSubscriptions, products, tiers, pricing commitments, checkout, and processed Stripe eventsPlatform, Signup, Provisioning, Stripe
Customer EngagementContact, feedback, and support request workflows plus email notification intentPlatform, SendGrid
ProvisioningExternal account and tenant-admin provisioning workflowsSignup, Subscription & Billing, Keystone, RoundTrip
Shared KernelCross-context primitives and contracts that are intentionally sharedAll contexts

Context-map relationship notes​

  • Signup -> Platform: a signup attempt results in a tenant being created or associated with the platform.
  • Signup -> Subscription & Billing: signup completion can create the initial subscription and checkout flow.
  • Platform ↔ Subscription & Billing: billing needs tenant identity, while platform-facing entitlement and subscription views depend on billing state.
  • Subscription & Billing -> Provisioning: a successful purchase or signup completion can trigger product-admin provisioning.
  • Customer Engagement -> Platform: engagement requests are associated with the tenant or customer making the request.
  • Contexts share infrastructure today: all contexts use the same API process, SQL Server database, and Shared Kernel. The diagram describes language and responsibility boundaries, not independent deployment boundaries.

Entity-relationship diagram​

This ER diagram reflects the EF Core model configured in CommandCenter.API.Infrastructure.Data.Config. TierPrices and PriceCommitmentLines are owned tables: they have their own primary keys in SQL Server, but their lifecycle belongs to the parent aggregate. Relationship lines marked as identifying owned data are therefore stronger than ordinary references.

Data-model notes​

  • Tenants, Products, ProductTiers, Subscriptions, PriceCommitments, SignupAttempts, and ProcessedStripeEvents are the aggregate roots exposed as DbSets by AppDbContext.
  • TierPrices is owned by ProductTiers; a tier can have many historical prices, with at most one current row where EffectiveTo IS NULL.
  • PriceCommitmentLines is owned by PriceCommitments; each commitment cannot repeat the same (ProductId, TierId) pair.
  • Subscriptions has a unique (TenantId, ProductId) constraint and indexes (TenantId, Status) for entitlement/status lookups.
  • ProductTiers.ProductId, Subscriptions.TenantId, Subscriptions.ProductId, Subscriptions.TierId, PriceCommitments.TenantId, PriceCommitmentLines.ProductId, and PriceCommitmentLines.TierId use restrictive foreign keys.
  • SignupAttempts.TenantId is nullable and is configured as a scalar value without an EF foreign-key constraint. It is a workflow correlation back to the tenant created during signup, not a navigational aggregate relationship.
  • ProcessedStripeEvents is an idempotency ledger keyed by Stripe's event ID; it intentionally has no foreign key to a single billing aggregate.

Extended request pipeline​

The following flow follows the middleware and service-registration order in CommandCenter.API.Web/Program.cs. It shows the common API request path, plus the special authentication behavior for the Stripe webhook and the public health-check endpoint.

Pipeline behavior by request type​

Request typeAuthenticationApplication pathPersistence/integration behavior
Normal commandAPI key and endpoint authorizationEndpoint -> Mediator -> validation -> transaction -> command handlerAggregate changes are persisted through EF Core; external adapters may be called
Normal queryAPI key and endpoint authorizationEndpoint -> Mediator -> validation -> query handlerRead repositories/specifications query SQL Server without a command transaction
Stripe webhookAnonymous at the API-key layer; Stripe signature is verified by the webhook endpointEndpoint -> billing commands/queries as requiredStripe event idempotency is recorded before/with event processing
Health checkPublic by defaultMapHealthChecks("/health")EF Core health check verifies database reachability
Swagger/OpenAPIGoverned by the configured Web pipelineSwagger middleware generates the API descriptionNo domain command/query is dispatched

Important ordering guarantees​

  1. GlobalExceptionMiddleware is first, so downstream failures are converted into the repository's standard error response.
  2. Routing runs before authorization so endpoint metadata such as AllowAnonymous is available.
  3. Rate limiting runs before authentication to reject abusive traffic cheaply, including repeated invalid API-key attempts.
  4. FastEndpoints runs after authentication and authorization, so endpoint code receives an already-authorized request unless the endpoint is explicitly anonymous.