ASP.NET Core - Usando RabbitMQ - I
 Hoje vamos recordar como usar o RabbitMQ em uma aplicação Asp.NET Core Web API.


Neste artigo, discutiremos o RabbitMQ Message Queue e sua implementação usando uma aplicação ASP.NET Core Web API da plataforma .NET como um produtor de mensagens e um aplicativo console como consumidor de mensagens.

 


 

O que é o RabbitMQ ?
 

O RabbitMQ é um sistema de mensagens de código aberto (open-source) que atua como um intermediário (broker) de mensagens em sistemas distribuídos. Ele implementa o protocolo de mensagens avançado chamado AMQP (Advanced Message Queuing Protocol) e facilita a comunicação entre diferentes componentes de software, distribuídos em redes ou em ambientes onde há a necessidade de trocar informações de forma assíncrona.


Assim podemos dizer que :

Abaixo temos um diagrama que mostra de forma resumida como o RabbitMQ atua :

 

   

Existem vários microsserviços em execução em segundo plano enquanto usamos o site de comércio eletrônico. Existe um serviço que cuida dos detalhes do pedido e outro serviço que cuida dos detalhes de pagamento e recibos.


Suponha que fizemos um pedido. Nesse momento, o serviço de pedidos iniciará e processará nosso pedido. Depois de obter os detalhes do pedido, ele enviará os dados para o serviço de pagamento, que receberá o pagamento e enviará o comprovante de pagamento aos usuários finais.

Nesse caso, pode haver a possibilidade de ocorrer algum problema técnico no serviço de pagamento. Caso o usuário não tenha recebido o comprovante de pagamento devido a isso, o usuário será impactado e conectado com a equipe de suporte para tentar saber o status do pedido.

Pode haver outro cenário no lado do usuário (consumidor). Talvez devido a algum problema técnico, o usuário sai do aplicativo quando o pagamento está em andamento. Mas ele não receberá nenhum detalhe de recebimento após o pagamento ser processado com sucesso pelos serviços de back-end.

Nesses cenários, o RabbitMQ desempenha um papel essencial para processar mensagens na fila de mensagens. Assim, quando o consumidor estiver online, ele receberá aquela mensagem de recebimento do pedido da fila de mensagens, produzida pelo produtor sem impactar a aplicação web.

Todos esses exemplos são apenas para fins de compreensão. Existem muitos cenários nos quais o RabbitMQ pode desempenhar um papel importante ao usar vários microsserviços. Às vezes, o RabbitMQ é usado totalmente para balanceamento de carga entre vários serviços ou para muitos outros propósitos.

 

Benefícios de usar RabbitMQ

Há muitos benefícios em usar um Message Broker para enviar dados ao consumidor. A seguir, falaremos sobre alguns desses benefícios.

Alta Disponibilidade

Quando vários microsserviços são usados pelo aplicativo, se um dos microsserviços for interrompido por motivos técnicos naquele momento, a mensagem nunca será perdida. Em vez disso, ele persiste no servidor RabbitMQ. Depois de algum tempo, quando nosso serviço começar a funcionar, ele se conectará ao RabbitMQ e receberá a mensagem pendente facilmente.

Escalabilidade

Quando utilizamos o RabbitMQ, nesse momento nossa aplicação não depende de apenas um servidor e máquina virtual para processar uma requisição. Se nosso servidor estiver parado nesse momento, o RabbitMQ transferirá nossa carga de aplicativo para outro servidor que tenha os mesmos serviços em execução em segundo plano.

 

Como exemplo vamos criar um projeto Web API que vai ser o produtor de mensagens e a seguir vamos incluir um projeto do tipo Console que vai ser o consumidor das mensagens.

 

Recursos usados:

Criando o projeto Web API

Abra o VS 2022 Community e selecione a opção Create New Project;

Selecione o template ASP.NET Core Web API e informe o nome ApiRabbitMq;

A seguir defina as configurações conforme a figura abaixo e clique em Create;

Vamos incluir no projeto as referências aos seguintes pacotes:

Podemos fazer isso usando o comando : dotnet add package <nome-pacote> ou via menu Tools-> ...-> Manage Nuget Packages for Solution e na guia Browse selecionar os pacotes.

A seguir vamos criar no projeto as seguintes pastas:

Vamos iniciar criando a entidade Product que representa o nosso modelo de domínio na pasta Models:

 

public class Product
{
    public int ProductID { get; set; }
    public string? Name { get; set; }
    public string? Descriptoin { get; set; }
    public int Stock { get; set; }
}

 

A seguir defina a string de conexão no arquivo appsettings.json:

 

