Comunicação entre API .NET Core usando REBUS e RabbitMQ

Por

O que é REBUS? Segundo o Git Hub do REBUS, é uma “implementação de um barramento de serviço enxuto”, ou seja, faz toda a comunicação com sua fila de mensageria (RabbitMQ, MSMQ, Azure Service Bus, etc.). Ele faz toda a parte de envio e escuta de sua fila de mensageria. O projeto. Foi criado uma POC com uma API de pedidos e uma API de estoque. Ao efetuar o pedido, uma mensagem é enviada para a API de estoque para que remova a quantidade do item do seu estoque. Se tudo der certo a API estoque envia uma mensagem a API de pedidos informando que esta tudo Ok, caso contrario, estoque envia uma mensagem a API de pedidos informando que existe uma inconsistência. Temos 3 eventos nesse processo.

  • Retirar item do estoque
  • Confirmar retirada estoque
  • Informar inconsistência

Foi criado um docker-compose para facilitar e subir o banco de dados em MySql e o RabbitMQ. Va a pasta Docker do projeto com seu terminal e digite “docker-compose up” (Tenha o docker instalado na sua maquina). Caso não queria o Docker, fique a vontade para instalar o MySql e o RabbitMQ em sua maquina, ou fazer como desejar. :D. Para criar o banco de dados do projeto, vá a pasta de cada projeto (Pedidos.Api e Estoque.Api) com o seu terminal e rode o migrations com o comando: “dotnet ef database update”. Assim o seu banco de dados será construído e populado. Configurando o REBUS. Para utilização do REBUS é preciso baixar os pacotes.

<PackageReference Include="Rebus" Version="6.1.0" />
<PackageReference Include="Rebus.RabbitMQ" Version="6.0.0" />
<PackageReference Include="Rebus.ServiceProvider" Version="5.0.3" />
<PackageReference Include="Rebus.Wire" Version="5.0.0" />
  1. API Estoque

No projeto de estoque, vamos receber um evento para retirar item do estoque. Precisamos criar o evento e o handler para executar esse evento.

Vemos nessa classe uma simples implementação da mensagem que precisamos para retirar o item do estoque. Ela contem o Numero do Pedido e uma lista de itens com o ID do produto e a quantidade a ser retirada. Handler

    public class RemoverEstoqueEventHandler : IHandleMessages<RemoverEstoqueEvent>
    {
        readonly IProdutoRepository _produtoRepository;
        readonly IBus _bus;

        public RemoverEstoqueEventHandler(IProdutoRepository produtoRepository, IBus bus)
        {
            _produtoRepository = produtoRepository;
            _bus = bus;
        }

        public Task Handle(RemoverEstoqueEvent message)
        {
            //busca a lista de produtos que deve baixar o estoque.
            foreach(var item in message.Itens)
            {
                var produto = _produtoRepository.ObterPorId(item.ProdutoID);

                //Caso o produto nao foi encontrado lanco um evento para o pedido de inconformidade.
                if (produto == null)
                {
                    _bus.Publish(new EstoqueInconsistenteEvent(message.NumeroPedido, "Produto nao encontrato para o id informado."));
                    return Task.CompletedTask;
                }

                produto.DiminuirQuantidadeEstoque(item.Quantidade);

                //Vefico se o estoque ficou correto.
                if (!produto.EhValido())
                {
                    _bus.Publish(new EstoqueInconsistenteEvent(message.NumeroPedido, string.Join("| ", produto.Erros)));
                    return Task.CompletedTask;
                }

                _produtoRepository.Alterar(produto);
            }

            //Grava a baixa do estoque.
            _produtoRepository.Gravar();

            _bus.Publish(new EstoqueFinalizadoEvent(message.NumeroPedido));

            return Task.CompletedTask;
        }
    }

O Handler, implementa a interface IHandlerMensages que faz parte do pacote do Rebus. Na interface deve informar o evento correspondente. IHandlerMensages<RemoverEstoqueEvent> Essa interface exige que se implemente o Handler.  public Task Handle(RemoverEstoqueEvent message) Nesse método é onde ficará todo o seu código referente a esse evento. Lembrando que aqui e somente um exemplo. Pode ser referenciados serviços de domínios, serviço de aplicação ou qualquer outra coisa relevante ao seu negocio ou arquitetura implantada. Nesse Handler, eu simplesmente valido se o ID do produto existe no banco de dados e se a quantidade de estoque não ficou abaixo de zero.

var produto = _produtoRepository.ObterPorId(item.ProdutoID);

//Caso o produto nao foi encontrado lanco um evento para o pedido de inconformidade.
if (produto == null)
{
    _bus.Publish(new EstoqueInconsistenteEvent(message.NumeroPedido, "Produto nao encontrato para o id informado."));
    return Task.CompletedTask;
}

produto.DiminuirQuantidadeEstoque(item.Quantidade);

//Vefico se o estoque ficou correto.
if (!produto.EhValido())
{
    _bus.Publish(new EstoqueInconsistenteEvent(message.NumeroPedido, string.Join("| ", produto.Erros)));
    return Task.CompletedTask;
}

