✎ Text Section 5 of 6

Make it concrete: one command, one port

Anchor the architecture in a single SaaS action: inviting a teammate to a workspace.

Abstractions evaporate without a concrete anchor, so here’s the smallest real thing. Take a single action in a SaaS product: inviting a teammate to a workspace.

The request arrives at a primary adapter (an HTTP endpoint). It hands the core a single instruction — invite this person to this workspace — and steps back. Inside the core, one piece of code is responsible for that instruction. Call it a command handler. It loads the workspace through a repository port, applies the rules (is there a free seat on the plan? is this person already a member?), and saves the result back through that same port.

// illustrative — not stack-specific
InviteTeammateHandler.handle(command):
    workspace = repository.load(command.workspaceId)   // secondary port
    workspace.invite(command.email)                    // business rule lives here
    repository.save(workspace)                          // secondary port

Three things to notice, because they carry the rest of the course:

  1. The handler is the single entry point for this behaviour. There’s one obvious place the rule lives, and one obvious place to test it.
  2. The handler talks to the database only through the repository port. It has no idea what’s behind it.
  3. The actual decision — can this invite happen? — lives in the core, in language a product person would recognise. Not in the controller, not in the database.