Comunicação entre API .NET Core usando REBUS e RabbitMQ
[av_textblock fold_type=” fold_height=” fold_more=’Leia mais’ fold_less=’Read less’ fold_text_style=” fold_btn_align=” textblock_styling_align=” textblock_styling=” textblock_styling_gap=” textblock_styling_mobile=” size=” av-desktop-font-size=” av-medium-font-size=” av-small-font-size=” av-mini-font-size=” font_color=” color=” fold_overlay_color=” fold_text_color=” fold_btn_color=’theme-color’ fold_btn_bg_color=” fold_btn_font_color=” size-btn-text=” av-desktop-font-size-btn-text=” av-medium-font-size-btn-text=” av-small-font-size-btn-text=” av-mini-font-size-btn-text=” fold_timer=” z_index_fold=” id=” custom_class=” template_class=” av_uid=’av-ls2byvy8′ sc_version=’1.0′ admin_preview_bg=”]
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. dsdumba@gmail.com.
Obrigado pela leitura e tamo junto!!
[/av_textblock]