Como escrever mensagens em uma fila do Azure Service Bus usando ASP.Net Core

This post has been republished via RSS; it originally appeared at: Microsoft Tech Community - Latest Blogs - .

Como escrever mensagens em uma fila do Azure Service Bus usando ASP.Net Core

O Azure Service Bus é um serviço PaaS (Platform as a service) que facilita a integração e desacoplamento de aplicações através de filas de mensageira e tópicos de pub-sub.

O objetivo deste artigo é demonstrar boas práticas de como escrever mensagens à uma fila do Azure Service Bus utilizando o SDK do Service Bus no ASP.NET Core.

 

Conceitos

Para facilitar a abordagem prática do artigo, os próximos tópicos são dedicados à introduzir os principais conceitos que envolvem o uso do Azure Service Bus.

 

Mensagens

O Azure Service Bus é um serviço de mensageiria, e faz o uso de mensagens para transferir dados entre as aplicações. Os dados podem ser qualquer tipo de informação, incluindo dados estruturados com os formatos: JSON, XML, Apache Avro, Plain Text.

 

Namespace

Um namespace é um container que agrupa todos os componentes de mensageiria (filas e tópicos). Um único namespace pode conter múltiplas filas e/ou tópicos.

 

Filas

As filas implementam a estratégia FIFO (First-In-First-Out), que garante que as mensagens sejam processadas na ordem em que foram enviadas.

No Service Bus as filas armazenam as mensagens até que algum consumidor a receba, depois que a aplicação recebe a mensagem pode ser: deletada ou entrar em um estado de lock.

Para entender melhor os métodos de consumo das Filas no Azure Service Bus, sugiro a leitura da documentação: Receive modes.

As mensagens são enviadas e recebidas em um fila, através das aplicações classificadas como:

  • Publicadores: Aplicações que enviam as mensagens, uma fila pode conter muitos publicadores.
  • Consumidores: Aplicações que recebem as mensagens, uma fila pode conter muitos consumidores.

O Azure Service Bus garante que uma mensagem não pode ser entregue a dois consumidores ao mesmo tempo, assim mensagem só será processada uma única vez.

 

Tópicos e assinaturas

Uma fila permite o processamento de uma mensagem por um único consumidor, em contrapartida um tópico prove uma estratégia onde uma mensagem pode ser consumida por vários consumidores simultaneamente, estabelecendo assim uma estratégia de publicação 1:N utilizando o pub-sub pattern.

Neste modelos contamos com:

  • Publicadores: Aplicações que enviam as mensagens.
  • Assinatura: Quando uma mensagem é publicada cada assinatura recebe uma cópia da mensagem.
  • Consumidores: Aplicações que recebem as mensagens, uma assinatura pode conter muitos consumidores.

Um publicador manda mensagens a um tópico da mesma forma que a mandaria para uma fila, mas um consumidor não recebe a mensagem diretamente do tópico. Ao invés disto os consumidores recebem mensagens das assinaturas.

Uma assinatura se assemelha a uma fila virtual que recebe cópias das mensagens enviadas ao tópico. Os consumidores recebem mensagens de uma assinatura da mesma forma que recebem mensagens de uma fila.

 

Criar um Namespace e uma Fila no Azure Service Bus

1 - Abra um terminal, e realize o login no Azure através do comando:

az login

2 - Crie um grupo de recursos.

az group create -n gc-artigo-servicebus -l brazilsouth

3 - Crie um Namespace no Service Bus.

az servicebus namespace create -g gc-artigo-servicebus -n sb-artigo -l brazilsouth

4 - Crie uma fila nomeada weatherforecast.

az servicebus queue create -g gc-artigo-servicebus -n weatherforecast  --namespace-name  sb-artigo

 

Projeto

crie uma nova Web API ASP.NET Core através do comando:

dotnet new webapi artigo-aspnetcore

Instale o pacote Azure.Messaging.ServiceBus:

dotnet add package Azure.Messaging.ServiceBus

O Azure.Messaging.ServiceBus é o pacote oficial da Microsoft, ele abstrai toda a complexidade de utilização do Azure Service Bus no .NET.

Neste artigo utilizaremos majoritariamente duas classes desta biblioteca:

