218 lines
6.0 KiB
Java
218 lines
6.0 KiB
Java
package com.ankurm.rabbit;
|
|
|
|
import org.springframework.amqp.core.Binding;
|
|
import org.springframework.amqp.core.BindingBuilder;
|
|
import org.springframework.amqp.core.DirectExchange;
|
|
import org.springframework.amqp.core.FanoutExchange;
|
|
import org.springframework.amqp.core.HeadersExchange;
|
|
import org.springframework.amqp.core.Queue;
|
|
import org.springframework.amqp.core.QueueBuilder;
|
|
import org.springframework.amqp.core.TopicExchange;
|
|
import org.springframework.context.annotation.Bean;
|
|
import org.springframework.context.annotation.Configuration;
|
|
|
|
import java.util.Map;
|
|
|
|
/**
|
|
* The whole topology as beans. {@code RabbitAdmin} declares every {@code Exchange},
|
|
* {@code Queue} and {@code Binding} bean when the connection is first opened.
|
|
*
|
|
* <p>Two things about that are worth knowing before you rely on it:
|
|
* <ul>
|
|
* <li>Declaration is <b>idempotent only if the arguments match</b>. Redeclaring an existing
|
|
* queue with different arguments is a channel-level {@code PRECONDITION_FAILED} — see
|
|
* docs/06-changing-your-mind.md.</li>
|
|
* <li>Declarations happen on connection, not on context refresh, so a topology error surfaces
|
|
* at the first publish rather than at startup.</li>
|
|
* </ul>
|
|
*
|
|
* @see <a href="../../../../../docs/02-exchanges.md">docs/02-exchanges.md</a>
|
|
*/
|
|
@Configuration(proxyBeanMethods = false)
|
|
public class Topology {
|
|
|
|
public static final String DIRECT = "orders.direct";
|
|
|
|
public static final String FANOUT = "orders.fanout";
|
|
|
|
public static final String TOPIC = "orders.topic";
|
|
|
|
public static final String HEADERS = "orders.headers";
|
|
|
|
public static final String DLX = "orders.dlx";
|
|
|
|
public static final String Q_NEW = "orders.new";
|
|
|
|
public static final String Q_CANCEL = "orders.cancel";
|
|
|
|
public static final String Q_AUDIT = "audit.all";
|
|
|
|
public static final String Q_ANALYTICS = "analytics.all";
|
|
|
|
public static final String Q_EU = "orders.eu";
|
|
|
|
public static final String Q_HIGH = "orders.high";
|
|
|
|
public static final String Q_PRIORITY = "orders.priority";
|
|
|
|
public static final String Q_ANY = "orders.any";
|
|
|
|
public static final String Q_WORK = "orders.work";
|
|
|
|
public static final String Q_TTL = "orders.ttl";
|
|
|
|
public static final String Q_DLQ = "orders.dlq";
|
|
|
|
// --- direct: routing key must match the binding key exactly -----------------------------
|
|
|
|
@Bean
|
|
DirectExchange directExchange() {
|
|
return new DirectExchange(DIRECT);
|
|
}
|
|
|
|
@Bean
|
|
Queue newQueue() {
|
|
return QueueBuilder.durable(Q_NEW).build();
|
|
}
|
|
|
|
@Bean
|
|
Queue cancelQueue() {
|
|
return QueueBuilder.durable(Q_CANCEL).build();
|
|
}
|
|
|
|
@Bean
|
|
Binding bindNew(DirectExchange directExchange, Queue newQueue) {
|
|
return BindingBuilder.bind(newQueue).to(directExchange).with("new");
|
|
}
|
|
|
|
@Bean
|
|
Binding bindCancel(DirectExchange directExchange, Queue cancelQueue) {
|
|
return BindingBuilder.bind(cancelQueue).to(directExchange).with("cancel");
|
|
}
|
|
|
|
// --- fanout: routing key ignored entirely -----------------------------------------------
|
|
|
|
@Bean
|
|
FanoutExchange fanoutExchange() {
|
|
return new FanoutExchange(FANOUT);
|
|
}
|
|
|
|
@Bean
|
|
Queue auditQueue() {
|
|
return QueueBuilder.durable(Q_AUDIT).build();
|
|
}
|
|
|
|
@Bean
|
|
Queue analyticsQueue() {
|
|
return QueueBuilder.durable(Q_ANALYTICS).build();
|
|
}
|
|
|
|
@Bean
|
|
Binding bindAudit(FanoutExchange fanoutExchange, Queue auditQueue) {
|
|
return BindingBuilder.bind(auditQueue).to(fanoutExchange);
|
|
}
|
|
|
|
@Bean
|
|
Binding bindAnalytics(FanoutExchange fanoutExchange, Queue analyticsQueue) {
|
|
return BindingBuilder.bind(analyticsQueue).to(fanoutExchange);
|
|
}
|
|
|
|
// --- topic: '*' is exactly one word, '#' is zero or more -------------------------------
|
|
|
|
@Bean
|
|
TopicExchange topicExchange() {
|
|
return new TopicExchange(TOPIC);
|
|
}
|
|
|
|
@Bean
|
|
Queue euQueue() {
|
|
return QueueBuilder.durable(Q_EU).build();
|
|
}
|
|
|
|
@Bean
|
|
Queue highQueue() {
|
|
return QueueBuilder.durable(Q_HIGH).build();
|
|
}
|
|
|
|
@Bean
|
|
Binding bindEu(TopicExchange topicExchange, Queue euQueue) {
|
|
return BindingBuilder.bind(euQueue).to(topicExchange).with("order.eu.*");
|
|
}
|
|
|
|
@Bean
|
|
Binding bindHigh(TopicExchange topicExchange, Queue highQueue) {
|
|
return BindingBuilder.bind(highQueue).to(topicExchange).with("order.#.high");
|
|
}
|
|
|
|
// --- headers: routing key ignored, header map matched ------------------------------------
|
|
|
|
@Bean
|
|
HeadersExchange headersExchange() {
|
|
return new HeadersExchange(HEADERS);
|
|
}
|
|
|
|
@Bean
|
|
Queue priorityQueue() {
|
|
return QueueBuilder.durable(Q_PRIORITY).build();
|
|
}
|
|
|
|
@Bean
|
|
Queue anyQueue() {
|
|
return QueueBuilder.durable(Q_ANY).build();
|
|
}
|
|
|
|
/** x-match=all: every header must match. */
|
|
@Bean
|
|
Binding bindPriority(HeadersExchange headersExchange, Queue priorityQueue) {
|
|
return BindingBuilder.bind(priorityQueue).to(headersExchange)
|
|
.whereAll(Map.of("priority", "high", "region", "eu")).match();
|
|
}
|
|
|
|
/** x-match=any: one is enough. */
|
|
@Bean
|
|
Binding bindAny(HeadersExchange headersExchange, Queue anyQueue) {
|
|
return BindingBuilder.bind(anyQueue).to(headersExchange)
|
|
.whereAny(Map.of("priority", "high", "region", "eu")).match();
|
|
}
|
|
|
|
// --- the dead-letter path ----------------------------------------------------------------
|
|
|
|
@Bean
|
|
DirectExchange deadLetterExchange() {
|
|
return new DirectExchange(DLX);
|
|
}
|
|
|
|
@Bean
|
|
Queue deadLetterQueue() {
|
|
// Deliberately NO dead-letter-exchange on the DLQ itself. Pointing a DLQ's DLX back at
|
|
// the exchange that feeds it is the classic infinite loop.
|
|
return QueueBuilder.durable(Q_DLQ).build();
|
|
}
|
|
|
|
@Bean
|
|
Binding bindDlq(DirectExchange deadLetterExchange, Queue deadLetterQueue) {
|
|
// The routing key used when dead-lettering is the message's ORIGINAL routing key unless
|
|
// deadLetterRoutingKey() overrides it. Both work queues below set it explicitly to
|
|
// "failed", so this one binding catches everything.
|
|
return BindingBuilder.bind(deadLetterQueue).to(deadLetterExchange).with("failed");
|
|
}
|
|
|
|
@Bean
|
|
Queue workQueue() {
|
|
return QueueBuilder.durable(Q_WORK)
|
|
.deadLetterExchange(DLX)
|
|
.deadLetterRoutingKey("failed")
|
|
.build();
|
|
}
|
|
|
|
@Bean
|
|
Queue ttlQueue() {
|
|
return QueueBuilder.durable(Q_TTL)
|
|
.ttl(1500)
|
|
.deadLetterExchange(DLX)
|
|
.deadLetterRoutingKey("failed")
|
|
.build();
|
|
}
|
|
|
|
}
|