Caso alguma inconsistência ocorra eu envio outro evento ao sistema com o Numero do Pedido e o motivo da inconsistência. _bus.Publish(new EstoqueInconsistenteEvent(message.NumeroPedido, “Produto nao encontrato para o id informado.”)); Caso tudo esteja correto, eu envio outro evento _bus.Publish(new EstoqueFinalizadoEvent(message.NumeroPedido)); Esses eventos vão para a fila RabbitMQ para processamento na API de pedidos. Arquivo Setup do API Estoque. No arquivo setup devemos configurar o REBUS. No método public void ConfigureServices(IServiceCollection services) adicione o rebus.

            var fila = "fila_pedido";
            services.AddRebus(c => c.Transport(t => t.UseRabbitMq(Configuration.GetConnectionString("RabbitConnection"), fila)));
            services.AutoRegisterHandlersFromAssemblyOf<RemoverEstoqueEventHandler>();

Informamos ao REBUS que estamos usando o RabbitMQ para transporte de nossas mensagens, na fila_pedido. No método public void Configure(IApplicationBuilder app, IWebHostEnvironment env), vamos configurar o que o REBUS vai esperar na fila.

app.ApplicationServices.UseRebus(c =>
{
    c.Subscribe<RemoverEstoqueEvent>().Wait();
});

Ao cair na fila qualquer mensagem de RemoverEstoqueEvent ele vai automaticamente chamar o handler RemoverEstoqueEventHandler.  

  1. API Pedidos

Para o projetos de Pedidos, temos um evento que deveremos enviar (RemoverEstoqueEvent) e dois eventos que podemos receber (EstoqueFinalizadoEvent e EstoqueInconsistenteEvent). Na controller de pedidos, ao incluir o pedido nos lançamos o evento para remover o item do estoque.

[HttpPost("")]
public async Task<IActionResult> Incluir(Model.Pedido pedido)
{
    _pedidoRepository.Incluir(pedido);

    var evento = new RemoverEstoqueEvent(pedido.Numero);

    foreach (var item in pedido.Itens)
    {
        evento.Itens.Add(new RemoverEstoqueItem(item.ProdutoID, item.Quantidade));
    }

    await _bus.Publish(evento);

    return Ok($"Pedido {pedido.Numero} incluido com sucesso.");
}

Note que estamos utilizando a interface IBus do REBUS para disparar os eventos. await _bus.Publish(evento); Isso fará com que o evento enviado caia na fila do RabbitMQ para que a API Estoque receba a mensagem. Para que a API Pedidos receba as mensagens de estoque, fazemos como foi criado na API de Estoque, so que aqui em Pedidos, temos dois Handlers, pois podemos receber duas mensagens (EstoqueFinalizadoEvent e EstoqueInconsistenteEvent). Em pedido eu chamei de PeditoEventHandler no qual informo os dois eventos que devo receber.

public class PedidoEventHandler : 
        IHandleMessages<EstoqueFinalizadoEvent>,
        IHandleMessages<EstoqueInconsistenteEvent>

No EstoqueFinalizadoEvent eu somente informo o Numero do Pedido para finalizar o pedido.

public Task Handle(EstoqueFinalizadoEvent message)
{
    var pedido = _pedidoRepository.ObterPorId(message.NumeroPedido);
    pedido.Finalizar();
    _pedidoRepository.Alterar(pedido);

    return Task.CompletedTask;
}

No EstoqueInconsistenteEvent eu cancelo o pedido e informo o motivo da inconsistência.

public Task Handle(EstoqueInconsistenteEvent message)
{
    var pedido = _pedidoRepository.ObterPorId(message.NumeroPedido);
    pedido.Cancelar(message.MotivoCancelamento);
    _pedidoRepository.Alterar(pedido);

    return Task.CompletedTask;
}

Arquivo de Setup Aqui teremos uma configuração bem parecida com o da API Estoque, somente que vamos fazer a injeção de dependência para o Handler de pedidos e escutar na fila os dois eventos que temos.

var fila = "fila_pedido";

services.AddRebus(c => c
    .Transport(t => t.UseRabbitMq(Configuration.GetConnectionString("RabbitConnection"), fila)) //Configura o RabbitMQ
);
//Injeta as dependencias para o Handler de pedidos
services.AutoRegisterHandlersFromAssemblyOf<PedidoEventHandler>();
app.ApplicationServices.UseRebus(c =>
{
    c.Subscribe<EstoqueFinalizadoEvent>().Wait();
    c.Subscribe<EstoqueInconsistenteEvent>().Wait();
});

Conclusão 

Tentei mostrar o quanto pode ser simples configurar uma escuta e escrita de fila usando APIs, mas não se enganem quanto a complexidade de uma arquitetura baseada em Microsserviços. E como diria o Elemar Junior, “complexidade é custo”. O REBUS é bem eficiente, com ele podemos criar SAGAs, gravar as etapas em um banco de dados físico ou em memória (Redis), configurar logs e muito mais. Aqui vera a documentação do projeto que e bem interessante. Lembrando que todo o projeto esta em meu Git Hub e se trata somente de uma POC, não levando em conta padrões de projetos ou qualquer outra arquitetura. Qualquer duvida não hesite em me mandar um e-mail, terei o prazer em conversar sobre esse artigo ou qualquer outra tecnologia. dsdumba@gmail.com. Obrigado pela leitura e tamo junto!! [/av_textblock]

Deixe seu comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *