Skip to content

Development

Adding a command

  1. Add an input type in .DomainModel and a *Validator in .DomainModel.Impl/Validators.
  2. Add the method to ISubscriptionsProcessor and implement in DefaultSubscriptionsProcessor (mutate the Subscription aggregate, publish events via IEventBus).
  3. Expose it: add an operation to ISubscriptionManagementService (ServiceModel), implement in the REST controller and the Grpc* adapter so both adapters stay at parity.
  4. Add unit + acceptance tests.

Reacting to another context

Cross-context reactions are sagas, not public endpoints:

  1. Add an inbound stub in MessagingModel/Inbound/ mapped to the source topic.
  2. Add/extend a state machine in FlowModel.MassTransit/ that calls a reaction hook (ReactTo...Async) on the processor.
  3. Keep handlers idempotent (see resiliency).

Aggregate invariants

Billing enforces one aggregate root (ISubscription) - the architecture test will fail if another IAggregateRoot<> appears in EntityModel. Keep invoice/payment as events, not aggregates (docs/out-of-scope.md).

Working with the base template

Shared infrastructure lives in the base-template/ submodule. The minimal-host feature matrix (build/DisableMicrosoftExtensionsStackForMinimalHost.BillingHost.props) controls which optional stacks are on (MassTransit, Orleans, NHibernate, Hangfire, Redis, OpenTelemetry) vs off. See template layering and reuse.

See also