Streams and subscriptions
Troolio uses Orleans streams and broadcast channels to move event envelopes toward projection and orchestration consumers. Subscription attributes connect a consumer to events from an actor type or name; they are routing declarations, not a durable completion receipt.
On this page
Projection subscriptions
ProjectionStreamSubscriptionAttribute matches a projection stream namespace derived from an actor type or actor name. Projection actors resume stream subscriptions and process IEventEnvelope messages. The source actor's append can complete before the read model reflects that event; design callers for eventual consistency.
Orchestration subscriptions
OrchestrationStreamSubscriptionAttribute serves orchestration listeners. An orchestration handler may issue internal commands that produce more events. A broadcast publish alone does not prove that every expected handler has finished, that nested work is registered, or that an event-only transaction is closed.
When atomic closure is required
Selected event-only routes require a reviewed subscriber manifest and a durable closure protocol. The manifest must account for the deployed binding inventory and handler effect classification. Completion receipts are recorded only after expected handlers and child work finish. Read orchestration and selected command route before using those surfaces.
Host responsibilities
Configure stream providers and subscription lifecycle in the host. Monitor consumer lag and recover pending durable handoffs. Keep a stable deployment/binding version during a selected route, and reject drift before commit. Do not use stream delivery as a substitute for store transaction guarantees.
Example: Subscribe a projection
This projection subscribes to streams produced by CounterActor. The application writer must apply the event version idempotently and handle retries.
public interface ICounterProjectionWriter
{
Task ApplyAsync(string actorId, ulong version);
}
[ProjectionStreamSubscription(typeof(CounterActor))]
public sealed class CounterProjection(ICounterProjectionWriter writer) : ProjectionActor
{
public Task On(EventEnvelope<Incremented> envelope) =>
writer.ApplyAsync(envelope.Id, envelope.Version);
}