Documentation / Transactions

Orchestration

An orchestration turns an event into another domain action. The source actor decides what happened; the listener chooses the next actor to contact; that destination actor validates the next command against its own state.

On this page

For example, a registration actor produces RegistrationRequested. A listener asks the workshop roster actor to record that registration. The same event can also feed a projection, but the projection's job is to update a reader's view.

Follow the business relationship

Start with three questions: which event triggers the workflow, which actor owns the next decision, and which business key identifies that work? In the workshop example, the event carries both registration ID and workshop ID. Those values make the destination explicit and let retries be recognized.

[OrchestrationStreamSubscription(typeof(RegistrationActor))]
public sealed class WorkshopEnrolmentOrchestration : OrchestrationActor
{
    public Task On(EventEnvelope<RegistrationRequested> envelope) =>
        System.ActorOf<IWorkshopRosterActor>(envelope.Event.WorkshopId.ToString("D"))
            .Tell(new AddAttendee(envelope.Event.Headers, envelope.Event.WorkshopId,
                envelope.Event.RegistrationId, envelope.Event.AttendeeId));

    public Task On(EventEnvelope<RegistrationCancelled> envelope) =>
        System.ActorOf<IWorkshopRosterActor>(envelope.Event.WorkshopId.ToString("D"))
            .Tell(new RemoveAttendee(envelope.Event.Headers, envelope.Event.WorkshopId,
                envelope.Event.RegistrationId));
}

The subscription selects events from RegistrationActor. The typed On methods handle request and cancellation separately. Returning the Tell task makes command dispatch part of the listener's observable work. The headers are forwarded for the ordinary internal-command protocol; metadata explains how Troolio creates the next message and causation context.

Keep state restoration separate

The registration actor's On method applies an event to its own in-memory state. It must be safe to call while replaying history. Sending the roster command there would repeat the workflow whenever the actor activates.

The orchestration listener is a separate role and receives its configured event delivery. See replay and live events for the boundary.

Ordinary and selected event-only work

An ordinary listener can issue internal commands within Troolio's transaction context. Its attribute and method do not by themselves establish that a store commits every involved stream atomically. Choose the actual route and provider according to the operation's requirements.

A selected event-only route plans a reviewed, closed set of participant events. It needs bounded planning, current route authority, supported handler classes, and a capable store that persists the required events, handoffs, and completion receipt. External effects belong behind durable intents rather than inside that precommit planner.

IEventOnlyOrchestrationStateStore retains the frozen operation state. IEventOnlyOrchestrationDeliveryStore makes expected subscriber work discoverable and claimable after restart. DurableEventOnlySubscriberExecutor validates registered plan handlers and their allowed emissions.

Claiming durable subscriber work

A delivery identity names an operation, source envelope, and expected subscriber. A claim gives a worker a bounded lease; the returned fencing token must guard state changes.

public static Task<EventOnlyOrchestrationDeliveryLease?> Claim(
    IEventOnlyOrchestrationDeliveryStore store, Guid operationId, Guid envelopeId) =>
    store.TryClaimAsync(new(operationId, envelopeId, "roster-planner"),
        "worker-1", TimeSpan.FromMinutes(1));

Claiming does not mean completion. A worker must register child work and participants before completing its receipt, and an old worker must not mutate state after a trusted takeover. The host provides durable store implementations, route admission, worker scheduling, and recovery.

Failure, cancellation, and outside systems

A repeated roster add should not increment a count twice. A cancelled registration should not be resurrected by a late add. The target actor needs durable business identity and lifecycle rules as well as message tracing.

A provider call or human approval introduces a longer-lived workflow. Record pending work and attempts explicitly, use idempotent providers, and record a definitive outcome as another domain command. A detached in-memory task cannot preserve that work through process loss.

For the complete practical treatment, follow orchestration workflows, correlation and causation, and notifications and background work. For selected atomic work, read selected command route, atomic commit, and recovery.