feat(events): durable inbox/outbox via Wolverine EF Core#192
Merged
Conversation
Replaces in-process Wolverine with durable messaging backed by the
configured database (SQLite, PostgreSQL, or SQL Server). Adds:
- WolverineFx.{Sqlite,Postgresql,SqlServer,EntityFrameworkCore} packages
- IEvent now requires EventId + OccurredAt; new DomainEvent record base
supplies both via Guid.CreateVersion7() / DateTimeOffset.UtcNow. All
25 event records migrated to inherit from DomainEvent
- DomainEventInterceptor deleted; replaced by Wolverine's
PublishDomainEventsFromEntityFrameworkCore<IHasDomainEvents>(x => x.Events)
- WolverineConfiguration.Configure() helper shared by web host and worker
- Auto-discovery of every module assembly for handler scanning; per-module
WolverineExtension shims removed
- 9 service-level publishes migrated to atomic outbox via
IDbContextOutbox<T>.SaveChangesAndFlushMessagesAsync (Tenants: Update,
ChangeStatus, AddHost, RemoveHost; Email templates: Update, Delete;
SendEmailJob; RetryFailedEmailsJob)
- FakeDbContextOutbox<T> test helper for unit-testing outbox-using services
- Test factory uses per-process temp SQLite file for Wolverine durability
(in-memory SQLite is rejected by Wolverine.Sqlite)
- CONSTITUTION.md documents the new guarantees and the small carve-out for
publishes that stay on bus.PublishAsync (DB-generated IDs, UserManager,
SettingsService DI cycle)
- New tests: WolverineAssemblyDiscoveryTests, WolverineDurabilitySmokeTests,
DomainEventScrapingTests; legacy DomainEventInterceptorTests removed
Deploying simplemodule-website with
|
| Latest commit: |
bfc7928
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://4d6ebebc.simplemodule-website.pages.dev |
| Branch Preview URL: | https://feature-event-system-7v91n.simplemodule-website.pages.dev |
Adds three end-to-end integration tests in SimpleModule.Core.Tests that exercise the durable event pipeline through the real host: - PublishAsync_Routes_Event_Through_Durable_Wolverine_Pipeline: publishes SettingChangedEvent via IMessageBus, uses Wolverine's TrackedSession to wait for processing, asserts wolverine_outgoing/ incoming/dead_letters tables exist on disk. - AuditConfigCacheInvalidatorHandler_Runs_For_SettingChangedEvent: seeds the audit-config cache, publishes a SettingChangedEvent for an "auditlogs.*" key, and asserts the cache entry was drained by the handler — proves a real handler executes end-to-end. - PublishedEvent_Survives_The_Durable_Pipeline_End_To_End: real HTTP PUT /api/settings against an authenticated client; the ASP.NET -> service -> Wolverine outbox path returns 2xx. Also fixes a real pre-existing bug surfaced by this verification: AuditConfigCacheInvalidator was never being discovered as a Wolverine handler because its class name didn't end in a recognized convention suffix (Handler/Consumer/Subscriber). Renamed to AuditConfigCacheInvalidatorHandler so default discovery picks it up and auditlogs.* setting changes now actually invalidate the request config cache as intended. Exposes SimpleModuleWebApplicationFactory.WolverineDbPath publicly so tests can query the durability tables directly.
xUnit treats any exception from a fixture's Dispose/DisposeAsync as a test-pipeline failure that fails the process even when every assertion passes. On Linux CI runners Wolverine's host shutdown intermittently throws during cleanup because the durability tables/connection are torn down out from under its polling agents — exit code 1 despite "Passed!" for every test project. Wrap base.Dispose / base.DisposeAsync and the kept-alive SqliteConnection disposal in try/catch on the test factory, and add the DisposeAsync override (xUnit.v3 prefers async disposal) so the shutdown race is silenced uniformly across local + CI.
PublishAsync is fire-and-forget; the assertion checking the handler's side effect raced the in-memory dispatcher and failed on Linux CI (passed on macOS where the dispatcher returned faster). Wrap the publish in Wolverine.TrackActivity so the assertion only runs after the handler has actually executed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Replaces the in-process Wolverine event bus with a durable inbox/outbox implementation backed by the configured database (SQLite, PostgreSQL, or SQL Server). Closes the gap where events could be lost on a crash between publish and dispatch, and gives handlers at-most-once delivery via the inbox dedup.
WolverineFx.{Sqlite,Postgresql,SqlServer,EntityFrameworkCore}packages added (v5.31.0)WolverineConfiguration.Configure(...)helper shared between web host and worker — single source of truth for provider switch, EF transactional middleware, durable queue/inbox policies, and entity-event scrapingIEventnow requiresEventId(Guid.CreateVersion7()) andOccurredAt; newDomainEventrecord base supplies both. All 25 event records migratedDomainEventInterceptordeleted, replaced byPublishDomainEventsFromEntityFrameworkCore<IHasDomainEvents>(x => x.Events)IDbContextOutbox<T>.SaveChangesAndFlushMessagesAsync—TenantService(Update, ChangeStatus, AddHost, RemoveHost),EmailService.Templates(Update, Delete),SendEmailJob(Sent, Failed),RetryFailedEmailsJobbus.PublishAsyncwith documented carve-outs — events depending on DB-generated IDs, UserManager-driven flows, andSettingsService(DI cycle withAuditingMessageBus)WolverineExtensionshims removedFakeDbContextOutbox<T>helper inSimpleModule.Tests.Shared/Fakesfor unit-testing outbox-using servicesSimpleModuleWebApplicationFactoryuses a per-process temp SQLite file for Wolverine durability (Wolverine.Sqlite rejects in-memory)CONSTITUTION.mdEvents section rewritten to describe the new guarantees and the remaining carve-outsVerification
dotnet run --project template/SimpleModule.Host; Wolverine creates the message-store schema (wolverine_outgoing_envelopes,wolverine_incoming_envelopes,wolverine_dead_letters, etc.) inapp.dbon startupWolverineAssemblyDiscoveryTests,WolverineDurabilitySmokeTests,DomainEventScrapingTestsDomainEventInterceptorTestsremoved (replaced by the scraping integration test)npm run check,npm run build,dotnet build,dotnet test --no-build(938 tests across 17 projects)npm run test:smokefails locally at auth setup; verified againstmain(no event-system changes) — fails identically there. Pre-existing macOS-only environment issue (port 5000 held by ControlCenter; smoke tests assume a CI environment). Should pass on the GitHub Actions runner.Test plan
WolverineConfiguration.Configureand the publisher migrations inTenantService/EmailService.Templates/ Email jobsCONSTITUTION.mdmatch the survivingbus.PublishAsynccall sites