Effects Composition¶
This page covers the Eff<RT, T> effect layer generated by the Event Store module — capability interfaces, effect methods, stream extensions, runtime integration, and OpenTelemetry instrumentation.
Capability Interface¶
A single capability interface is generated per event store:
public interface IHasAppEventStore : IAutoCommittable
{
IOrderEventStore OrderEventStore { get; }
// ... one property per aggregate
}
Use as a runtime constraint for effect methods. The capability interface itself extends IAutoCommittable for transactional commit across all aggregate stores.
Effect Methods¶
Effect methods are generated as nested static classes on the event store container:
public static partial class AppEventStore
{
// Commit all pending changes across all stores
public static Eff<RT, Unit> Commit<RT>()
where RT : IHasAppEventStore => ...
public static partial class Orders
{
public static Eff<RT, IEventStream<Order>> FetchForWriting<RT>(OrderId id)
where RT : IHasAppEventStore => ...
public static Eff<RT, Option<Order>> Aggregate<RT>(OrderId id)
where RT : IHasAppEventStore => ...
public static Eff<RT, Unit> StartStream<RT>(OrderId id, object @event)
where RT : IHasAppEventStore => ...
public static Eff<RT, OrderId> StartStream<RT>(object @event)
where RT : IHasAppEventStore => ...
public static Eff<RT, Unit> Append<RT>(OrderId id, object @event)
where RT : IHasAppEventStore => ...
public static Eff<RT, IReadOnlyList<EventEnvelope>> FetchStream<RT>(OrderId id)
where RT : IHasAppEventStore => ...
}
}
Nullable → Option
Effect methods wrap nullable results in Option<T>. AggregateAsync returns Task<Order?> on the interface but Eff<RT, Option<Order>> in effects.
Stream Extensions¶
The generator also produces extension methods for fluent event appending in Eff pipelines:
public static class AppEventStoreStreamExtensions
{
public static Eff<RT, Unit> Append<RT>(
this IEventStream<Order> stream, object @event)
where RT : IHasAppEventStore { ... }
public static Eff<RT, Unit> AppendMany<RT>(
this IEventStream<Order> stream, IEnumerable<object> events)
where RT : IHasAppEventStore { ... }
}
Use with FetchForWriting for the fetch-append-commit pattern:
var program =
from stream in AppEventStore.Orders.FetchForWriting<AppRuntime>(orderId)
from _ in stream.Append<AppRuntime>(new OrderItemAdded("SKU-001", 3))
>> AppEventStore.Commit<AppRuntime>()
select unit;
Usage in Handlers¶
public static class OrderCommands
{
[CommandHandler]
public static Eff<AppRuntime, OrderCreated> Handle(CreateOrder cmd) =>
from id in AppEventStore.Orders.StartStream<AppRuntime>(
new OrderCreated(OrderId.New(), cmd.CustomerName))
select new OrderCreated(id);
[CommandHandler]
public static Eff<AppRuntime, Unit> Handle(AddOrderItem cmd) =>
from stream in AppEventStore.Orders.FetchForWriting<AppRuntime>(cmd.OrderId)
from _ in stream.Append<AppRuntime>(new OrderItemAdded(cmd.Sku, cmd.Quantity))
select unit;
}
Auto-commit is handled by the Dispatch module — CommitAsync() is called automatically after each command handler.
Using with Runtime¶
Declare the event store on your runtime with [Uses]:
The runtime bootstrapper registers all event stores and configures OpenTelemetry tracing automatically.
OpenTelemetry Instrumentation¶
When Instrumented = true (the default on [EventStore]), each effect method is wrapped with .WithActivity():
- Creates an
Activityspan fromActivitySource("{Namespace}.{StoreName}", "1.0.0") - Span names include the aggregate and operation (e.g.,
Orders.FetchForWriting,Orders.Append) - The bootstrapper registers the activity source with the OpenTelemetry tracing pipeline
Set Instrumented = false to skip instrumentation:
See Generated Code for the plain .NET interfaces, or Attributes for the full attribute reference.