Entenda como o Azure Service Bus funciona usa o AMQP em: AMQP 1.0 in Azure Service Bus and Event Hubs protocol guide.

Instale o pacote Microsoft.Extensions.Azure:

dotnet add package Microsoft.Extensions.Azure

Com o Microsoft.Extensions.Azure poderemos injetar o ServiceBusClient como uma dependência.

Fazendo isso não precisaremos nos preocupar com o ciclo de vida do client, isso reduz o risco do uso indevido das classes que precisam implementar o Dispose Pattern.

Na linha 4 da classe Program.cs adicione o código:

    builder.Services.AddAzureClients(azBuilder =>
    {
        azBuilder.AddServiceBusClient(builder.Configuration.GetConnectionString("ServiceBus"));
    });

Agora precisamos da ConnectionString do Namespace que criamos anteriormente, faremos isso através do comando:

az servicebus namespace authorization-rule keys list --resource-group gc-artigo-servicebus --namespace-name sb-artigo --name RootManageSharedAccessKey --query primaryConnectionString --output tsv

Copie o resultado deste comando.

Em appsettings.json adicione a seção de ConnectionStrings e uma connection string chamada ServiceBus:

 "ConnectionStrings": {
    "ServiceBus":""
  }

Cole a string de conexão criada anteriormente no valor da ServiceBus.

Com essas configurações ja podemos partir para o desenvolvimento do envio de mensagens para o Service Bus.

Na raiz do projeto crie uma nova pasta chamada Services.

Dentro desta pasta, crie uma classe chamada WeatherForecastService.cs, nesta escreva o código:

using System.Text.Json;
using Azure.Messaging.ServiceBus;

namespace servicebusapi.Services
{
    public class WeatherForecastService
    {
        private const string queueName = "weatherforecast";
        private readonly ServiceBusClient _serviceBusClient;
        private readonly ILogger<WeatherForecastService> _logger;

        private static readonly string[] Summaries = new[]
        {
            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
        };

        public WeatherForecastService(ServiceBusClient serviceBusClient, ILogger<WeatherForecastService> logger)
        {
            _serviceBusClient = serviceBusClient;
            _logger = logger;
        }


        public async Task<string> Get()
        {

        }

    }
}

Repare que essa classe espera uma instância do tipo ServiceBusClient no seu método construtor, isso só é possível pois a registramos como um dependência utilizando o pacote Microsoft.Extensions.Azure.

No método WeatherForecastService.Get escreva o código:

    public async Task<string> Get()
    {
        var casts = Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateTime.Now.AddDays(index),
            TemperatureC = Random.Shared.Next(-20, 55),
            Summary = Summaries[Random.Shared.Next(Summaries.Length)]
        }).ToArray();

        string jsonCasts = JsonSerializer.Serialize(casts);

        var sender = _serviceBusClient.CreateSender(queueName);

        try { await sender.SendMessageAsync(new ServiceBusMessage(jsonCasts)); }
        catch (ServiceBusException ex) {
             _logger.LogError($"Service Bus Error \n Reason: {ex.Reason} \n Data: {ex.Data} \n Message: {ex.Message} "); 
        }
        finally
        {
            await sender.DisposeAsync();
        }

        return jsonCasts;
    }

Precisamos configurar a classe WeatherForecastService como uma dependência, neste caso o ciclo de vida mais adequado é o Scoped.

Mais informações sobre injeção de dependências no ASP.NET Core em: Injeção de dependência no ASP.NET Core.

Na linha 12 da classe Program.cs adicione o código:

builder.Services.AddScoped<WeatherForecastService>();

Por fim a Program.cs deve ficar assim:

using Microsoft.Extensions.Azure;
using servicebusapi.Services;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddAzureClients(azBuilder =>
{
    azBuilder.AddServiceBusClient(builder.Configuration.GetConnectionString("ServiceBus"));
});

builder.Services.AddScoped<WeatherForecastService>();

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Substitua o código da classe WeatherForecastController pelo código:

using Microsoft.AspNetCore.Mvc;
using servicebusapi.Services;

namespace servicebusapi.Controllers;

