Skip to content

Commit 48b0556

Browse files
committed
Add Redis transport SDK for BabelQueue with publisher and consumer
0 parents  commit 48b0556

23 files changed

Lines changed: 1705 additions & 0 deletions

.github/workflows/ci.yml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
8+
permissions:
9+
contents: read
10+
11+
jobs:
12+
test:
13+
runs-on: ubuntu-latest
14+
strategy:
15+
fail-fast: false
16+
matrix:
17+
java: [ "17", "21" ]
18+
steps:
19+
- uses: actions/checkout@v5
20+
21+
- uses: actions/setup-java@v4
22+
with:
23+
distribution: temurin
24+
java-version: ${{ matrix.java }}
25+
cache: maven
26+
27+
# `verify` runs the JUnit 5 suite (incl. the Redis binding conformance test)
28+
# and the JaCoCo >=90% line-coverage gate. Tests mock the Redis command seam —
29+
# no Redis, no network — so they run in the same job as the build.
30+
- name: Test
31+
run: mvn -B --no-transfer-progress verify
32+
33+
conformance:
34+
name: Conformance suite in sync
35+
runs-on: ubuntu-latest
36+
steps:
37+
- uses: actions/checkout@v5
38+
- name: Verify vendored conformance matches the canonical suite
39+
run: |
40+
git clone --depth 1 https://github.com/BabelQueue/conformance.git "$RUNNER_TEMP/conformance"
41+
diff -ru "$RUNNER_TEMP/conformance/manifest.json" "src/test/resources/conformance/manifest.json"
42+
diff -ru "$RUNNER_TEMP/conformance/fixtures" "src/test/resources/conformance/fixtures"
43+
diff -ru "$RUNNER_TEMP/conformance/schema" "src/test/resources/conformance/schema"
44+
echo "Vendored conformance is in sync with the canonical suite."
45+
46+
ci-green:
47+
name: CI green
48+
runs-on: ubuntu-latest
49+
needs: [test, conformance]
50+
if: ${{ always() }}
51+
steps:
52+
- name: Fail if any required job did not pass
53+
run: |
54+
if ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}; then
55+
echo "A required job failed or was cancelled."
56+
exit 1
57+
fi

.github/workflows/release.yml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags: [ "v*" ]
6+
7+
permissions:
8+
contents: write
9+
10+
jobs:
11+
release:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v5
15+
16+
- uses: actions/setup-java@v4
17+
with:
18+
distribution: temurin
19+
java-version: "17"
20+
cache: maven
21+
# Writes a settings.xml server entry `central` using the env vars below,
22+
# and imports the GPG key for signing.
23+
server-id: central
24+
server-username: CENTRAL_TOKEN_USERNAME
25+
server-password: CENTRAL_TOKEN_PASSWORD
26+
gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}
27+
gpg-passphrase: MAVEN_GPG_PASSPHRASE
28+
29+
- name: Run tests
30+
run: mvn -B --no-transfer-progress verify
31+
32+
- name: Publish to Maven Central
33+
run: mvn -B --no-transfer-progress -Prelease -DskipTests deploy
34+
env:
35+
CENTRAL_TOKEN_USERNAME: ${{ secrets.CENTRAL_TOKEN_USERNAME }}
36+
CENTRAL_TOKEN_PASSWORD: ${{ secrets.CENTRAL_TOKEN_PASSWORD }}
37+
MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
38+
39+
- name: Create GitHub Release
40+
uses: softprops/action-gh-release@v2
41+
with:
42+
generate_release_notes: true

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
target/
2+
*.class
3+
4+
# CommitBrief local config and cache
5+
.commitbrief/

