Viklund Random thoughts


Series: Architecture in Practice

A field report from six months of building a modular .NET backend with one team, based on presentations I’ve given at work. It isn’t a tutorial. The concepts are explained better elsewhere, and the posts link to where. This series is about what the architecture looks like in a real codebase: what we started with, what changed, and which rules the build enforces now. It’s for developers who already know the basics of domain-driven design and ports and adapters.

  1. Four Models of One Person: domain-driven design, from the glossary to the rules inside a module.
  2. Ports and Adapters Without Exceptions: the layout, the naming and the architecture tests that enforce it.
  3. Bridges Ask, Events Tell: how modules talk to each other without knowing each other.
  4. Types as Proof: parsing at the boundary, so rules can’t be skipped.

Cheat sheet

Where does it go?

WhatWhere
A business rule, aggregate, value object or domain eventCore
Something the application can do, one per use caseApplication/Ports/Driving/<Action>/: the port, its command and its response
The code that does itApplication/UseCases/<Action>UseCase, internal and sealed
Something a use case needs from outsideApplication/Ports/Driven/IFor…ing
Logic shared by use cases and implemented in ApplicationApplication/Services. It isn’t a port
HTTP endpoints, background jobs, integration event handlersInfrastructure, as driving adapters
Database, scheduler, configurationInfrastructure, as driven adapters
Asking another module for factsA bridge in your Infrastructure, calling that module’s Contracts
What other modules may call or hear aboutYour Contracts, implemented by contract adapters

Naming

ThingExample
Driving portISubmitTicketPort, with SubmitTicketCommand and SubmitTicketResponse
Use caseSubmitTicketUseCase
Driven portIForStoringTickets: what the application needs, not the technology
ContractIContractForFetchingTopic
Integration event handlerOnTicketSubmittedCreateAssignment

Adding a feature

  1. The behaviour and its domain event in Core, with tests.
  2. A use case test with test doubles for the driven ports. It’s the first driver, so it shapes the command, the response and the names.
  3. The driving port, with a command made of primitives.
  4. The use case: parse the input, call the domain, store, commit.
  5. Reuse existing driven ports. Add a new IFor…ing port and its adapter only when nothing fits.
  6. Map the domain event to an integration event, if other modules should know.
  7. The driving adapter last: an endpoint, a job or an event handler.

Rules the build enforces

The test for the Guid rule is in part 2.

Further reading

2026