The Same Invoice, Twice
The same products. The same amounts. The same invoice.
A conventional application-orchestrated implementation compared with the same business act enacted as a Puppet.
Same input · Same result · Different architectural authority
Can the business act remain complete and executable without letting the domain decide how it is authorized, persisted, transported or rendered?
One Invoice Flow.
Two Architectural Implementations.
The conventional version places domain behavior, authorization, persistence and response handling inside one application-controlled flow.
The Puppet version preserves the invoice as one coherent domain act, while infrastructure remains outside the domain’s authority.
await authorizer.RequestAuthorizationAsync(c.Amount, ct);Authorization I/Oawait c.SaveAsync(repository, ct);Persistence I/Oawait xmlGenerator.GenerateInvoiceXmlAsync(c, ct);Output formatawait printer.GenerateInvoiceAsync(c, ct);Output formatweb.Respond(400);HTTP response
One application flow coordinates domain behavior, authorization, persistence and response.
- Emits values. The host decides the format — the words “tax authority” never appear inside the domain.
- No I/O calls
- The domain contains no authorization call, no save, no response and no output format.
The Puppet decides the validity and consequences of the domain act. Infrastructure remains at the boundary.
The same coupling, visible in the project file.
<FrameworkReference Include="Microsoft.AspNetCore.App" />Required even by the console app, which serves no HTTP
Same invoice. Same result.
Different architectural authority.
The same sale, recorded.
Cashier Example — Code Validation
A walk through both codebases, file by file, checking what each one is allowed to decide.
Cashier as a Puppet
The sale enacted as a Puppet — the act, its Check, and what the boundary observes.
The responsibilities do not disappear. They move.
- external calls
- persistence
- HTTP response
- output formatting
- cross-domain sequencing
- validity of evidence
- domain consequences
- observable domain values
- authorization acquisition
- persistence
- transport
- presentation
The conventional implementation uses interfaces, dependency injection, repositories, logging and transactions — and the domain still knows about infrastructure and destinations. The difference is not discipline. It is where authority sits.
Not the invoice.
Not the values.
Not the business outcome.
What changed was the architectural authority: who was allowed to validate, persist, transport and render the act.
What changed was who was allowed to decide:
- the domain no longer decides HTTP
- the domain no longer decides persistence
- the domain no longer decides presentation
- the host no longer decides whether the domain act is valid
What the domain stopped carrying.
An existence proof that the separation is buildable — not a cost or performance evaluation.
The coupling is structural, not organizational.
The conventional domain cannot be split into three projects. Sales and Warehouse form a dependency cycle, so the build fails — the repository keeps the failed attempt as evidence. Both sides are then left unsplit, so the comparison stays controlled.
- The conventional console requires the ASP.NET Core framework despite serving no HTTP.
- Splitting the conventional domain into three DLLs does not compile: Sales ↔ Warehouse.
- A rejection is not a rollback. Nothing journals, so the entry ID never advances.
- One act serves two audiences; the journal records both as identical invocations.
The Puppet does not remove infrastructure.
It removes infrastructure authority from the domain.
The invoice remains one coherent act.
The journal preserves what occurred.
The boundary decides how that act is staged and observed.
See for yourself.
Execute both implementations
dotnet build CashierExample.slnx — the README covers the setup. Run the same sale through both implementations and compare them yourself.
View run instructionsReview the architectural evidence
Both implementations, the comparison charts, and the framework friction the exercise surfaced — including the split that does not compile.
Open GitHubBring your own act
If your domain is carrying infrastructure it should not, the same separation applies to it.
Start a conversation