Asynchronous Microservices Communication

Nós criaremos dois microsserviços utilizando uma comunicação assíncrona. Ao efetuar o request no microsserviço “ms-order” o mesmo vai processar as suas regras e depois emitir um evento para o “ms-payment”.

As aplicações baseadas no devprime incorporam nativamente uma aborgaem ‘Event-Driven architecture’ recebendo eventos externos, processam regras de negócio emitindo eventos internos e caso necessário propagam os eventos externamente utilizando o adapter Stream que se conecta nativamente com RabbitMQ, Kafka e outras plataformas.

Nesse momento montaremos uma demonstração envolvendo dois Microservice. O primeiro é “Order” para receber pedidos e o segundo “Payment” para processar o pagamento. No exemplo utilizaremos dois containers criados no Docker. É fundamental subir os containers MongoDB e RabbitMQ e configurar adicionar a fila “orderevents e paymentevents” no Rabbitmq. Antes de avançar siga os passos iniciais instalando o DevPrime CLI.

Se você já possuir ss serviços MongoDB/RabbitMQ altere as configurações no arquivo /src/App/app/appsettings.json no State e no Stream com as suas credenciais.

Criando o primeiro microservice “Order”

Nós utilizaremos o DevPrime CLI para a criação do microsserviços. Nesse exemplo informaremos o nome da nova aplicação, o tipo de serviço de Stream como “RabbitMQ” para emissão de eventos assíncronos e o State como o “MongoDB” para a persistência dos dados. Para iniciar um novo Microservice utilize o exemplo de comando apresentado e uma nova aplicação será criada em segundos e pronta para produção.
dp new Order --stream rabbitmq --state mongodb

Adicionando uma regra de negócio do marketplace acelerando o desenvolvimeneto
dp marketplace order

Executando acelerador para implementação de código baseado o Domain. Ao executar o comando a primeira vez nessa demonstração digite “A” para que possa avançar e realizar as alterações.
dp init

O acelerador implementa automaticamente as classes de “Domain Events” em Domain, Event Handlers em Application, “Domain Services” em Application, “Application Services” em Application, persistência em State, “API’s” em Web e alguns testes unitários em Tests como exemplo.

DevPrime CLI

Ao executar a aplicação novamente utilizando o script “.\run.ps1” (Windows) ou “.\run.sh” (Linux/macOS) nós já teremos uma nova visualização da sua API. Abra o browser web na url http://localhost:5000 e localize no Swagger o método post e clique em “Try it out".

Preencha dos dados do JSON para depois clicar em “execute”. Retorne ao prompt de commando acompanhe o resultado no log do microservice gerado automaticamente pelo adapter de Observability do DevPrime.

DevPrime CLI

Para conferir um exemplo de código implementado pelo acelerador acompanhe o código do OrderCreatedEventHandler.cs na pasta src/Core/Application/EventHandlers/Order que fornece um Handle onde persiste o estado de um aggregate ‘order’ e depois notifica via adapter de Stream.

public override dynamic Handle(OrderCreated orderCreated)
    {
        var success = false;
        var order = orderCreated.Get<Domain.Aggregates.Order.Order>();
        Dp.State.Order.Add(order);
        var destination = Dp.Settings.Default("stream.orderevents");
        var eventName = "OrderCreated";
        var eventData = new OrderCreatedEventDTO()
        {ID = order.ID, CustomerName = order.CustomerName, CustomerTaxID = order.CustomerTaxID, Total = order.Total};
        Dp.Stream.Send(destination, eventName, eventData);
        success = true;
        return success;
    }

DevPrime starting microservice

Nesse momento o log demonstra o recebimento de um request HTTP na API “Order” que repassa para o serviço de aplicação e depois a regra de negócio no domínio que processa e emite evento “OrderCreated” que além de ter o estado persistido no mongo resultou em uma notificação no mundo externo pelo Stream.

Ao entrar agora no RabbitMQ pelo portal do mesmo é possível observar esse evento na fila “OrderEvents”. Isso significa que esse microservice já cumpriu o seu papel e notificou esse fato de negócio para que possa ser percebido por outro serviço.

Como a comunicação nesse exemplo é totalmente assíncrona se houver eventos na fila do RabbitMQ eles serão lidos pelo microservice “Payment” assim que configuramos. Você pode parar a aplicação pressionando a tecla “Control” + ”C” a qualquer momento e carregar novamente quando desejar.

Parabéns !!! Você já tem o microservice “Order”

Criando o segundo microservice “Payment”

O segundo microsserviços chamará “Payment” e iniciaremos a criação do mesmo nesse momento. Um item adicional que faremos durante a implementação é justamente ativar o “subscribe” no adapter de Stream para que possamos receber os eventos emitidos pelo microservice “Order”.

Executando o dp new e criando o microservices
dp new ms-payment --state mongodb --stream rabbitmq

Adicionando uma regra de negócio do marketplace acelerando o desenvolvimeneto
dp marketplace payment

Executando acelerador para implementação de código baseado o Domain
dp init

Como nós executaremos dois microsserviços no mesmo ambiente local é necessário alterar a porta http do Payment. Na pasta do projeto ms-payment edite o arquivo src/App/appsettings.json na chave web e alterando para 5002 e 5003 conforme exemplo.

  "DevPrime_Web": {
    "url": "https://localhost:5003;http://localhost:5002",
    "enable": "true",
    "enableswagger": "true",
    "PostSuccess": "201",
    "PostFailure": "500",
    "GetSuccess": "200",
    "GetFailure": "500",
    "PatchSuccess": "200",
    "PatchFailure": "500",
    "PutSuccess": "200",
    "PutFailure": "500",
    "DeleteSuccess": "200",
    "DeleteFailure": "500"
  },

Execute a aplicação utilizando o comando abaixo
.\run.ps1 ou ./run.sh (Linux, MacOS)

Se a aplicação rodou está correndo tudo bem. Feche a mesma com “Control+C” e seguimeremos com as implamentações para habilitar o recebimento dos eventos emitidos pelo microservices “Order”.

O próximo passo é adicionar a configuração no serviço de Stream e criação de um DTO para recebermos os dados do evento. Você pode fazer todo o processo manualmente ou utilizando o acelerador abaixo.
dp add event OrderCreated -as PaymentService

Caso ele solicite alteração basta confirmar e seguir e ao final nós teremos dois arquivos alterados para que possamos avançar nas configurações.

/src/Core/Application/Services/Payment/Model/OrderCreatedEventDTO.cs
/src/Adapters/Stream/EventStream.cs 

Abra o arquivo Edite o arquivo OrderCreatedEventDTO em src/Core/Application/Services/Payment/Model/OrderCreatedEventDTO.cs e adicione as propriedades abaixo.
code src\Core\Application\Services\Payment\Model\OrderCreatedEventDTO.cs

1
2
3
4
5
6
public class OrderCreatedEventDTO                     
  {                                                     
    public Guid OrderID { get; set; }
    public string CustomerName { get; set; }
    public double Value { get; set; }  
  }

Agora edite o arquivo “EventStream.cs” na pasta /src/Adapters/Stream e modifique adicionando as propriedades do nosso OrderCreatedEventDTO. Nós modificaremos o serviço de aplicação para utilizar o serviço “Add”. Essa implementação indica que estaremos realizando “Subscribe” no evento chamado “OrderCreated” que será vinculado ao alias “Stream1” no adapter Stream.
code src\Adapters\Stream\EventStream.cs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
namespace DevPrime.Stream;
public class EventStream : EventStreamBase, IEventStream
{
    public override void StreamEvents()
    {
        Subscribe<IPaymentService>("Stream1", "OrderCreated", (payload, paymentService, Dp) =>
        {
            var dto = System.Text.Json.JsonSerializer.Deserialize<OrderCreatedEventDTO>(payload);
            var command = new Payment()
            {
                CustomerName = dto.CustomerName,
                OrderID = dto.OrderID,
                Value = dto.Value
            };
            paymentService.Add(command);
        });
    }
}

