Skip to main content

Examples

Runnable projects in the examples/ directory demonstrate Synapse features in context. Each project builds and runs stand-alone from the repo root.

Overview

ProjectTypeWhat it demonstrates
GettingStartedConsole (step-by-step tutorial)Commands, queries, events, streaming, pipelines, context, error handling, validation — organized as Steps 1–7
MinimalApiASP.NET Core + Native AOTCanonical integration tour: all core features + pipeline ordering + CQRS enforcement + cross-assembly composition
MinimalApi.Modules.ContractsClass library (shared contract)Shared IEvent contract — the decoupling seam between Orders and Notifications
MinimalApi.Modules.OrdersClass library (module)Emitter module using [RequestHandler<>] + source-generated RegisterGroup; publishes OrderPlacedEvent
MinimalApi.Modules.NotificationsClass library (module)Subscriber module using manual cfg.RegisterEventHandler<>() — no source generator

Running an example

# Console tutorial
cd examples/GettingStarted && dotnet run

# ASP.NET Core (full feature tour)
cd examples/MinimalApi && dotnet run

All examples reference Synapse via local project references, so they always build against the current source.


Modular monolith: decoupled cross-assembly events

The MinimalApi.Modules.* projects demonstrate a modular-monolith pattern where two feature modules communicate through domain events without knowing about each other.

Topology

Neither Orders nor Notifications references the other. Both depend only on Contracts which holds OrderPlacedEvent : IEvent.

Registration: source-gen vs. manual — side by side

The two modules use different registration styles to highlight that both are first-class options.

Orders (source generator)

// In MinimalApi.Modules.Orders — handler carries the attribute
[RequestHandler<PlaceOrderCommand, Guid>]
public sealed class PlaceOrderCommandHandler : IRequestHandler<PlaceOrderCommand, Guid>
{
public async ValueTask<Result<Guid>> HandleAsync(PlaceOrderCommand request, CancellationToken ct)
{
var orderId = _store.Place(request.Product);
await _emitter.EmitAsync(new OrderPlacedEvent { OrderId = orderId, ... }, ct);
return Result.Success(orderId);
}
}

// In Program.cs (host) — one call wires everything the generator discovered
cfg.AddRegisterGroup(new global::UnambitiousFx.Examples.MinimalApi.Modules.Orders.RegisterGroup());

Notifications (manual registration)

// In MinimalApi.Modules.Notifications — NO [EventHandler<T>] attribute
public sealed class OrderPlacedNotificationHandler : IEventHandler<OrderPlacedEvent>
{
public ValueTask<Result> HandleAsync(OrderPlacedEvent @event, CancellationToken ct)
{
_log.Add(new NotificationEntry { OrderId = @event.OrderId, ... });
return ValueTask.FromResult(Result.Success());
}
}

// In NotificationsModule.cs — explicit extension method the host calls
public static ISynapseConfig AddNotificationsHandlers(this ISynapseConfig cfg)
{
// Registers the DI service AND the AOT-safe dispatch delegate in one call.
cfg.RegisterEventHandler<OrderPlacedNotificationHandler, OrderPlacedEvent>();
return cfg;
}

// In Program.cs (host)
cfg.AddNotificationsHandlers();

Event flow

POST /orders { "product": "Widget", "quantity": 2 }
└─ PlaceOrderCommandHandler [Orders module]
└─ IEmitter.EmitAsync(new OrderPlacedEvent { ... })
└─ OrderPlacedNotificationHandler [Notifications module]
└─ NotificationLog.Add(entry)

GET /notifications
└─ Returns [ { orderId, product, quantity, receivedAt }, ... ]

Key files

FileRole
MinimalApi.Modules.Contracts/OrderPlacedEvent.csShared IEvent record
MinimalApi.Modules.Orders/Handlers/PlaceOrderCommandHandler.csSource-gen handler, emits event
MinimalApi.Modules.Orders/OrdersModule.csAddOrdersModule() + MapOrdersEndpoints()
MinimalApi.Modules.Notifications/Handlers/OrderPlacedNotificationHandler.csManual handler, no attribute
MinimalApi.Modules.Notifications/NotificationsModule.csAddNotificationsHandlers(ISynapseConfig) + endpoint
MinimalApi/Http/modules.httpHTTP test file — place order, then verify notification
tip

Open examples/MinimalApi/Http/modules.http in your IDE's HTTP client, run the requests top-to-bottom, and observe the cross-module log lines in the terminal to confirm the event was delivered.