Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/dedup-sametx-and-kafkaheaderutils-316-323"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Two independent, additive public-API features for
im2be-platform-libs/processed-kafka-events, one commit each. No consumer service is changed —adoption of both is a separate later phase.
Commit 1 —
feat(dedup): add tryClaimSameTx at-least-once claim variant (#316)Platform foundation for #316 (at-least-once dedup). Adds
DedupGuard.tryClaimSameTx(...)as an additive sibling of the existingtryClaim(...), with the same two-overload (explicit-scope + no-scope) →shared private
tryClaimInternalcore split, but@Transactional(propagation = MANDATORY)instead ofREQUIRES_NEW:tryClaim(REQUIRES_NEW)tryClaimSameTx(MANDATORY)IllegalTransactionStateExceptionif noneMANDATORY(notREQUIRED) is deliberate: asserts the consumer istransactional and fails fast rather than silently degrading to at-most-once —
mirrors
OutboxPublisher.publish(...)fail-fast (ADR-0014 D-7). Justified inthe Javadoc.
@Transactionalproxy entry — the core is never self-invoked.tryClaimSameTxcallers MUST have a DLT +retry-limit, or a poison message redelivers forever.
tryClaimbehaviour + Javadoc are unchanged.Consumer adoption TBD per-service (incl. diary) — not in this PR.
Commit 2 —
feat(kafka): extract shared KafkaHeaderUtils.resolveEventId (#323)Platform foundation for #323 (shared header-parsing util). Extracts the
resolveEventIdhelper that all five consumer services (user / family /notification / calendar / diary) duplicated privately in their
KafkaConsumerServiceinto a single shared public util,KafkaHeaderUtils.resolveEventId(ConsumerRecord<?,?>)inprocessed-kafka-events(dedup-adjacent — the resolved id feeds theDedupGuardclaim).Canonical behaviour: read the
event_idheader (lastHeader, decoded UTF-8,trimmed; non-blank wins) else fall back to
topic:partition:offset.Divergence audit: user/family/notification/calendar copies are
byte-identical; diary differs only cosmetically (a
StringBuilderfallback4-service plain-concatenation form is canonical.
Consumer adoption of the shared util TBD — not in this PR.
Test plan
mvn -B clean install→ BUILD SUCCESS, all modules greenDedupGuardTest):tryClaimSameTxshares the core (insert=1 →CLAIMED, insert=0 → DUPLICATE), blank-scope rejected
DedupGuardIT, Testcontainers PG): (a) same-tx claimvisible/durable on commit, (b) caller-tx rollback → claim NOT persisted →
redelivery re-claims (the at-least-once property), (c) no active tx →
IllegalTransactionStateExceptionKafkaHeaderUtilsTest, 7): header present → returned;whitespace → trimmed; multiple → last wins; absent / null / empty / blank
→
topic:partition:offsetfallbackim2be-platform-libstouched; specificfiles staged
R0 feat findings (kept=0): Adds DedupGuard.tryClaimSameTx(...) as an additive sibling of tryClaim(...). Same two-overload (explicit-scope + no-scope) -> shared private tryClaimInternal core split, but @Transactional(propagation = MANDATORY) instead of REQUIRES_NEW. The claim JOINS the caller's transaction, so a business-logic failure rolls the claim back and the event is redelivered -> at-least-once (vs tryClaim's separate REQUIRES_NEW tx -> at-most-once-on-failure, no poison loop). Existing tryClaim behaviour + Javadoc are unchanged. (1) feat processed-kafka-events/.../DedupGuard.java -- tryClaimSameTx overloads. Both overloads carry @Transactional(MANDATORY) and delegate to the existing tryClaimInternal core (no self-invocation -> proxy is not bypassed). MANDATORY (not REQUIRED) is deliberate: it fails fast with IllegalTransactionStateException when no caller tx exists, asserting the consumer is transactional rather than silently degrading to at-most-once -- mirrors OutboxPublisher.publish() fail-fast (ADR-0014 D-7). Javadoc contrasts the two variants in a table + warns that tryClaimSameTx callers MUST have a DLT + retry-limit or a poison message redelivers forever. Fix: factored both new overloads onto the existing shared core; no logic duplicated, no existing public behaviour changed. Verified MANDATORY semantics (joins caller tx; IllegalTransactionStateException when none) against current Spring Framework transaction-propagation docs. Verification: - mvn -B clean install -> BUILD SUCCESS; processed-kafka-events surefire+failsafe green - DedupGuardTest unit tests: tryClaimSameTx shares core (insert=1 -> CLAIMED, insert=0 -> DUPLICATE), blank-scope rejected - DedupGuardIT (Testcontainers PG): (a) same-tx claim visible/durable on commit, (b) caller-tx rollback -> claim NOT persisted -> redelivery re-claims (at-least-once), (c) no active tx -> IllegalTransactionStateException, nothing writtenR0 feat findings (kept=0): Extracts the canonical resolveEventId helper that all five consumer services (user / family / notification / calendar / diary) duplicated privately in their KafkaConsumerService into a single shared public util in processed-kafka-events (dedup-adjacent: the resolved id feeds DedupGuard.tryClaim/tryClaimSameTx, and guard consumers already depend on this module). No consumer service is modified -- adoption is a separate phase. (1) feat processed-kafka-events/.../KafkaHeaderUtils.java -- new final utility class. resolveEventId(ConsumerRecord<?,?>): read the 'event_id' header via headers().lastHeader (last value wins), decode UTF-8, trim; return if non-blank, else fall back to topic:partition:offset. HEADER_EVENT_ID = "event_id" constant matches the consumers. Wildcard-bound record type (key/value irrelevant to id). Canonical form / divergence audit: user/family/notification/calendar copies are byte-identical (header-or-coordinates, UTF-8, trim, !isBlank). diary differs ONLY cosmetically (StringBuilder fallback + fuller Javadoc) -- identical output, no behavioural divergence; the 4-service plain-concatenation form is canonical. Fix: single extraction, no consumer touched, additive public API. Verification: - mvn -B clean install -> BUILD SUCCESS - KafkaHeaderUtilsTest (7): header present -> returned; surrounding whitespace -> trimmed; multiple headers -> last wins; absent / null-value / empty / blank -> topic:partition:offset fallback. ConsumerRecord 11-arg constructor verified against kafka-clients 3.9.2 (javap)hib-pr-reviewer review — PR #14 (affinity-intelligence-rework/im2be-platform-libs)
Round 1 — head
c113dfa60d9c, basemain, triggeropenedTL;DR: NO_NEW_FINDINGS — No new findings this round.
Summary
[quorum-converged] A=0 = B=0. ## Review Summary
The
tryClaimSameTx(#316) andKafkaHeaderUtils(#323) additions are well-architected and correctly implemented. The propagation-agnostictryClaimInternalcore, the MANDATORY-not-REQUIRED rationale, the poison-message warning in both README and Javadoc, and the three new IT scenarios (same-tx visibility, rollback, no-tx fail-fast) all meet the standard set by the existingtryClaimwork.KafkaHeaderUtilsis stateless, final, and its test matrix is complete across all seven header-presence cases.Two minor actionable findings below; no blocking or correctness regressions.
CI status (head
c113dfa60d9c)Overall: ✓ success
2 checks: 2 pending
Findings
No new findings this round.
Quorum converged on empty findings (A + B both returned 0).
Verdict
NO_NEW_FINDINGS
hib-pr-reviewer • round 1 • 0 findings • 2026-05-29T15:58:38.296Z → 2026-05-29T16:03:55.341Z • posted-as: pr-reviewer-bot • [bookkeeping fallback]