Build Customer Support Ticketing System with Azure Service Bus, C# and .NET
A basic example of using Azure Service Bus with C# and .NET
A Fictional Ticketing System Example
In the digital realm, ensuring seamless communication between applications is crucial for delivering effective customer support.
Azure Service Bus, with its robust messaging capabilities, offers a powerful solution for building scalable and resilient systems.
Our fictional and very basic example involves a customer support ticketing system, demonstrating the potential of Service Bus. All code related to this example is available on GitHub (click here) for developers to explore and use.
Synchronous Communication
In traditional synchronous communication models, each request necessitates an immediate response before the next interaction can begin. This request-response pattern is analogous to a strict turn-taking conversation, where each speaker must wait for the other to finish before proceeding.
In scenarios with high volumes of requests, this can lead to significant wait times and inefficient resource utilization, as the service handling the requests must process each one before moving on to the next, creating a potential bottleneck.
Asynchronous Communication
Asynchronous communication, facilitated by messaging systems (sometimes called message brokers) like Azure Service Bus, addresses these constraints by allowing requests to be handled independently of their responses.
Requests are placed into a queue, where they can be processed in due course without requiring the sender to wait for an immediate response. This approach enhances system scalability, improves resource allocation, and ensures that high demand does not compromise system performance or availability.
What is Azure Service Bus?
Azure Service Bus is a cloud-based messaging service that enables independent applications to exchange data and information reliably and securely. Supporting advanced messaging architectures such as queues, topics, and subscriptions, Service Bus is ideal for applications requiring high levels of scalability and reliability.
Setting up Azure Service Bus and Creating a Queue
Before integrating Service Bus into our ticketing system, we need to set it up within the Azure portal. Here’s how you can create a new Service Bus namespace and a queue named tickets-queue
:
Log in to the Azure Portal:
Visit
https://portal.azure.com
and sign in with your credentials.
Create a new Service Bus namespace:
Navigate to the ‘Create a resource’ button (the plus icon in the top left corner).
Search for and select ‘Service Bus’.
Click ‘Create’.
Provide the necessary details such as the Name, Subscription, Resource Group, and Location.
Choose the pricing tier (Basic, Standard, or Premium) based on your needs.
Click ‘Review + create’, and then ‘Create’ to deploy the Service Bus namespace.
Create a queue named
tickets-queue
:Once your namespace is ready, go to it by clicking on ‘Go to resource’.
In the namespace window, select ‘Queues’ under ‘Entities’.
Click ‘+ Queue’ to add a new queue.
Name the queue
tickets-queue
.Configure settings like message time-to-live (TTL), lock duration, and max delivery count as needed.
Click ‘Create’ to finalize the queue.
Practical Implementation of Service Bus in a Ticketing System
In this fictional scenario, the system architecture includes:
API for Ticket Requests (TicketReceivingService): This service collects new customer support ticket requests and forwards them to Azure Service Bus, ensuring that the frontend remains responsive.
Console Application for Processing Tickets (TicketHandlerService): This service listens to the
tickets-queue
on Service Bus. It processes incoming tickets efficiently, maintaining the performance of the API.
Let’s take a quick look at each of them.
TicketReceivingService This project is responsible for receiving incoming ticket requests through an API endpoint.
using Azure.Messaging.ServiceBus;
using System.Text.Json;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseHttpsRedirection();
app.MapPost("/tickets", async (Ticket ticket) =>
{
Console.WriteLine($"Ticket received: {ticket}");
// Publish the ticket to Azure Service Bus
ServiceBusClient client = new ServiceBusClient(builder.Configuration["ServiceBus:ConnectionString"]);
ServiceBusSender sender = client.CreateSender("tickets-queue");
try
{
await sender.SendMessageAsync(new ServiceBusMessage(JsonSerializer.Serialize(ticket)));
Console.WriteLine($"Ticket message has been published to the queue.");
}
catch
{
Console.WriteLine("Error occured while sending the message to the queue!");
throw;
}
finally
{
await sender.DisposeAsync();
await client.DisposeAsync();
}
return ticket;
});
app.Run();
Note that you need to get your service bus connection string from Azure and add it either as user-secret like I did here or by hardcoding it. For production you need to use either KeyVault or RBAC and managed identity so you don’t have any sensitive information in your code.
ServiceBusClient client = new ServiceBusClient(builder.Configuration["ServiceBus:ConnectionString"]);
The Program.cs
file contains our one and only POST endpoint that would receive tickets and push them to Service Bus.
app.MapPost("/tickets", async (Ticket ticket) =>
{
Console.WriteLine($"Ticket received: {ticket}");
// Publish the ticket to Azure Service Bus
ServiceBusClient client = new ServiceBusClient(builder.Configuration["ServiceBus:ConnectionString"]);
ServiceBusSender sender = client.CreateSender("tickets-queue");
try
{
await sender.SendMessageAsync(new ServiceBusMessage(JsonSerializer.Serialize(ticket)));
Console.WriteLine($"Ticket message has been published to the queue.");
}
catch
{
Console.WriteLine("Error occured while sending the message to the queue!");
throw;
}
finally
{
await sender.DisposeAsync();
await client.DisposeAsync();
}
return ticket;
});
The Ticket.cs
file defines the structure of a ticket object.
internal record Ticket
{
public required string Title { get; set; }
public required string Description { get; set; }
public required string Email { get; set; }
public required string Phone { get; set; }
public override string ToString()
{
return $"Title: {Title}, Description: {Description}, Email: {Email}, Phone: {Phone}";
}
}
The API.md
file contains a markdown file with API documentation, and TicketReceivingService.http
provides an HTTP request examples for testing the API.
For our project to be able to work with Service Bus we only need to install the Azure.Messaging.ServiceBus package.
Here’s a simple json of the ticket.
{
"Title": "Error when purchasing on your site.",
"Description": "Whenever I try to purchase something I get an error!",
"Email": "johndoe@example.com",
"Phone": "1234567890"
}
TicketHandlerService This service is in charge of processing the tickets dequeued from Azure Service Bus. The Program.cs
file contains the logic for connecting to the Service Bus, listening for new messages in the tickets-queue
, and processing them.
using Azure.Messaging.ServiceBus;
using Microsoft.Extensions.Configuration;
var config = new ConfigurationBuilder().AddUserSecrets<Program>().Build();
var connectionString = config.GetSection("ServiceBus")["ConnectionString"];
ServiceBusClient client;
ServiceBusProcessor processor;
var clientOptions = new ServiceBusClientOptions()
{
TransportType = ServiceBusTransportType.AmqpWebSockets
};
client = new ServiceBusClient(connectionString);
processor = client.CreateProcessor("tickets-queue", new ServiceBusProcessorOptions());
try
{
processor.ProcessMessageAsync += MessageHandler;
processor.ProcessErrorAsync += ErrorHandler;
await processor.StartProcessingAsync();
Console.WriteLine("Wait for a minute and then press any key to end the processing");
Console.ReadKey();
await processor.StopProcessingAsync();
}
finally
{
await processor.DisposeAsync();
await client.DisposeAsync();
}
async Task MessageHandler(ProcessMessageEventArgs args)
{
string body = args.Message.Body.ToString();
Console.WriteLine($"New ticket received: {body}");
await args.CompleteMessageAsync(args.Message);
}
Task ErrorHandler(ProcessErrorEventArgs args)
{
Console.WriteLine(args.Exception.ToString());
return Task.CompletedTask;
}
Here, again, you need to add your Service Bus connection string.
var config = new ConfigurationBuilder().AddUserSecrets<Program>().Build();
var connectionString = config.GetSection("ServiceBus")["ConnectionString"];
Since it's a console application, it might be running in the background or be scheduled to run at certain intervals to process the ticket requests.
These projects represent the core components of a ticketing system, with one service accepting and forwarding ticket requests to a queue, and the other handling and processing those requests, sorting them, assigning them to different teams, and etc.
Benefits of Using Service Bus
Decoupling of Services: The API and the console application communicate via messages in Service Bus, allowing them to operate independently.
Reliability: Service Bus ensures that every ticket is processed, handling failures and retries gracefully.
Scalability: Additional processors can be added to handle higher volumes of tickets without impacting the API.
Conclusion
Utilizing Azure Service Bus in this fictional ticketing system underscores its capability to optimize request processing, enhance reliability, and scale on demand. Also, as you can see, the code for a basic implementation is short and simple, making creating an MVP or a basic implementation just to test this architecture easy and fast to implement. All project code is available on GitHub for you to take a look.
Introduction to Azure Service Bus (Microsoft Learn)