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
- A client calls a FastEndpoints endpoint in
CommandCenter.API.Web. - Middleware applies exception handling, routing, CORS, rate limiting, and API-key authentication/authorization.
- The endpoint dispatches a command or query to
CommandCenter.API.UseCases. - Validation and transaction pipeline behaviors run around the handler.
- Handlers use domain types and interfaces from
CommandCenter.API.CoreandCommandCenter.API.SharedKernel. CommandCenter.API.Infrastructuresupplies the registered implementations: EF Core repositories and unit of work, SQL Server persistence, and external integrations such as Stripe, SendGrid, Keystone, and RoundTrip.- 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
| Area | Responsibility in this solution |
|---|---|
| Commands | Express an intention to change state. Handlers coordinate aggregates, domain ports, repositories, and transactions. |
| Queries | Retrieve data without changing domain state. Handlers use IReadRepository and specifications. |
| Aggregates | Enforce domain invariants around tenants, signup attempts, subscriptions, products, pricing, and processed Stripe events. |
| Domain ports | Keep the domain/application layers independent of SendGrid, Stripe, provisioning APIs, and password-protection implementations. |
| Repository ports | Define persistence contracts in the inner layers; EF Core implementations remain in Infrastructure. |
| Transaction behavior | Provides 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 context | Owns | Collaborates with |
|---|---|---|
| Platform | Tenant identity, ownership, and platform-level catalog concepts | Signup, Subscription & Billing, Customer Engagement |
| Signup | Signup attempts, idempotency, signup state, and the transition into an active tenant/customer | Platform, Subscription & Billing, Provisioning |
| Subscription & Billing | Subscriptions, products, tiers, pricing commitments, checkout, and processed Stripe events | Platform, Signup, Provisioning, Stripe |
| Customer Engagement | Contact, feedback, and support request workflows plus email notification intent | Platform, SendGrid |
| Provisioning | External account and tenant-admin provisioning workflows | Signup, Subscription & Billing, Keystone, RoundTrip |
| Shared Kernel | Cross-context primitives and contracts that are intentionally shared | All 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, andProcessedStripeEventsare the aggregate roots exposed asDbSets byAppDbContext.TierPricesis owned byProductTiers; a tier can have many historical prices, with at most one current row whereEffectiveTo IS NULL.PriceCommitmentLinesis owned byPriceCommitments; each commitment cannot repeat the same(ProductId, TierId)pair.Subscriptionshas 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, andPriceCommitmentLines.TierIduse restrictive foreign keys.SignupAttempts.TenantIdis 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.ProcessedStripeEventsis 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 type | Authentication | Application path | Persistence/integration behavior |
|---|---|---|---|
| Normal command | API key and endpoint authorization | Endpoint -> Mediator -> validation -> transaction -> command handler | Aggregate changes are persisted through EF Core; external adapters may be called |
| Normal query | API key and endpoint authorization | Endpoint -> Mediator -> validation -> query handler | Read repositories/specifications query SQL Server without a command transaction |
| Stripe webhook | Anonymous at the API-key layer; Stripe signature is verified by the webhook endpoint | Endpoint -> billing commands/queries as required | Stripe event idempotency is recorded before/with event processing |
| Health check | Public by default | MapHealthChecks("/health") | EF Core health check verifies database reachability |
| Swagger/OpenAPI | Governed by the configured Web pipeline | Swagger middleware generates the API description | No domain command/query is dispatched |
Important ordering guarantees
GlobalExceptionMiddlewareis first, so downstream failures are converted into the repository's standard error response.- Routing runs before authorization so endpoint metadata such as
AllowAnonymousis available. - Rate limiting runs before authentication to reject abusive traffic cheaply, including repeated invalid API-key attempts.
- FastEndpoints runs after authentication and authorization, so endpoint code receives an already-authorized request unless the endpoint is explicitly anonymous.