Patterns
Everything the library does, and how much of it
application.yml can reach.
The starter is a thin layer: it
builds beans from properties and hands you the library's own
AceMq. So every pattern below is available to a Boot
application — but they divide into three groups, and knowing which group
you are in saves a search for a property that does not exist.
The three groups
Properties. Configured entirely from
application.yml, with no Java beyond a handler.
| Pattern | Where |
|---|---|
| Consume a queue | @AceListener |
| Topology: exchanges, queues, bindings, drift | Topology |
| Retries — the non-blocking ladder | Retries |
Dead letters, .dlq and .parked |
Dead letters |
| TLS, credentials, development certificates | Security |
| Serialization format | Formats |
| Health, metrics | Observability |
| Graceful shutdown | Shutdown |
| Publisher confirms, back pressure, blocked connections | Publishing |
A bean that replaces an auto-configured one. Change
the behaviour of everything on the connection at once. Each of these
backs off with @ConditionalOnMissingBean, so declaring
yours is the whole mechanism.
| Pattern | The bean to declare |
|---|---|
| Payload encryption | Codec |
| Claim check | Codec |
| Avro with a schema registry | Codec |
| Tracing | Telemetry |
| Rotating credentials | ConnectionConfig |
A bean of its own. The pattern is a library object
the starter has no opinion about. Declare it, take AceMq as
a parameter, and give it destroyMethod = "close" if it
holds anything.
| Pattern | The type |
|---|---|
| Publishing | Publisher<T> |
| Idempotent consumer | IdempotencyStore + a ConsumerGroup |
| Replay | Replay, per call |
| Request and reply | Requester, Responder |
| Scheduling | Scheduler |
| Transactional outbox | OutboxStore + OutboxRelay |
| Saga | Saga<T>, built where the steps are |
| Pipelines | Pipeline<T> |
| Ordered per key | OrderedQueue<T> |
| Routing slips | Itinerary, per message |
| Streams | StreamConsumer |
| Interceptors | PublishInterceptor,
ConsumeInterceptor |
Four things the annotation cannot do
Worth stating plainly, because the natural first move is to look for an attribute.
@AceListener cannot take an idempotency
store. The registry builds ConsumerOptions from
properties, and there is no property for a store because a store is a
bean. Declare the consumer as a ConsumerGroup bean instead
— Idempotency has it.
@AceListener cannot have its own retry ladder or
codec. Same reason, same answer. The ladder in
acemq.listener.retry.* applies to every annotated listener
in the application.
@AceListener cannot read a stream. A
stream reader has to say where in the log to start, and that answer
usually comes from a database. Streams explains
why an annotation would be worse than a bean here.
There is no return value that acknowledges. The
library has an AckAwareHandler that returns an
Ack, and nothing in the library dispatches to it yet. An
annotation wired to an unused code path would be a starter growing a
feature its own library does not have.
Choosing between them
A few of these solve overlapping problems, and the overlap is where time gets lost.
Saga or pipeline? A saga is one process running several steps in memory, with compensations; a pipeline is several queues with a consumer on each. Use a saga when the steps must be undone on failure and the whole thing is short enough to hold open. Use a pipeline when the steps are long, need to scale independently, or must survive a restart partway through.
Pipeline or routing slip? A pipeline's route is fixed at build time and typed by the compiler. A slip's route travels in the message and varies per message. Prefer the pipeline; it is the one where a wrong step is a compile error.
Ordered queue or concurrency 1?
concurrency = 1 on a listener gives total order across
every message on the queue, and a throughput of one handler. An ordered queue gives
order per key and parallelism across keys. Total order is almost never
the requirement.
Outbox or just publish? If the publish and a database write must both happen or neither, the outbox. If the publish is the only side effect, publish directly — the outbox adds a table, a relay and a polling interval, and paying that for a message with nothing to be consistent with is cost without a benefit.
Stream or queue? A queue distributes work and forgets. A stream is a log that several readers each read in full and can re-read. Streams has the table.
The library's own examples
Each of these patterns has a runnable, single-file example in acemq-java-amqp-examples, against a real broker and without Spring. When a page here shows a pattern wired into a context, that repository shows the same pattern working on its own — which is the shorter thing to read when the question is what the library does rather than how the starter reaches it.