← Components

A supervision tree shaped like the domain.

The daemon is not a bag of goroutines sharing locks. Each domain owns its state behind an actor, and the tree that supervises them is named after the business, not the transport.

Why actors at all

A daemon that handles inbound streams, retries deliveries, commits consensus entries, and serves local gRPC has plenty of ways to race. The usual answer is mutexes around shared structures, which spreads the invariants of one concept across every function that touches it.

The actor model inverts that. Each piece of mutable state has exactly one owner, and the only way to change it is to send that owner a message. Concurrency stops being something every call site has to remember and becomes a property of the structure.

Where domain-driven design comes in

The packages are named after the domain rather than the machinery: thread, tasks, inbox, outbox, registry, names, deliver. Each is a bounded context that owns its own vocabulary and its own persistence, and none reaches into another's tables.

Those contexts map one to one onto the supervision tree. Every domain package exposes the same EnableActor shape, which spawns that domain's actor under a shared hierarchy. The result is that the runtime topology is a picture of the domain model, not of the framework.

A thread is an aggregate: it has an identity, its own invariants, and a boundary nothing writes through except its actor. The actor is what makes that boundary real at runtime instead of merely intended.

Aggregates and consistency

Threads are the clearest case. A thread's log must stay hash-linked and correctly ordered, which is an invariant that only holds if writes to that thread are serialised. Giving each thread an actor means that serialisation is structural: there is no code path that can append to a thread except by going through the one actor that owns it.

Supervision

Actors are spawned under a single root supervisor with domain containers beneath it, and children are registered by logical name so the same domain actor is never started twice. Stopping the root stops the whole application tree rather than leaving orphaned workers behind, which makes shutdown and inspection a single operation instead of a checklist.

What you get out of it

  • Locality of reasoning. To understand how thread state changes, read the thread actor. Not every caller of it.
  • Backpressure and isolation. A slow subscriber or a stalled peer affects its own mailbox, not the daemon's network loop.
  • A model that survives growth. New domain concepts arrive as new bounded contexts with their own actors, rather than as new fields on something shared.