Building an idempotent event-driven microservices

Building an idempotent event-driven microservices

Criando um microserviço para utilizar no exemplo

Nós utilizaremos um microsserviço baseado na plataforma DevPrime

  • Utilize uma conta ativa no DevPrime e ative o DevPrime CLI.
  • Inicie os containers pelo Docker (MongoDB, RabbitMQ e Redis)
  • Crie a fila orderevents e paymentevents no RabbitMQ.
  • Crie um novo microsserviço seguindo o exemplo do “payment” apresemtado.
  • Esse microsserviço escutará a fila/tópico “orderevents”

Iniciando a configuração da idempotência

Configuração da idempotência na chave “DevPrime_App”
Altere na configuração do Idempotency na opção “Enabled” para “true” e o “Flow” para “frontend”. Nós alteramos a “key” para “my-idempotency-key”. Com essa configuração a API vai requerer no Headar a chave de idempotência.
code .\src\app\appsettings.json

1
2
3
4
5
6
7
8
9
    "Idempotency": {
      "Enable": "true",
      "Alias": "State2",
      "Duration": "86400",
      "Flow": "backend",
      "key": "idempotency-key",
      "Scope": "all",
      "Action": "auto"
    }

Inclua um segunda persistência na chave “DevPrime_State”
Nesse exemplo utilizaremos um Redis local com uma senha padrão
code .\src\app\appsettings.json

1
2
3
4
5
6
7
8
9
{
  "enable": "true",
  "alias": "State2",
  "dbtype": "redis",
  "connection": "127.0.0.1:6379,password=LltF8Nx*yo",
  "timeout": "5",
  "retry": "2",
  "durationofbreak": "45"
}

Para esse cenário nós adicionaremos o evento diretamente no RabbitMQ para simularmos uma duplicidade de eventos.

Execute o microsserviço dp-stream e entre no portal do Rabbitmq em
http://localhost:15672 com o usuário guest/guest. Localize o menu Queues > orderevents e localize o item “Publish message”

Localize o campo payload e cole o json abaixo e dpeois vá em “Publish message” para colocar esse evento na fila.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
{
   "Headers":{
      "uber-trace-id":"126e838875d34dd3:5644a477603d0a55:bc20635b06c79bc2:1"
   },
   "Id":"36dbd9d0-7b8b-41fb-bdaf-2ff6a0672bd4",
   "CorrelationId":"18c71a48-0253-4247-9c8b-d576c4f8394e",
   "AppId":"21cef89c-229e-4291-8d6a-1ccf5c401484",
   "AppName":"ms-idempotency",
   "TenantID":"",
   "TenantUserID":"",
   "Version":1,
   "Name":"OrderCreated",
   "CreationDate":"2022-03-13T11:14:01.0284863-03:00",
   "Payload":{
      "ID":"ccfc8248-2ab5-4423-92fc-b16c4555add2",
      "CustomerName":"Bill",
      "CustomerTaxID":"string",
      "Total":100
   }
}

O microserviço está escutado essa fila e já vai reagir ao evento e processar com sucesso.

DevPrime Capabilities Idempotency Stream

Agora retorne ao RabbitMQ e envie novamente o mesmo evento e vai observar que ao chegar ao microsserviço ele é rejeitado automaticamente.

DevPrime Capabilities Idempotency Stream

Para repetir o teste altere um valor no Payload do json colocando um outro guid no ID por exemplo.

Iniciando a idempotência manual

O DevPrime permite que você informe manualmente um método especifico que terá o suporte a idempotência. O primeiro passo é alterar a configuração principal e depois ir diretamente no método para adicionar a configuração.

** Alterando a chave “Action” para “Manual”**

1
2
3
4
5
6
7
8
9
    "Idempotency": {
      "Enable": "true",
      "Alias": "State2",
      "Duration": "86400",
      "Flow": "backend",
      "key": "idempotency-key",
      "Scope": "all",
      "Action": "manual"
    }

No modo manual ao efetuar um teste enviando eventos duplicados vai observar que eles serão aceitos. Você precisa visitar cada método desejado e adicionar “Dp.Idempotency.Enable = true;” para a habilitar o controle de idempotência.

** Modificando o Stream para ter idempotência"**
Abra o Hub de eventos e localizar o subscribe do evento desejado que nesse contexto é o “OrderCreated” e adicine o “Dp.Idempotency.Enable = true;”.
code src\Adapters\Stream\EventStream.cs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
namespace DevPrime.Stream;
public class EventStream : EventStreamBase, IEventStream
{
    public override void StreamEvents()
    {
        Subscribe<IPaymentService>("Stream1", "OrderCreated", (payload, paymentService, Dp) =>
        {
            Dp.Idempotency.Enable = true;

            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);
        });
    }
}

Ao tentar executar novamnente com eventos duplicados já vai perceber que voltou a funcionar.

Mais artigos sobre idempotência

Idempotency in microservices and REST APIs
Building an idempotent event-driven microservices

Última modificação March 13, 2022 (cb296b2)