Class RedisOutboxRelay

java.lang.Object
com.aim2be.platform.outbox.redis.RedisOutboxRelay

public class RedisOutboxRelay extends Object
Inline hot relay for the Redis backend.

Why this exists separately from OutboxPublisher. The PG publisher registers its hot relay on a Spring TransactionSynchronization.afterCommit() hook — UNCONDITIONALLY, per the OutboxBackend.requiresActiveTransaction() @apiNote. A transaction-free Redis backend invoked outside a Spring transaction would fail at that relay-registration step with IllegalStateException("Transaction synchronization is not active"). Phase 0b therefore owns its OWN relay: it does NOT route through the PG OutboxPublisher. After the consumer enqueues an entry via RedisOutboxBackend.persistPending(com.aim2be.platform.outbox.OutboxRecord), it calls relay(OutboxRecord) (or relayAsync(OutboxRecord)) to attempt the Kafka send immediately; on failure the RedisOutboxPollerWorker cold path drains it later.

The send is gated by a Resilience4j CircuitBreaker (CIRCUIT_BREAKER_NAME) mirroring the PG publisher's im2be-outbox-relay breaker, so a degraded broker short-circuits the hot path while the cold poller keeps draining PENDING entries.

Delivery is at-least-once (ADR-0014 D-9): a successful Kafka ack before the markSent write that then fails leaves a PENDING entry whose message is already delivered — the next poller tick re-sends it and the consumer-side dedup (processed-kafka-events) absorbs the duplicate.

  • Field Summary

    Fields
    Modifier and Type
    Field
    Description
    static final String
    Resilience4j breaker instance name for the Redis hot relay.
  • Constructor Summary

    Constructors
    Constructor
    Description
    RedisOutboxRelay(OutboxBackend backend, org.springframework.kafka.core.KafkaTemplate<byte[],byte[]> kafkaTemplate, io.github.resilience4j.circuitbreaker.CircuitBreaker circuitBreaker, RedisOutboxMetrics metrics, int maxRetries, long sendTimeoutMs)
     
  • Method Summary

    Modifier and Type
    Method
    Description
    boolean
    Hot-relays one entry SYNCHRONOUSLY: blocks on the Kafka ack (bounded by the caller's producer config), then applies the markSent / markFailureAttempt transition.
    void
    Hot-relays one entry FIRE-AND-FORGET: enqueues the send and reconciles the outcome on the Kafka client thread's whenComplete callback.

    Methods inherited from class java.lang.Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Field Details

    • CIRCUIT_BREAKER_NAME

      public static final String CIRCUIT_BREAKER_NAME
      Resilience4j breaker instance name for the Redis hot relay. Distinct from the PG publisher's im2be-outbox-relay so the two backends' breaker windows never alias when both are (hypothetically) on one classpath.
      See Also:
  • Constructor Details

    • RedisOutboxRelay

      public RedisOutboxRelay(OutboxBackend backend, org.springframework.kafka.core.KafkaTemplate<byte[],byte[]> kafkaTemplate, io.github.resilience4j.circuitbreaker.CircuitBreaker circuitBreaker, RedisOutboxMetrics metrics, int maxRetries, long sendTimeoutMs)
      Parameters:
      backend - the SPI (the Redis backend) for the markSent / markFailureAttempt transitions; never null
      kafkaTemplate - byte-key/byte-value Kafka template; never null
      circuitBreaker - the hot-relay breaker; never null
      metrics - the metrics binder; never null
      maxRetries - retry budget passed to OutboxBackend.markFailureAttempt(java.util.UUID, java.util.UUID, java.lang.String, int) so the hot path uses the same budget as the cold poller
      sendTimeoutMs - bounded wait on the synchronous relay(com.aim2be.platform.outbox.OutboxRecord) Kafka ack (same budget the cold poller's tryColdPublish uses); a stalled broker MUST NOT block the calling thread indefinitely
  • Method Details

    • relay

      public boolean relay(OutboxRecord record)
      Hot-relays one entry SYNCHRONOUSLY: blocks on the Kafka ack (bounded by the caller's producer config), then applies the markSent / markFailureAttempt transition. Returns true on a confirmed SENT transition, false otherwise (breaker open, send failed, or the markSent applied 0 rows because the poller already SENT it).

      Synchronous form is the simplest to reason about for the inline path and is what the ITs exercise; relayAsync(OutboxRecord) is the fire-and-forget variant for latency-sensitive callers.

      Parameters:
      record - the PENDING entry to relay; never null
      Returns:
      true on confirmed SENT, else false
    • relayAsync

      public void relayAsync(OutboxRecord record)
      Hot-relays one entry FIRE-AND-FORGET: enqueues the send and reconciles the outcome on the Kafka client thread's whenComplete callback. Returns immediately; the caller does not block on the broker. Used by latency-sensitive callers (e.g. the per-connect ticket mint) where blocking the request thread on the broker is unacceptable.
      Parameters:
      record - the PENDING entry to relay; never null