Appearance
RabbitMQ Integration
This document provides a detailed technical overview of the RabbitMQ integration within the IT Ticketing Service. The integration is a core component of our event-driven architecture, enabling asynchronous communication by publishing events whenever a ticket is created or updated.
For a higher-level overview of how this fits into the system's design, please see the Event-Driven Architecture documentation.
RabbitMQ Configuration
The application's connection and topology for RabbitMQ are defined declaratively using Spring AMQP's Java configuration. All core components (exchange, queue, and binding) are managed as Spring beans, promoting modularity and testability.
The configuration is centralized in src/main/java/com/slalom/demo/ticketing/config/RabbitMQConfig.java.
Exchange, Queue, and Binding
The application defines a durable topic exchange, a durable queue, and a binding to link them. This setup ensures that messages are routed correctly and persist through broker restarts.
- Exchange: A
TopicExchangeis used, providing flexible routing capabilities based on pattern matching. While the current implementation uses a static routing key, a topic exchange allows for future expansion with more complex routing rules (e.g.,ticket.created.high_priority,ticket.updated.status). - Queue: A single durable
Queueis declared to receive ticket events. - Binding: A
Bindingconnects the queue to the exchange using a specific routing key.
The names for these components are externalized and should be configured in application.properties. See the Configuration Guide for more details.
For this application, the following values are configured:
- Exchange name:
ticket.exchange - Routing key:
ticket.routing.key
java
// src/main/java/com/slalom/demo/ticketing/config/RabbitMQConfig.java
@Configuration
public class RabbitMQConfig {
@Value("${rabbitmq.exchange.name}")
private String exchangeName;
@Value("${rabbitmq.queue.name}")
private String queueName;
@Value("${rabbitmq.routing.key}")
private String routingKey;
// Defines the queue where messages will be stored.
// The 'true' argument makes the queue durable.
@Bean
public Queue queue() {
return new Queue(queueName, true);
}
// Defines the exchange that receives messages from the publisher.
@Bean
public TopicExchange exchange() {
return new TopicExchange(exchangeName);
}
// Binds the queue to the exchange with a specific routing key.
// Messages sent to the exchange with this routing key will be delivered to the queue.
@Bean
public Binding binding(Queue queue, TopicExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with(routingKey);
}
// ... other beans
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Connection Settings
Spring Boot auto-configures the ConnectionFactory based on properties defined in application.properties. The default values target a local RabbitMQ instance.
properties
# src/main/resources/application.properties
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
# spring.rabbitmq.username=guest
# spring.rabbitmq.password=guest1
2
3
4
5
2
3
4
5
Note: For production or cloud environments, these properties must be updated to point to the correct RabbitMQ instance. This is a key step in the GCP Migration strategy.
Message Publishing
Message publishing is handled by the MessagePublisher service, which provides a clean abstraction over the RabbitTemplate.
MessagePublisher Service
The MessagePublisher service is responsible for sending TicketEvent objects to the configured RabbitMQ exchange. It is injected wherever event publishing is required, decoupling the business logic from the messaging infrastructure.
java
// src/main/java/com/slalom/demo/ticketing/service/MessagePublisher.java
@Service
@RequiredArgsConstructor
@Slf4j
public class MessagePublisher {
private final RabbitTemplate rabbitTemplate;
@Value("${rabbitmq.exchange.name}")
private String exchangeName;
@Value("${rabbitmq.routing.key}")
private String routingKey;
public void publishTicketEvent(TicketEvent event) {
log.info("Publishing ticket event: {} for ticket ID: {}", event.getEventType(), event.getTicketId());
rabbitTemplate.convertAndSend(exchangeName, routingKey, event);
log.info("Successfully published event to exchange: {}", exchangeName);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Event Serialization
Events are serialized to JSON format before being placed on the queue. This is handled automatically by the configured Jackson2JsonMessageConverter, which converts Plain Old Java Objects (POJOs) like TicketEvent into a JSON string. This makes messages language-agnostic and easily consumable by other services.
Error Handling
The current publishing logic is "fire-and-forget." If the RabbitMQ broker is unavailable or rejects the message, the convertAndSend method will throw an AmqpException, which is currently unhandled and will roll back the parent transaction (if one exists).
Potential Improvements for Maintainers:
- Publisher Confirms: For guaranteed delivery, enable publisher confirms (
spring.rabbitmq.publisher-confirms=true) and implement aConfirmCallbackto handle acknowledgments from the broker. - Dead-Letter Queue (DLQ): Configure a DLQ to capture and hold messages that cannot be processed, preventing message loss and allowing for later inspection or reprocessing.
Spring AMQP
The integration relies heavily on the Spring AMQP project, which provides the high-level abstractions for interacting with RabbitMQ.
RabbitTemplate
The RabbitTemplate is the central component for sending and receiving messages. Our application customizes the default RabbitTemplate to ensure it uses our preferred JSON message format.
java
// src/main/java/com/slalom/demo/ticketing/config/RabbitMQConfig.java
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
// Set the custom message converter to handle JSON serialization.
rabbitTemplate.setMessageConverter(messageConverter());
return rabbitTemplate;
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
MessageConverter
A Jackson2JsonMessageConverter bean is configured to handle the serialization of TicketEvent objects to JSON and the deserialization on the consumer side (if a consumer were present in this service). This eliminates boilerplate serialization code.
java
// src/main/java/com/slalom/demo/ticketing/config/RabbitMQConfig.java
@Bean
public MessageConverter messageConverter() {
// This converter uses the Jackson library to convert objects to/from JSON.
return new Jackson2JsonMessageConverter();
}1
2
3
4
5
6
7
2
3
4
5
6
7
Event Types
The application publishes a single type of event object, TicketEvent, which contains all relevant information about the ticket modification. The specific action is denoted by the eventType field.
Event Payload Structure
The TicketEvent class defines the structure of the message payload. It is a simple data class annotated with Lombok for conciseness.
java
// src/main/java/com/slalom/demo/ticketing/event/TicketEvent.java
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class TicketEvent {
private String eventType; // "CREATED" or "UPDATED"
private Long ticketId;
private String title;
private TicketStatus status;
private TicketPriority priority;
private String requesterEmail;
private String assignedTo;
private LocalDateTime timestamp;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Event Categories
CREATED: Published when a new ticket is successfully created via thePOST /api/ticketsendpoint.UPDATED: Published when an existing ticket is modified via thePUT /api/tickets/{id}endpoint.
A sample JSON payload for a CREATED event would look like this:
json
{
"eventType": "CREATED",
"ticketId": 101,
"title": "Laptop won't start",
"status": "OPEN",
"priority": "HIGH",
"requesterEmail": "user@example.com",
"assignedTo": "support@example.com",
"timestamp": "2024-10-27T10:00:00.123456"
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
Testing Integration
Developers can test and debug the RabbitMQ integration locally using the provided Docker Compose environment and the RabbitMQ Management UI.
Local Environment Setup
- Ensure you have Docker installed.
- From the project root, run
docker-compose up. This will start both a MySQL database and a RabbitMQ instance. - The RabbitMQ broker will be available at
localhost:5672. - The RabbitMQ Management UI will be accessible at
http://localhost:15672. Use the default credentialsguest/guestto log in.
The docker-compose.yml file defines the service:
yaml
# docker-compose.yml
services:
# ...
rabbitmq:
image: rabbitmq:3.12-management
container_name: ticketing-rabbitmq
ports:
- "5672:5672" # AMQP port
- "15672:15672" # Management UI1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
Debugging Event Flow
To verify that events are being published correctly:
- Start the application and the Docker Compose services.
- Open the RabbitMQ Management UI in your browser (
http://localhost:15672). - Navigate to the Queues tab. You should see the queue defined by your
${rabbitmq.queue.name}property (e.g.,ticket.queue). The exchange and binding can be inspected under the Exchanges tab. - Use an API client (like
curlor Postman) to trigger an event. For example, create a new ticket:bashPOST /api/tickets Content-Type: application/json { "title": "Test Event Ticket", "description": "Testing RabbitMQ event publishing.", "status": "OPEN", "priority": "LOW", "requesterEmail": "tester@example.com" }1
2
3
4
5
6
7
8
9
10 - Return to the Management UI and refresh the Queues page. You should see the "Ready" message count for your queue increment by one.
- Click on the queue name, expand the "Get messages" panel, and click the "Get Message(s)" button. The payload of the message will be displayed, allowing you to inspect the JSON and verify its contents are correct.