← DemosDemo 01 · Executable comparison

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

The question

Can the business act remain complete and executable without letting the domain decide how it is authorized, persisted, transported or rendered?

Executable architectural comparison

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.

Conventional implementationApplication owns the flow
01-before/Cashier.Domain/Sales/InvoicingService.csInvoice()
  1. await authorizer.RequestAuthorizationAsync(c.Amount, ct);Authorization I/O
  2. await c.SaveAsync(repository, ct);Persistence I/O
  3. await xmlGenerator.GenerateInvoiceXmlAsync(c, ct);Output format
  4. await printer.GenerateInvoiceAsync(c, ct);Output format
  5. web.Respond(400);HTTP response
View this file on GitHub →

One application flow coordinates domain behavior, authorization, persistence and response.

Puppet implementationDomain owns the act
PerformCheckThenEnact
One act. Authorized evidence enters it from outside.
Check
Compares the authorized amount against the actual item value. The host brings evidence, not a verdict.
print
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.

Cashier.Domain.csproj
  1. <FrameworkReference Include="Microsoft.AspNetCore.App" />Required even by the console app, which serves no HTTP
Cashier.Puppet.Domain.csproj
  1. <FrameworkReference Include="Microsoft.NETCore.App" />No web framework, and no ItemGroup dependencies

Same invoice. Same result.
Different architectural authority.

See it run

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.

CashierExampleBoth implementations, the comparison charts and the framework friction the exercise surfaced. Clone it and run the same sale through each to compare them yourself.Open on GitHub
What each side is allowed to decide

The responsibilities do not disappear. They move.

Application owns
  • external calls
  • persistence
  • HTTP response
  • output formatting
  • cross-domain sequencing
Puppet owns
  • validity of evidence
  • domain consequences
  • observable domain values
Boundary owns
  • 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.

What changed?

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
Measurable differences

What the domain stopped carrying.

Files in the domain
Conventional implementation22
Puppet implementation5
I/O calls inside the flow
Conventional implementation5
Puppet implementation0
Output formats in the domain
Conventional implementationweb framework dependency
Puppet implementation0

An existence proof that the separation is buildable — not a cost or performance evaluation.

What the exercise surfaced

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.
See the attempt that does not compile →
What the experiment shows

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.

Run

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 instructions
Inspect

Review the architectural evidence

Both implementations, the comparison charts, and the framework friction the exercise surfaced — including the split that does not compile.

Open GitHub
Talk

Bring your own act

If your domain is carrying infrastructure it should not, the same separation applies to it.

Start a conversation