81 lines
4.0 KiB
Markdown
81 lines
4.0 KiB
Markdown
# `rabbitmq` — exchanges, bindings and a dead-letter queue that works
|
|
|
|
Companion project for
|
|
[**Spring Boot and RabbitMQ: Exchanges, Queues, Bindings and a Working Dead-Letter Queue**](https://ankurm.com/spring-boot-rabbitmq-exchanges-dead-letter-queue/)
|
|
on ankurm.com.
|
|
|
|
Ten tests against a **real RabbitMQ broker**. All four exchange types, manual acknowledgement,
|
|
a dead-letter path exercised through `basicNack` and through TTL expiry, and the three ways a
|
|
topology loses work quietly.
|
|
|
|
## Versions
|
|
|
|
| | Version | Notes |
|
|
|---|---|---|
|
|
| JDK | 25 (Temurin 25.0.4.1+1) | current LTS |
|
|
| Spring Boot | 4.1.1 | |
|
|
| Spring AMQP / Spring Rabbit | 4.1.1 | Boot-managed |
|
|
| `com.rabbitmq:amqp-client` | **5.30.0** | Boot-managed. Central has 5.35.0 |
|
|
| Testcontainers | 2.0.5 | artifact is `testcontainers-rabbitmq`, not `rabbitmq` |
|
|
| **Broker for the committed transcripts** | **RabbitMQ 3.10.25** | see the note below |
|
|
|
|
Client-side versions were read from `repo1.maven.org/.../maven-metadata.xml` and Boot's
|
|
`spring-boot-dependencies` POM.
|
|
|
|
> **About the broker version.** The machine that regenerates `docs/output/` has no Docker daemon
|
|
> and no root, so `scripts/broker.sh` starts a generic-unix RabbitMQ against an extracted Erlang
|
|
> 24 runtime, and the newest release that pairs with Erlang 24 is 3.10.25. Everything exercised
|
|
> here — the four exchange types, `x-dead-letter-exchange`, `x-dead-letter-routing-key`,
|
|
> `x-message-ttl`, `x-death`, manual ack, `mandatory` returns, `PRECONDITION_FAILED` on
|
|
> inequivalent arguments — is AMQP 0-9-1 behaviour that is unchanged in RabbitMQ 4.x. The one
|
|
> broker-version difference worth knowing for 4.x is that **quorum queues are the default queue
|
|
> type** and classic mirrored queues are gone; nothing in this module declares a queue type, so
|
|
> the topology is valid on both. If you have Docker, run the same tests against
|
|
> `rabbitmq:4.1-management` with
|
|
> [`TestcontainersConfiguration`](src/test/java/com/ankurm/rabbit/TestcontainersConfiguration.java).
|
|
|
|
## Quickstart
|
|
|
|
```bash
|
|
# with Docker: point the tests at Testcontainers, or run your own broker on 5672
|
|
mvn test
|
|
|
|
# without Docker:
|
|
export ERL_ROOT=/path/to/erlang RABBITMQ_HOME=/path/to/rabbitmq_server-3.10.25
|
|
./scripts/run-all.sh
|
|
```
|
|
|
|
## Documentation
|
|
|
|
1. [The on-ramp](docs/01-the-on-ramp.md)
|
|
2. [Four exchange types](docs/02-exchanges.md)
|
|
3. [The message that goes nowhere and says nothing](docs/03-the-silent-drop.md)
|
|
4. [A dead-letter queue that actually works](docs/04-dead-lettering.md)
|
|
5. [Acknowledgement](docs/05-acknowledgement.md)
|
|
6. [Changing your mind about a queue](docs/06-changing-your-mind.md)
|
|
|
|
## Captured output
|
|
|
|
| File | Shows |
|
|
|---|---|
|
|
| [`topic-wildcards.txt`](docs/output/topic-wildcards.txt) | `*` vs `#` over five routing keys |
|
|
| [`dead-letter.txt`](docs/output/dead-letter.txt) | `x-death` for `rejected` and for `expired` |
|
|
| [`requeue-loop.txt`](docs/output/requeue-loop.txt) | 199 redeliveries, 0 dead-lettered |
|
|
| [`unroutable.txt`](docs/output/unroutable.txt) | `312 NO_ROUTE` |
|
|
| [`precondition-failed.txt`](docs/output/precondition-failed.txt) | `406` on an inequivalent argument |
|
|
| [`tests.txt`](docs/output/tests.txt) | 10 tests |
|
|
|
|
## Five things this module exists to prove
|
|
|
|
1. **An unroutable message is discarded silently**, and finding out needs `publisher-returns`
|
|
*and* `mandatory` — two settings in two different places. Setting only `mandatory` does
|
|
nothing.
|
|
2. **`requeue=true` never dead-letters.** 200 attempts, 199 redeliveries, an empty DLQ and a
|
|
queue depth that stays at 1 the whole time.
|
|
3. **`x-death.reason` distinguishes `rejected` from `expired`**, which is the difference between
|
|
a consumer that refused the work and a consumer that never got to it.
|
|
4. **Queue arguments are immutable** — `406 PRECONDITION_FAILED`, wrapped in an exception whose
|
|
own message is the string `java.io.IOException`.
|
|
5. **Boot auto-configures no JSON converter for RabbitMQ**, and the converter you want is
|
|
`JacksonJsonMessageConverter`, not `Jackson2JsonMessageConverter`.
|