Repository navigation
Fix(eventing)/outbox with dedicated db - #1434
HiroshiYoshino wants to merge 2 commits into
Conversation
Framework-owned Outbox rows already follow a tenant's dedicated database, but event dispatch rebuilds the ambient tenant from its ID alone. This loses connection metadata and can write consumer effects and Inbox rows to default while completing the correct source Outbox. Add a same-tenant Finbuckle scope diagnostic and a public-dispatch PostgreSQL regression using default plus one dedicated database. Keep all 13 assertions for persisted business rows, per-handler Inbox placement, source completion, tenant ownership, and unchanged redelivery snapshots. Retain the supplemental same-tenant, cross-tenant, and global routing test. Production remains unchanged; the two primary regressions intentionally fail on the unfixed baseline. Supplemental test host ownership is corrected in the following fix. Red checkpoint: 12 nearby existing tests passed and the two primary regressions failed for the expected metadata/routing mismatch, with no skips. The original execution checkpoint is retained in backup history.
…mers An Outbox drain target selects the source database, not the destination for every event it contains. A root renewal can originate in default while its tenant-aware consumers and Inbox belong in a dedicated database. Preserve this separation instead of inheriting the source connection or reconstructing an ID-only tenant for dispatch. Add ExecuteAsync with a default synchronous bridge. The Finbuckle implementation resolves full tenant metadata from the default catalog through a short-lived scoped resolver, disposes the lookup scope, and runs consumer construction inside the same async callback. Restore the previous ambient context on success, error, and cancellation. Keep source claim, completion, retry, and dead-letter updates in the original database. Explicitly reset default drain context. Stop Webhook fanout from overwriting resolved metadata; keep central Billing business data in default while its framework Inbox follows the event tenant. Preserve Begin and the original two-argument constructor. Same-ID Begin retains full ambient metadata; different-ID Begin remains legacy ID-only. Global async dispatch uses minimal root/default context and preserves a null event TenantId in Inbox. Reject unknown and inactive tenants before consumer construction; inactive-only dedicated databases are still not automatically drained. Cover real Finbuckle async propagation, construction order, cancellation, isolation, two-database routing, Webhook subscriptions, root HTTP renewal, and redelivery after source completion failure. Replace the supplemental test's extra host with a lightweight provider to avoid disposing shared Hangfire storage. Production Hangfire and its shared factory are unchanged. Update the internal eventing and Webhook guidance. The original Green tree passed all 2,067 solution tests (no failures or skips), including 797 integration and 55 architecture tests; the full build had no warnings or errors. An isolated ID-only mutation failed the primary regression and passed again after restoration. The earlier history cleanup removed only an unrelated .gitignore entry; validated production, test, and guidance files remain unchanged. No distributed transaction, exactly-once guarantee, schema migration, or automatic repair of previously misrouted data is introduced. External docs publication and deployment/data-recovery review remain separate.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 549931f37f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| /// The default bridge uses <see cref="Begin"/>; multitenancy implementations can resolve | ||
| /// full catalog metadata and define global routing before invoking the action. | ||
| /// </summary> | ||
| async Task ExecuteAsync( |
There was a problem hiding this comment.
Obtain approval before changing BuildingBlocks
When no explicit approval accompanies this change, do not land the new shared interface method: the repository's AGENTS.md marks all of src/BuildingBlocks as protected and prohibits modifications without explicit approval, while this commit changes both the public eventing abstraction and the framework event bus. Record the approval or move the behavior behind a module-owned adapter before merging.
AGENTS.md reference: AGENTS.md:L82-L82
Useful? React with 👍 / 👎.
Related Issue:fullstackhero/dotnet-starter-kit/issues#1427
Related Docs:fullstackhero/docs/pull#268
Problem
The system stores events in an Outbox so that follow-up work can run in the background after a user operation. Preparing Webhook notifications after a file upload is one example. Some tenants use a dedicated database rather than the shared default database.
The problem is that an event can be stored in the correct database while its follow-up processing connects to the wrong one. Before invoking consumers, the system rebuilt the tenant context using only the tenant ID. This discarded the dedicated-database connection information and caused consumers to use default instead. Webhook subscriptions in the dedicated database could therefore be missed, and Inbox records used to track completed processing could be written to the wrong database.
The database holding an event is not necessarily the database its consumers should use.
Simply reusing the Outbox database for all follow-up processing would therefore not solve the problem.
Fix
Resolve the event tenant’s complete information before constructing consumers. The implementation looks up the tenant in the default database’s tenant catalog, including its dedicated connection information, and only then creates handlers and DbContexts.
This ordering matters because DbContexts select their database at construction. Setting tenant information afterwards cannot repair an already selected connection.
IEventTenantScope.ExecuteAsyncprovides the flow for resolution and consumer execution, restoring the previous context after success, failure, or cancellation. Webhook fanout also stops replacing the resolved context with an ID-only context.Completion and retry updates remain in the original Outbox database, while consumers and Inbox use the appropriate destination for the event tenant. The existing synchronous API and central Billing storage remain unchanged.
Created 1 todo
Tests
dotnet test src/FSH.Starter.slnxpassed all 2,026 tests. A separate rebuild ofIntegration.Testsalso succeeded with zero warnings and errors.Webhook tests cover subscription lookup and fanout, not successful delivery to an external HTTP endpoint.
Behaviour Change and Limits
Consumers and Inbox now follow the resolved event tenant. Claim, completion, retry, and dead-letter updates remain in the source Outbox database; central Billing storage is unchanged. Custom buses and tenant scopes should verify that full context resolution precedes consumer construction.
No schema migration or new configuration is required. This change does not automatically repair historical misrouting, introduce distributed transactions, or guarantee exactly-once processing. Dedicated databases used exclusively by inactive or deleted tenants remain excluded from automatic drains and require operator review. The companion docs PR includes the reference updates and an
Unreleasedchangelog entry, intended to be integrated alongside the backend change.