Appearance
Event-Driven Architecture
This document provides a detailed technical overview of the event-driven architecture (EDA) implemented within the IT Ticketing Service. The application leverages asynchronous messaging to decouple components and enable scalable, resilient integrations. This approach is central to broadcasting changes in the state of a ticket to other interested systems.
For a higher-level overview of the messaging infrastructure, refer to the RabbitMQ Integration Guide.
Event Publishing
The application publishes events to a message broker whenever a ticket's state is created or modified. This ensures that any changes are broadcast asynchronously, allowing other services to react without creating tight coupling with the ticketing service.
Triggering Event Publication
Events are published by the MessagePublisher service. This service is invoked from the core business logic responsible for handling ticket creation and updates.
- Creation: An event with type
CREATEDis published when a new ticket is successfully persisted to the database. - Update: An event with type
UPDATEDis published whenever an existing ticket is modified (e.g., status change, re-assignment, content edit).
Publishing Logic
The MessagePublisher class encapsulates the logic for sending events to RabbitMQ. It utilizes Spring AMQP's RabbitTemplate for a high-level abstraction over the messaging protocol. The exchange and routing key are externalized in the application configuration, allowing for flexibility across different environments.
java
// src/main/java/com/slalom/demo/ticketing/service/MessagePublisher.java
@Service
@RequiredArgsConstructor
@Slf4j
public class MessagePublisher {
private final RabbitTemplate rabbitTemplate;
// The exchange name is injected from application properties.
@Value("${rabbitmq.exchange.name}")
private String exchangeName;
// The routing key is also injected from application properties.
@Value("${rabbitmq.routing.key}")
private String routingKey;
public void publishTicketEvent(TicketEvent event) {
log.info("Publishing ticket event: {} for ticket ID: {}", event.getEventType(), event.getTicketId());
// The convertAndSend method handles serialization and sending the message.
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
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
TicketEvent Model
The TicketEvent class serves as the Data Transfer Object (DTO) for all ticket-related events. It defines a standardized contract for the information being published, ensuring that consumers can reliably interpret the message payload.
Data Structure
The model is designed to be a flat structure containing all relevant information about the ticket at the time the event occurred.
java
// src/main/java/com/slalom/demo/ticketing/event/TicketEvent.java
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class TicketEvent {
private String eventType; // e.g., "CREATED", "UPDATED"
private Long ticketId;
private String title;
private TicketStatus status;
private TicketPriority priority;
private String requesterEmail;
private String assignedTo;
private LocalDateTime timestamp; // Timestamp of when the event was generated
}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
The fields can be broken down into two categories:
| Field | Type | Description | Category |
|---|---|---|---|
eventType | String | The type of event that occurred. Expected values are CREATED and UPDATED. | Event Metadata |
timestamp | LocalDateTime | The server timestamp (UTC) when the event was generated. | Event Metadata |
ticketId | Long | The unique identifier of the ticket that this event pertains to. | Payload Data |
title | String | The current title of the ticket. | Payload Data |
status | TicketStatus | The current status of the ticket (e.g., OPEN, IN_PROGRESS). | Payload Data |
priority | TicketPriority | The current priority of the ticket (e.g., HIGH, CRITICAL). | Payload Data |
requesterEmail | String | The email address of the user who created the ticket. | Payload Data |
assignedTo | String | The email or identifier of the agent currently assigned to the ticket. | Payload Data |
Serialization
Events are serialized into JSON format before being sent to the message broker. This is explicitly configured in RabbitMQConfig using Spring AMQP's Jackson2JsonMessageConverter. This ensures interoperability with a wide range of potential consumers, regardless of their programming language or platform.
java
// src/main/java/com/slalom/demo/ticketing/config/RabbitMQConfig.java
@Configuration
public class RabbitMQConfig {
// ... other beans
@Bean
public MessageConverter messageConverter() {
// This bean ensures all outgoing objects are serialized to JSON.
return new Jackson2JsonMessageConverter();
}
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
// The RabbitTemplate is configured to use the JSON message converter.
rabbitTemplate.setMessageConverter(messageConverter());
return rabbitTemplate;
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Developer Note: Any consumer service must also be configured to use a compatible JSON deserializer and have access to the
TicketEventclass definition (or a compatible representation) to correctly parse incoming messages.
Message Flow
The application uses a simple but robust topology for routing messages from the publisher to potential consumers. This is defined declaratively as Spring beans in RabbitMQConfig.
Broker Topology
- Publisher: The
MessagePublisherservice initiates the flow. - Exchange: Messages are sent to a
TopicExchange. Using a topic exchange provides future flexibility, allowing for more complex, content-based routing by changing routing keys without altering the publisher's code. The exchange name is configured via${rabbitmq.exchange.name}. - Binding: A binding connects the exchange to a queue. The binding uses the routing key configured via
${rabbitmq.routing.key}. - Queue: A durable queue, configured via
${rabbitmq.queue.name}, receives the messages. Durability (durable=true) ensures that messages are not lost if the broker restarts.
The following beans from RabbitMQConfig.java define this topology:
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;
// 1. Defines a durable queue where messages will be stored.
@Bean
public Queue queue() {
return new Queue(queueName, true);
}
// 2. Defines a topic exchange to receive messages from the publisher.
@Bean
public TopicExchange exchange() {
return new TopicExchange(exchangeName);
}
// 3. Binds the queue to the exchange using the routing key.
// Messages sent to 'exchange' with 'routingKey' will be routed to '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
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
Event Consumers
This application is responsible only for publishing events. There are no consumers defined within this codebase. Downstream systems are expected to declare their own listeners on the ticket.queue (or bind their own queues to the ticket.exchange with the appropriate routing key) to process the events.
For more information on the core components of this application, see Core Concepts & Components.
Use Cases
The event-driven approach enables several key use cases by decoupling functionality from the core ticketing service.
Audit Logging: A separate auditing service can consume all
CREATEDandUPDATEDevents. It can persist a complete, immutable history of every ticket change into a dedicated audit store (like a data warehouse or another database) for compliance and security analysis. This removes the performance overhead of audit logging from the main transaction path.Real-time Notifications: A notification service can listen for events and trigger real-time user notifications. For example:
- On an
UPDATEDevent wherestatuschanges toRESOLVED, send an email to therequesterEmail. - On a
CREATEDevent, send a Slack message to the support channel associated with theassignedToteam.
- On an
Data Synchronization & Integration: The events can be used to keep other systems in sync with the ticketing data.
- BI/Analytics: A data pipeline can consume events to populate a data warehouse, enabling the creation of dashboards that track metrics like ticket resolution time, agent workload, and ticket volume by priority.
- CRM Integration: If a ticket is linked to a customer, events can trigger updates in a CRM system, providing a unified view of customer interactions.
Cache Invalidation: If a read-optimized caching layer exists in front of the ticketing service, it can listen for
UPDATEDevents to invalidate the cache for the correspondingticketId, ensuring data consistency.