CHANGELOG.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Changelog
2+
3+
All notable changes to `com.babelqueue:babelqueue-redis` are documented here.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
6+
this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
The envelope wire format is versioned separately by `meta.schema_version`
8+
(currently **1**) — see the contract at [babelqueue.com](https://babelqueue.com).
9+
10+
## [1.0.0] - 2026-06-14
11+
12+
### Added
13+
- Initial release. A Redis transport on `babelqueue-core` + the Lettuce client,
14+
implementing §1 of the broker-bindings contract (the reliable-queue list pattern):
15+
`RedisPublisher` (produce = `RPUSH` the canonical envelope JSON byte-for-byte, no
16+
wrapping and no property projection — Redis lists carry no native metadata) and
17+
`RedisConsumer` (reserve = `BLMOVE <queue> <queue>:processing LEFT RIGHT` so an
18+
in-flight message survives a worker crash → URN-routed `BabelHandler`s → ack via
19+
`LREM`; at-least-once, a throwing handler leaves the message on the processing list;
20+
`onError`/`onUnknownUrn` hooks; `attempts` taken from the body unchanged since Redis
21+
has no native delivery counter). Java 17, JUnit 5, Mockito, JaCoCo ≥90% line coverage
22+
(currently 100%); unit tests mock the `RedisCommands` seam (no Redis, no network) and
23+
capture the `RPUSH`/`BLMOVE`/`LREM` calls. The envelope is unchanged
24+
(`schema_version: 1`); Redis is purely additive. This is a Java-owned reliable queue;
25+
full parity with Laravel's reserved-sorted-set reservation on a shared Redis queue is
26+
a separate task (broker-bindings §1.4).
27+
28+
[Unreleased]: https://github.com/BabelQueue/babelqueue-java-redis/compare/v1.0.0...HEAD
29+
[1.0.0]: https://github.com/BabelQueue/babelqueue-java-redis/releases/tag/v1.0.0

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 Muhammet Şafak
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# BabelQueue — Redis (Java)
2+
3+
`com.babelqueue:babelqueue-redis` — a Redis transport for
4+
[BabelQueue](https://babelqueue.com), built on the [Lettuce](https://lettuce.io)
5+
client and the framework-agnostic
6+
[`babelqueue-core`](https://github.com/BabelQueue/babelqueue-java).
7+
8+
A canonical-envelope **publisher** and a URN-routed **consumer**, so a Redis-based Java
9+
service speaks the same wire contract (envelope shape, URN identity, trace propagation)
10+
as the PHP/Laravel, Python, Go, Node and .NET SDKs. Implements
11+
[§1 of the broker-bindings contract](https://babelqueue.com) — the reliable-queue list
12+
pattern.
13+
14+
## Install (Maven)
15+
16+
```xml
17+
<dependency>
18+
<groupId>com.babelqueue</groupId>
19+
<artifactId>babelqueue-redis</artifactId>
20+
<version>1.0.0</version>
21+
</dependency>
22+
```
23+
24+
It pulls `babelqueue-core` and `io.lettuce:lettuce-core` transitively.
25+
26+
## Use
27+
28+
```java
29+
RedisClient client = RedisClient.create("redis://localhost:6379");
30+
RedisCommands<String, String> redis = client.connect().sync(); // the command seam
31+
32+
// produce
33+
String id = RedisPublisher.create(redis, "orders")
34+
.publish("urn:babel:orders:created", Map.of("order_id", 1042));
35+
36+
// consume
37+
RedisConsumer consumer = RedisConsumer.builder(redis, "orders")
38+
.handler("urn:babel:orders:created", (env, body) -> {
39+
// env.data(), env.traceId(), env.attempts() ...
40+
})
41+
.onError((err, env, body) -> log.warn("bad message", err))
42+
.build();
43+
consumer.run(); // blocking-reserves until the thread is interrupted
44+
```
45+
46+
Point the `RedisClient` at any Redis (local, cluster via the appropriate client, or a
47+
managed instance). The command seam is Lettuce's `RedisCommands<String, String>`
48+
interface, so it is trivially mockable in your own tests.
49+
50+
## Contract mapping (§1)
51+
52+
| Envelope | Redis |
53+
| :--- | :--- |
54+
| body | the list element — the canonical envelope JSON, **byte-for-byte, no wrapping** |
55+
| `job` (URN) | read from the body and routed consumer-side (Redis lists carry no native metadata) |
56+
| produce | `RPUSH <queue> <envelope>` |
57+
| reserve | `BLMOVE <queue> <queue>:processing LEFT RIGHT` (head → tail; crash-safe in-flight) |
58+
| ack | `LREM <queue>:processing 1 <envelope>` |
59+
| `attempts` | taken from the body unchanged (Redis has no native delivery counter) |
60+
61+
Redis lists have **no native attribute channel**, so — unlike the SQS/RabbitMQ/Kafka
62+
bindings — there is **no property projection**. The single cross-SDK invariant is
63+
**payload identity**: the stored element is the exact envelope bytes, with no outer
64+
job-structure and no added fields.
65+
66+
Retry is **at-least-once**: a throwing handler leaves the message on the
67+
`<queue>:processing` list (a recovery sweep can requeue it); a successful handler `LREM`s
68+
it. The poll loop never stops on a bad message — observe via `onError` / `onUnknownUrn`.
69+
The envelope is unchanged (`schema_version` stays `1`); Redis is purely additive.
70+
71+
> **Scope.** This is a **Java-owned reliable queue**, mirroring the Go runtime's
72+
> reliable-queue mechanism. Pointed at a queue this SDK owns end-to-end it is a complete,
73+
> crash-safe transport. **Full parity with Laravel's reserved-sorted-set reservation on a
74+
> _shared_ PHP+Java Redis queue is a separate task** (see broker-bindings §1.4): a
75+
> consumer reading a queue produced by the Laravel driver must replicate Laravel's
76+
> reserve/ack semantics.
77+
78+
## Build & test
79+
80+
```bash
81+
mvn verify
82+
```
83+
84+
Unit tests mock the `RedisCommands` seam (no Redis, no network) and capture the
85+
`RPUSH`/`BLMOVE`/`LREM` calls with Mockito. JaCoCo gates the build at ≥90% line coverage.
86+
87+
## License
88+
89+
MIT

0 commit comments

Comments
 (0)