Olá Devs!!

Hoje vamos falar sobre comunicação de APIs .NET Core utilizando eventos com REBUS e RabbitMQ.

Esse artigo sugere que conheça um pouco sobre WebAPI e criação de eventos.

Todo o código fonte desse projeto encontra – se no meu Git Hub.

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.

public class RemoverEstoqueEvent : Message
{
    public RemoverEstoqueEvent()
    {
        Itens = new List<RemoverEstoqueItem>();
    }

    public RemoverEstoqueEvent(int numeroPedido)
    {
        NumeroPedido = numeroPedido;
        Itens = new List<RemoverEstoqueItem>();
    }

    public int NumeroPedido { get; set; }
    public List<RemoverEstoqueItem> Itens { get; set; }
}

public class RemoverEstoqueItem
{
    public RemoverEstoqueItem()
    {

    }
    public RemoverEstoqueItem(int produtoID, int quantidade)
    {
        ProdutoID = produtoID;
        Quantidade = quantidade;
    }

    public int ProdutoID { get; set; }
    public int Quantidade { get; set; }
}

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. [email protected].

Obrigado pela leitura e tamo junto!!