{
  "ConnectionStrings": {
    "DefaultConnection": "Data Source=SQLEXPRESS;Initial Catalog=RabitMQDB;Integrated Security=True"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}


Na pasta Context vamos criar o arquivo de contexto  do EF Core - AppDbContext -  que herda de DbContext e define o mapeamento ORM :
 

public class AppDbContext : DbContext
{
    protected readonly IConfiguration Configuration;
    public AppDbContext(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    protected override void OnConfiguring(DbContextOptionsBuilder options)
    {
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
    }

    public DbSet<Product> Products{get;set;}
}

 

 

Vamos criar na pasta Services a interface IProductService :

 

public interface IProductService
{
    public IEnumerable<Product> GetProductList();
    public Product GetProductById(int id);
    public Product AddProduct(Product product);
    public Product UpdateProduct(Product product);
    public bool DeleteProduct(int Id);
}

 

A seguir crie a classe ProductService que implementa esta interface e que vai definir as operações a serem realizadas:
 

using ApiRabbitMq.Context;
using ApiRabbitMq.Models;

namespace ApiRabbitMq.Services;

public class ProductService : IProductService
{
    private readonly AppDbContext _context;
    public ProductService(AppDbContext dbContext)
    {
        _context = dbContext;
    }
    public IEnumerable<Product> GetProductList()
    {
        return _context.Products.ToList();
    }
    public Product GetProductById(int id)
    {
        return _context.Products.Where(x => x.ProductId == id).FirstOrDefault();
    }
    public Product AddProduct(Product product)
    {
        var result = _context.Products.Add(product);
        _context.SaveChanges();
        return result.Entity;
    }
    public Product UpdateProduct(Product product)
    {
        var result = _context.Products.Update(product);
        _context.SaveChanges();
        return result.Entity;
    }
    public bool DeleteProduct(int Id)
    {
        var filteredData = _context.Products.Where(x => x.ProductId == Id).FirstOrDefault();
        var result = _context.Remove(filteredData);
        _context.SaveChanges();
        return result != null ? true : false;
    }
}

Na pasta RabbitMQ vamos implementar o serviço do Message Broker para produzir mensagens. Crie a interface IRabbitMQProducer :

public interface IRabbitMQProducer
{
    void SendProductMessage<T>(T message);
}

A seguir crie a classe RabbitMQProducer que implementa esta interface :

using RabbitMQ.Client;
using System.Text;
using System.Text.Json;

namespace ApiRabbitMq.RabbitMQ;

public class RabbitMQProducer : IRabbitMQProducer
{
    public void SendProductMessage<T>(T message)
    {
        //Definição do servidor Rabbit MQ : usamos uma imagem Docker
        var factory = new ConnectionFactory
        {
            HostName = "localhost"
        };
        //Cria uma conexão RabbitMQ usando uma factory
        var connection = factory.CreateConnection();
        //Cria um channel com sessão e model
        using  var channel = connection.CreateModel();
        //declara a fila(queue) a seguir o nome e propriedades
        channel.QueueDeclare("product", exclusive: false);
        //Serializa a mensagem
        var json = JsonSerializer.Serialize(message);
        var body = Encoding.UTF8.GetBytes(json);
        //Põe os dados na fila : product
        channel.BasicPublish(exchange: "", routingKey: "product", body: body);
    }
}

Agora vamos registrar os serviços na classe Program:

using ApiRabbitMq.Context;
using ApiRabbitMq.RabbitMQ;
using ApiRabbitMq.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

//registra os serviços no container
builder.Services.AddDbContext<AppDbContext>();
builder.Services.AddScoped<IProductService, ProductService>();
builder.Services.AddScoped<IRabbitMQProducer, RabbitMQProducer>();

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();

 

Agora podemos aplicar o Migrations para criar o banco de dados RabitMQDB e a tabela Products no SQL Server :

 

dotnet ef migrations add Inicial

dotnet ef database update

 

Ao final podemos consulta a janela Server Explorer do VS 2022 e verificar o banco e a tabela criados:
 

 

Executando o projeto neste momente teremos na interface do Swagger os endpoints criados para nossa API:

 

 

Na próxima parte do artigo vou incluir a aplicação Console no projeto e enviar e receber mensagens usando o RabbitMQ.

 

"Mas se não derdes ouvidos à voz do Senhor, e antes fordes rebeldes ao mandado do Senhor, a mão do Senhor será contra vós, como o era contra vossos pais."
1 Samuel 12:15

 

Porque um menino nos nasceu, um filho se nos deu, e o principado está sobre os seus ombros, e se chamará o seu nome: Maravilhoso, Conselheiro, Deus Forte, Pai da Eternidade, Príncipe da Paz.

Isaías 9:6
Porque um menino nos nasceu, um filho se nos deu, e o principado está sobre os seus ombros, e se chamará o seu nome: Maravilhoso, Conselheiro, Deus Forte, Pai da Eternidade, Príncipe da Paz.

Isaías 9:6

Referências:


José Carlos Macoratti