feat(eventbus): add idempotent consumer dedup primitive - #203
Merged
Conversation
Add a reusable consumer-side dedup toolkit so at-least-once consumers process an event exactly once. ProcessedEventStore keys dedup by (envelope id, consumer group); IdempotentEventProcessor claims first then runs the handler, propagating handler exceptions so the caller's transaction rolls back the claim and the event is retried. JdbcProcessedEventStore claims via INSERT ON CONFLICT DO NOTHING and participates in the ambient transaction; InMemoryProcessedEventStore serves tests and local dev. The store is wired by the consuming service, never auto-defaulted to in-memory in production. Covered by unit tests plus a Testcontainers-Postgres integration test for dedup, transactional rollback of the claim, and concurrent first-seen. Closes #192
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.
What
Consumer-side counterpart to the at-least-once producer/dispatcher: a reusable dedup primitive so consumers process each envelope exactly once. No consumer service exists yet, so this ships the generic, tested toolkit in
libs/fincore-eventbus; the per-serviceprocessed_eventsmigration lands with the first real consumer (E-02).ProcessedEventStore.markIfFirstSeen(envelopeId, consumerGroup)- dedup keyed by(envelope id, consumer group)so independent consumers each process an event once.IdempotentEventProcessor.process(envelopeId, consumerGroup, handler)- claim-then-handle: a thrown handler propagates so the caller's transaction rolls back the claim and the event is retried (exactly-once effect, never silent loss or double effect).JdbcProcessedEventStore-INSERT ... ON CONFLICT DO NOTHING, transaction-participating;InMemoryProcessedEventStorefor tests/dev. The store is wired by the consuming service - never auto-defaulted to in-memory in production (that would silently reprocess after a restart).processed-events.sqlDDL (PK(envelope_id, consumer_group)) for adopting services.Security
tableNameis the only interpolated SQL token (JDBC can't bind identifiers); it is validated at construction against^[A-Za-z_][A-Za-z0-9_]*$. All values are bound parameters.Gates
Local (
-x integrationTest, Docker down in WSL)::libs:fincore-eventbus:{test,detekt,detektTest,spotlessCheck,assemble,compileIntegrationTestKotlin}green. The Testcontainers-Postgres IT runs on CI: dedup, claim rolled back when the surrounding transaction rolls back (asserted in a separate transaction), and two concurrent transactions where exactly one wins first-seen. critic GO, code-reviewer APPROVE (2 must-fixes applied), security-auditor PASS, evaluator 0.89.spring-jdbcadded BOM-managed.Closes #192