En este artículo, exploraremos la implementación práctica de la garantía de orden de mensajes en Azure ServiceBus, un requisito común en escenarios empresariales donde los mensajes deben procesarse secuencialmente.
La necesidad de mantener el orden de los mensajes es fundamental en muchos sistemas distribuidos. Por ejemplo, al procesar órdenes de venta, es crucial que las operaciones se realican en el mismo orden en que fueron enviadas (FIFO - First In, First Out).
A continuación, presentaremos un ejemplo práctico utilizando colas de mensajes en Azure ServiceBus para procesar órdenes de venta de menera secuencial.
- Configuración inicial del entorno
Utilizaremos un espacio de nombres (Namespace) ya existente en Azure:
- Nombre del espacio de nombres: servicebustest
- Nombre de la cola de órdenes: OrderQueue
- Implementación de una utliidad para ServiceBus
Creamos una clase auxiliar que facilitará la interacción con las colas de ServiceBus:
using Microsoft.ServiceBus;
using Microsoft.ServiceBus.Messaging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
namespace AzureMensajeria.Ordenada
{
/// <summary>
/// Clase utilitaria para operaciones con ServiceBus
/// </summary>
class HerramientasServiceBus
{
// Nombre del espacio de nombres
private static readonly string nombreEspacio = "servicebustest";
/// <summary>
/// Crea una nueva cola
/// </summary>
/// <param name="nombreCola">Nombre de la cola</param>
/// <param name="conSesion">Indica si requiere sesiones</param>
public void CrearCola(string nombreCola, bool conSesion = true)
{
var clienteEspacio = NamespaceManager.Create();
if (clienteEspacio.QueueExists(nombreCola))
{
clienteEspacio.DeleteQueue(nombreCola);
}
var cola = new QueueDescription(nombreCola) { RequiresSession = conSesion };
clienteEspacio.CreateQueue(cola);
}
/// <summary>
/// Elimina una cola existente
/// </summary>
/// <param name="nombreCola">Nombre de la cola</param>
public void EliminarCola(string nombreCola)
{
var clienteEspacio = NamespaceManager.Create();
if (clienteEspacio.QueueExists(nombreCola))
{
clienteEspacio.DeleteQueue(nombreCola);
}
}
/// <summary>
/// Obtiene un cliente para la cola especificada
/// </summary>
/// <returns>Cliente de la cola</returns>
public QueueClient ObtenerClienteCola(string nombreCola, bool conSesion = false, ReceiveMode modo = ReceiveMode.ReceiveAndDelete)
{
return QueueClient.Create(nombreCola, modo);
}
/// <summary>
/// Obtiene un cliente para recepción de mensajes
/// </summary>
/// <returns>Cliente de recepción</returns>
public QueueClient ObtenerClienteRecepcion(string nombreCola, ReceiveMode modo = ReceiveMode.PeekLock)
{
var clienteEspacio = NamespaceManager.Create();
return QueueClient.Create(nombreCola, modo);
}
/// <summary>
/// Crea un mensaje a partir de un objeto serializable
/// </summary>
/// <param name="objetoSerial">Objeto serializable</param>
/// <returns>Mensajes de ServiceBus</returns>
public BrokeredMessage ConstruirMensaje(Object objetoSerial)
{
var serializador = new DataContractSerializer(objetoSerial.GetType(),
new DataContractSerializerSettings() { IgnoreExtensionDataObject = true, PreserveObjectReferences = false });
var mensaje = new BrokeredMessage(objetoSerial);
mensaje.Properties.Add("Tipo", objetoSerial.GetType().ToString());
return mensaje;
}
}
}
- Definición de la entidad de Orden de Venta
Creamos la clase que representará las órdenes de venta en nuestro sistema:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AzureMensajeria.Ordenada
{
/// <summary>
/// Entidad para órdenes de venta
/// </summary>
public class OrdenVenta
{
/// <summary>
/// Identificador único de la orden
/// </summary>
public string IDOrden { get; set; }
/// <summary>
/// Código de la orden
/// </summary>
public string Codigo { get; set; }
/// <summary>
/// Fecha de creación
/// </summary>
public DateTime FechaCreacion { get; set; }
/// <summary>
/// Monto total
/// </summary>
public Decimal MontoTotal { get; set; }
/// <summary>
/// Identificador del producto
/// </summary>
public int IDProducto { get; set; }
}
}
- Envío secuencial de mensajes
Implementamos el proceso para enviar mensajes en orden a la cola:
private static readonly string nombreCola = "OrderQueue";
/// <summary>
/// Envía mensajes a la cola
/// </summary>
private static void EnviarMensajes()
{
var utilidadesSB = new HerramientasServiceBus();
// Crear la cola si no existe
utilidadesSB.CrearCola(nombreCola, false);
// Cliente para envío de mensajes
var clienteEnvio = utilidadesSB.ObtenerClienteCola(nombreCola);
// Enviar 10 mensajes en orden
for (int i = 0; i < 10; i++)
{
var orden = new OrdenVenta() {
IDOrden = i.ToString(),
Codigo = "OrdenVenta_" + i,
FechaCreacion = DateTime.Now,
IDProducto = 17967,
MontoTotal = new decimal(19999)
};
var mensaje = utilidadesSB.ConstruirMensaje(orden);
clienteEnvio.Send(mensaje);
Console.WriteLine(string.Format("Enviado mensaje {0}: {1}", i, mensaje.MessageId));
}
Console.WriteLine("¡Envío completado!");
}
- Recepción secuencial de mensajes
Implementamos el proceso para recibir y procesar los mensajes en el orden correcto:
private static readonly string nombreCola = "OrderQueue";
/// <summary>
/// Recibe mensajes de la cola
/// </summary>
private static void RecibirMensajes()
{
int contador = 0;
BrokeredMessage mensaje = null;
var utilidadesSB = new HerramientasServiceBus();
var clienteRecepcion = utilidadesSB.ObtenerClienteRecepcion(nombreCola, ReceiveMode.ReceiveAndDelete);
while ((mensaje = clienteRecepcion.Receive(TimeSpan.FromMilliseconds(3))) != null)
{
Console.WriteLine(string.Format("Recibido mensaje {0}: {1}", contador, mensaje.MessageId));
contador++;
}
Console.WriteLine("¡Recepción completada!");
}
Al ejecutar estos ejemplos, podemos observar que Azure ServiceBus garantiza que los mensajes se envíen y reciban en el mismo orden en que fueron creados, cumpliendo con el requisito FIFO (First In, First Out) para nuestro procesamiento de órdenes de venta.