[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    private readonly WeatherForecastService _service;

    public WeatherForecastController(WeatherForecastService service)
    {
        _service = service;
    }

    [HttpGet(Name = "GetWeatherForecast")]
    public async Task<IActionResult> Get()
    {
        var response = await _service.Get();

        if(!string.IsNullOrEmpty(response)) return Ok(response);

        return StatusCode(500);
    }
}

Agora o construtor da Controller vai receber uma instância do tipo WeatherForecastService, e o método WeatherForecastController.Get apenas irá chamar o método WeatherForecastService.Get e retornar http status code 200/500.

Rode o projeto através do comando:

dotnet run -c Release

Em seguida teste o projeto através do comando:

curl https://localhost:7196/WeatherForecast

Se tudo estiver correto você obterá um resultado parecido com:

StatusCode        : 200
StatusDescription : OK
Content           : [{"date":"2022-04-30T11:26:46.5182357-03:00","temperatureC":7,"temperatureF":44,"summary":"Mild
                    "},{"date":"2022-05-01T11:26:46.5182438-03:00","temperatureC":-1,"temperatureF":31,"summary":"S
                    weltering"...
RawContent        : HTTP/1.1 200 OK
                    Transfer-Encoding: chunked
                    Content-Type: application/json; charset=utf-8
                    Date: Fri, 29 Apr 2022 14:26:45 GMT
                    Server: Kestrel

                    [{"date":"2022-04-30T11:26:46.5182357-03:00","temper...
Forms             : {}
Headers           : {[Transfer-Encoding, chunked], [Content-Type, application/json; charset=utf-8], [Date, Fri, 29
                    Apr 2022 14:26:45 GMT], [Server, Kestrel]}
Images            : {}
InputFields       : {}
Links             : {}
ParsedHtml        : mshtml.HTMLDocumentClass
RawContentLength  : 498

Para saber se a mensagem está na fila, rode o comando:

 az servicebus queue list --resource-group gc-artigo-servicebus --namespace-name sb-artigo

Analise o resultado:

[
  {
    "accessedAt": "2022-05-01T01:31:30.838890+00:00",
    "autoDeleteOnIdle": "10675199 days, 2:48:05.477581",
    "countDetails": {
      "activeMessageCount": 1,
      "deadLetterMessageCount": 0,
      "scheduledMessageCount": 0,
      "transferDeadLetterMessageCount": 0,
      "transferMessageCount": 0
    },
    "createdAt": "2022-04-30T18:35:06.357492+00:00",
    "deadLetteringOnMessageExpiration": false,
    "defaultMessageTimeToLive": "10675199 days, 2:48:05.477581",
    "duplicateDetectionHistoryTimeWindow": "0:10:00",
    "enableBatchedOperations": true,
    "enableExpress": false,
    "enablePartitioning": false,
    "forwardDeadLetteredMessagesTo": null,
    "forwardTo": null,
    "id": "/subscriptions/54345470-2d94-4fe7-8504-14087a4b0326/resourceGroups/gc-artigo-servicebus/providers/Microsoft.ServiceBus/namespaces/sb-artigo/queues/weatherforecast",
    "location": "Brazil South",
    "lockDuration": "0:01:00",
    "maxDeliveryCount": 10,
    "maxSizeInMegabytes": 1024,
    "messageCount": 5,
    "name": "weatherforecast",
    "requiresDuplicateDetection": false,
    "requiresSession": false,
    "resourceGroup": "gc-artigo-servicebus",
    "sizeInBytes": 3727,
    "status": "Active",
    "type": "Microsoft.ServiceBus/Namespaces/Queues",
    "updatedAt": "2022-04-30T18:35:06.357492+00:00"
  }
]

O objeto mais importante deste JSON é o countDetails.

 "countDetails": {
      "activeMessageCount": 1,
      "deadLetterMessageCount": 0,
      "scheduledMessageCount": 0,
      "transferDeadLetterMessageCount": 0,
      "transferMessageCount": 0
    },

Perceba que o campo activeMessageCount é igual a 1, o que significa que tudo funcionou como o esperado, continue executando o curl, e veja esse numero subir a cada execução.

 

Conclusão

Utilizar o Azure Service Bus no ASP.NET Core é extremamente simples, e de fácil implementação, entender os conceitos neste artigo é importante pois ele servirá de base para outros artigos mais profundos, como por exemplo: análise de performance no Azure Service Bus.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.