A etapa final é alterar o arquivo de configuração do Adapter de Stream no arquivo (src/App/appsettings.json) e adicionar o nome da fila / tópico “OrderEvents” em “subscribe” conforme exemplo abaixo.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
"DevPrime_Stream": [
    {
      "Alias": "Stream1",
      "Enable": "true",
      "Default": "true",
      "StreamType": "RabbitMQ",
      "HostName": "Localhost",
      "User": "guest",
      "Password": "guest",
      "Port": "5672",
      "Exchange": "devprime",
      "ExchangeType": "direct",
      "Retry": "3",
      "Fallback": "State1",
      "Subscribe": [ { "Queues": "orderevents" } ]
    }

Parabéns !!! Você já tem o microsserviços “Payment”

Comunicação assíncrona entre os microservices

Agora você cheou ao grande momento de executar os dois microservices. Abra cada um em uma janela e execute para quem tenha os serviços ativos. Acesse a url do microservices “Order” em http://localhost:5000 e depois vá em Post e clique no botão “Try it out”. Realize um post e acompanhe o resultado incrível com o detalhamento completo em cada Microservices e o Payment reagindo ao evento recebido.

Nesse momento nós estamos conferindo o resultado do processamento no microservices “Order” que recebe
um post e emite o evento “OrderCreated”. Todos os detalhes são gerados automaticamente pelo adapter Observability do DevPrime.

[INF][Web]["https://localhost:5001;http://localhost:5000"][Host]["Production"][Parameters]["Appsettings"][RequestId: 3282e6f8-d2bd-4ccf-934a-546276a83038]
[2021-06-19T20:23:07.654-03:00][INF][Web]["HTTP"][Order][Add]["{\n  \"customerName\": \"Ramon\",\n  \"customerTaxID\": \"string\",\n  \"itens\": [\n    {\n      \"description\": \"string\",\n      \"amount\": 0,\n      \"sku\": \"string\",\n      \"price\": 0\n    }\n  ],\n  \"total\": 0\n}"][Origin:"https://localhost:5001/swagger/index.html"][RequestId: 06721dfc-fd50-4783-8ea7-df201c22599b]
[INF][Application][OrderService][Add][RequestId: 06721dfc-fd50-4783-8ea7-df201c22599b]
[INF][Domain][Order][Add][RequestId: 06721dfc-fd50-4783-8ea7-df201c22599b]
[INF][Domain][Order][Handle][Event]["OrderCreated"][RequestId: 06721dfc-fd50-4783-8ea7-df201c22599b]
[INF][Application][EventHandler]["OrderCreatedEventHandler"][Event]["OrderCreated"][RequestId: 06721dfc-fd50-4783-8ea7-df201c22599b]
[INF][State][Type "MongoDB"][Alias "State1"][OrderRepository][Add][RequestId: 06721dfc-fd50-4783-8ea7-df201c22599b]
[INF][Stream][Type "RabbitMQ"][Alias "Stream1"][Out][Event]["OrderCreated"]["Delivered"]["OrderEvents"]["{\"SagaId\":\"00000000-0000-0000-0000-000000000000\",\"Id\":\"35bc8937-deb8-4e35-a8c4-003171fd6e1b\",\"CorrelationId\":\"06721dfc-fd50-4783-8ea7-df201c22599b\",\"AppId\":\"3282e6f8-d2bd-4ccf-934a-546276a83038\",\"AppName\":\"ms-order\",\"Version\":1,\"Name\":\"OrderCreated\",\"When\":\"2021-06-19T20:23:07.8540357-03:00\",\"Payload\":{\"CustomerName\":\"Ramon\",\"CustomerTaxID\":\"string\",\"Total\":0,\"ID\":\"55a72f16-f4e5-476b-85d9-e86c6e6f390c\"}}"][RequestId: 06721dfc-fd50-4783-8ea7-df201c22599b]

E agora o momento que chega o evento ‘OrderCreated’ no microservices “Payment”.

[INF][Web]["https://localhost:5003;http://localhost:5002"][Host]["Production"][Parameters]["Appsettings"][RequestId: 072b12e7-c941-4b4b-9823-cbd6e4425309]
[INF][Stream][Type "RabbitMQ"][Alias "Stream1"][In][Event]["OrderCreated"]["OrderEvents"]["{\"SagaId\":\"00000000-0000-0000-0000-000000000000\",\"Id\":\"35bc8937-deb8-4e35-a8c4-003171fd6e1b\",\"CorrelationId\":\"06721dfc-fd50-4783-8ea7-df201c22599b\",\"AppId\":\"3282e6f8-d2bd-4ccf-934a-546276a83038\",\"AppName\":\"ms-order\",\"Version\":1,\"Name\":\"OrderCreated\",\"When\":\"2021-06-19T20:23:07.8540357-03:00\",\"Payload\":{\"CustomerName\":\"Ramon\",\"CustomerTaxID\":\"string\",\"Total\":0,\"ID\":\"55a72f16-f4e5-476b-85d9-e86c6e6f390c\"}}"][RequestId: 35bc8937-deb8-4e35-a8c4-003171fd6e1b]
[INF][Application][PaymentService][Add][RequestId: 35bc8937-deb8-4e35-a8c4-003171fd6e1b]
[INF][Domain][Payment][Add][RequestId: 35bc8937-deb8-4e35-a8c4-003171fd6e1b]
[INF][Domain][Payment][Handle][Event]["PaymentCreated"][RequestId: 35bc8937-deb8-4e35-a8c4-003171fd6e1b]
[INF][Application][EventHandler]["PaymentCreatedEventHandler"][Event]["PaymentCreated"][RequestId: 35bc8937-deb8-4e35-a8c4-003171fd6e1b]
[INF][State][Type "MongoDB"][Alias "State1"][PaymentRepository][Add][RequestId: 35bc8937-deb8-4e35-a8c4-003171fd6e1b]

Parabéns🚀 Você acabou de desenvolver dois microservices. Para utilizar o Kafka bastas alterar as configurações do adapter de Stream.

Última modificação May 2, 2022 (9f8c9e9)