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" />
- 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.
- 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!!