Connecting Runtimes¶
Your contexts are separate, but they need to talk. Deepstaging gives you two event systems — one for "something happened inside this context" and one for "the outside world needs to know."
EventQueue vs IntegrationEvents¶
| EventQueue | IntegrationEvents | |
|---|---|---|
| Scope | Within one context | Across contexts |
| Transport | In-process channel | In-process (dev) or Service Bus (prod) |
| Durability | Lost on process restart | Durable (with real transport) |
| Handler discovery | Same assembly | Any subscribing assembly |
| Use when | Reacting to internal state changes | Notifying other contexts |
In the Library example: LendingDomainEvents (EventQueue) handles patron loan count updates. LendingIntegrationEvents (IntegrationEvents) tells Catalog about inventory changes. Same BorrowBook command publishes to both.
Topic Classes¶
Integration events live in a shared Contracts project. Each publishing context gets one topic class:
[IntegrationEventTopic]
public sealed class LendingEvents
{
[IntegrationEvent("lending.book-borrowed")]
public sealed record BookBorrowed(BookId BookId, PatronId PatronId);
[IntegrationEvent("lending.book-returned")]
public sealed record BookReturned(BookId BookId, PatronId PatronId);
}
The string in [IntegrationEvent("...")] is the wire identity — it's what gets serialized on the transport. Pick a naming convention (context.event-name) and stick with it. Changing this string is a breaking change for any subscriber.
Publishing¶
The publishing context declares [IntegrationEvents(typeof(LendingEvents))] and calls Enqueue:
>> LendingIntegrationEvents.Enqueue<LendingRuntime>(new LendingEvents.BookBorrowed(bookId, patronId))
Subscribing¶
The subscribing context also declares [IntegrationEvents(typeof(LendingEvents))] and adds a handler class:
[IntegrationEventHandler<CatalogRuntime>(typeof(LendingEvents))]
public static partial class CatalogLendingEventHandlers
{
public static Eff<CatalogRuntime, Unit> Handle(LendingEvents.BookBorrowed evt) =>
CatalogDispatch.UpdateInventory(evt.BookId, -1).AsUnit();
}
The handler delegates to the dispatch layer — it doesn't touch the store directly. Same code path, same validation, same effects pipeline.
Common Mistakes¶
- Forgetting
[IntegrationEvent("...")]on the event record. Without the string identity, the event can't be serialized or routed. - Putting handlers in the wrong assembly. The handler class must be in the subscribing context's project, not the publisher's.
- Sharing command types across contexts. Commands are internal to a context. If Catalog needs to react to a Lending event, use an integration event handler — don't import Lending's command types.