Comunicação entre API .NET Core usando REBUS e RabbitMQ
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.
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. dsdumba@gmail.com. Obrigado pela leitura e tamo junto!! [/av_textblock]