Class JdbcDelivery
java.lang.Object
org.j7m.middleware.integrations.JdbcDelivery
JDBC transactional outbox/inbox for H2 and PostgreSQL using application-supplied drivers.
Enqueue and consume methods never commit, roll back, close or replace the caller's connection.
Callers must use one non-autocommit transaction for their application writes and these methods,
and roll back that whole transaction on any exception. Relay owns its short transactions and
sends at least once; there is deliberately no atomic DB-plus-remote-queue commit claim.
Instances are immutable and thread-safe; connections must not be shared between threads.
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic interfaceApplication-managed connection factory; every returned connection is independently owned.static enumSupported schema dialects; drivers are supplied by the hosting application.static interfaceApplication mutation executed inside the same transaction as the inbox insertion.static final recordImmutable persisted relay row. -
Constructor Summary
ConstructorsConstructorDescriptionJdbcDelivery(String scope, Observability telemetry) Binds DB identities to one authenticated queue/inbox scope. -
Method Summary
Modifier and TypeMethodDescriptionbooleanconsume(Connection connection, String id, byte[] envelope, JdbcDelivery.Mutation mutation, Instant consumedAt) Inserts the inbox identity and applies consumer mutation in the caller's single transaction.longenqueue(Connection connection, String id, String aggregate, TracedQueue.Message message, Instant createdAt, Instant retainUntil) Appends an ordered message in the caller's application transaction.static voidinitialize(Connection connection, JdbcDelivery.Dialect dialect) Creates additive integration tables; execute as an explicit migration on a dedicated connection.pending(Connection connection, int maximum) Selects only the first unsent message of each aggregate, preserving send order across relays.intpruneSentPayloads(Connection connection, Instant now) Removes only payloads already confirmed sent and past their retention time.intrelayOnce(JdbcDelivery.Connections connections, org.j7m.middleware.distributed.ReplicatedQueue queue, String name, int maximum, Instant deadline) Publishes head rows at least once and marks only confirmed queue publications in short DB transactions.
-
Constructor Details
-
JdbcDelivery
Binds DB identities to one authenticated queue/inbox scope.- Parameters:
scope- scope identifier, at most 128 ASCII identifier characterstelemetry- optional observability integration; null disables span export- Throws:
IllegalArgumentException- for invalid scope
-
-
Method Details
-
initialize
public static void initialize(Connection connection, JdbcDelivery.Dialect dialect) throws SQLException Creates additive integration tables; execute as an explicit migration on a dedicated connection. Never call during an application transaction: DDL transaction semantics differ by database. Tables and tombstones remain on rollback/uninstall to preserve delivery identity.- Parameters:
connection- dedicated migration connectiondialect- supported database dialect- Throws:
SQLException- for schema creation failure
-
enqueue
public long enqueue(Connection connection, String id, String aggregate, TracedQueue.Message message, Instant createdAt, Instant retainUntil) throws SQLException, IOException Appends an ordered message in the caller's application transaction. Existing IDs with identical envelope/aggregate return their original sequence; conflicting IDs fail. Concurrent first insertion of an aggregate can require retry of the whole transaction.- Parameters:
connection- exact non-autocommit application transactionid- stable message identityaggregate- aggregate ordering identitymessage- immutable application payload and retry-stable contextcreatedAt- application-selected timestamp used only for retention metadataretainUntil- earliest time payload may be removed after successful publication- Returns:
- aggregate-local sequence
- Throws:
SQLException- for inactive transaction, conflicts or database errorsIOException- for invalid message envelope
-
pending
Selects only the first unsent message of each aggregate, preserving send order across relays. Sending/marking an older row more than once is safe only because the real queue retains the same message identity and content fingerprint. This method does not claim rows or commit.- Parameters:
connection- caller-owned connectionmaximum- bounded head count in [1,256]- Returns:
- immutable ordered head rows
- Throws:
SQLException- for database access failure
-
relayOnce
public int relayOnce(JdbcDelivery.Connections connections, org.j7m.middleware.distributed.ReplicatedQueue queue, String name, int maximum, Instant deadline) throws SQLException, IOException Publishes head rows at least once and marks only confirmed queue publications in short DB transactions. Stable queue requests bind both DB scope and message ID. A timeout preserves the pending row. Concurrent relays may resend the same head but cannot publish a successor before confirmed send.- Parameters:
connections- independent relay connection factoryqueue- authenticated queue bound to this scopename- destination queue namemaximum- at most 256 aggregate headsdeadline- absolute queue deadline- Returns:
- number of confirmed/marked rows
- Throws:
SQLException- for DB failures; rows remain recoverable on retryIOException- for queue failure/conflict or unknown outcome
-
consume
public boolean consume(Connection connection, String id, byte[] envelope, JdbcDelivery.Mutation mutation, Instant consumedAt) throws SQLException, IOException Inserts the inbox identity and applies consumer mutation in the caller's single transaction. The database uniqueness constraint serializes concurrent duplicates. After a uniqueness error, roll back and retry the whole transaction so the committed digest can be checked. A returned false means an identical previously committed consumption and performs no application mutation.- Parameters:
connection- exact non-autocommit consumer transactionid- queue message identityenvelope- received traced message bytesmutation- application mutation using this connectionconsumedAt- audit timestamp, not an expiry authority- Returns:
- true for a newly applied mutation, false for a verified replay
- Throws:
SQLException- on conflict, application failure or DB failureIOException- on malformed envelope
-
pruneSentPayloads
Removes only payloads already confirmed sent and past their retention time. Hash/identity/order tombstones are retained indefinitely so expired payloads cannot permit conflicting ID reuse. Operators must provision/monitor tombstone storage explicitly.- Parameters:
connection- caller-managed retention transactionnow- retention cutoff timestamp- Returns:
- number of payloads removed
- Throws:
SQLException- for inactive transaction or database error
-