Idempotência em microsserviços usando .NET, C# e Devprime

22 de agosto de 2023 Por Ramon Durães

O desenvolvimento de microsserviços traz consigo uma nova perspectiva para a arquitetura de software. Enfrentar desafios é algo comum, e isso inclui aspectos familiares de aplicações tradicionais, como a idempotência. Essa terminologia tem raízes na matemática e diz respeito ao conceito de que operações idênticas executadas repetidamente não devem alterar o resultado.

Em uma situação prática, imagine pressionar várias vezes o botão “comprar” no carrinho de compras de um e-commerce, resultando em múltiplos pedidos de compra. Esse problema é bastante recorrente, seja em aplicações tradicionais ou em sistemas distribuídos, como os microsserviços. Além de se preocupar com o processamento redundante na API, é crucial implementar a mesma estratégia para os eventos recebidos.

A plataforma Devprime revolucionou o desenvolvimento moderno de aplicações ao oferecer um projeto completo de arquitetura de software que utiliza a arquitetura hexagonal e a arquitetura orientada a eventos (event-driven architecture) para microsserviços. Isso permite desenvolver o primeiro microsserviço em apenas 30 minutos e economizar cerca de 70% dos custos de implementação do software no backend. Entre as diversas estratégias oferecidas, um recurso crucial é a abordagem automática de idempotência. Essa estratégia pode ser aplicada tanto a posts recebidos na API quanto a eventos recebidos por meio de streams, como Kafka, RabbitMQ e outros. Para compreender rapidamente o comportamento da idempotência, é possível observar o log a seguir de um microsserviço implementado com a Devprime.

Esse log mostra o momento em que a idempotência é automaticamente iniciada e posteriormente finalizada. Ele representa o primeiro POST na API do microsserviço.

O próximo passo consiste em reproduzir o mesmo POST na API para observar internamente o funcionamento do recurso de idempotência. Isso permitirá identificar um processamento duplicado, como ilustrado na imagem, e o sistema irá rejeitar essa duplicação automaticamente, sem passar pelas regras de negócio que foram executadas no exemplo anterior. É importante ressaltar que essa implementação é realizada de forma automática e é disponibilizada pela plataforma Devprime. Essa abordagem contribui para a redução de erros e dos custos de processamento.

No primeiro artigo desta série, intitulado “Acelerando o desenvolvimento de microsserviços usando .NET / C# e Devprime“, demonstramos a criação do primeiro microsserviço. Neste exemplo, utilizaremos o mesmo cenário, efetuando modificações para representar o contexto de idempotência, protegendo automaticamente uma API contra processamento duplicado.

Instalação
– NET 7.0 ou superior
 – Visual Studio Code
– Docker
– Devprime CLI.

Preparando o ambiente
Para preparar o ambiente inicial utilizando o Docker, faremos uso do banco de dados MongoDB e do RabbitMQ como fila, ambos em um ambiente local no Docker. Adicionalmente, o Redis será empregado como um banco de dados secundário para a funcionalidade de idempotência. É importante assegurar que você tenha o Docker instalado e em funcionamento, com os containers do MongoDB, RabbitMQ e Redis ativos. Para orientações detalhadas sobre como criar a exchange “devprime” e as filas “orderevents” e “paymentevents” no RabbitMQ, e realizar o Bind com a exchange, sugiro consultar a documentação da Devprime. As aplicações utilizarão as credenciais padrão fornecidas na documentação da Devprime.

Obtendo e configurando a aplicação de exemplo
Nesse artigo modificaremos um microsserviço de exemplo baseado na plataforma Devprime para ter funcionalidade de idempotência.

1) Efetue clone no github
git clone https://github.com/devprime/devprime-microservices-order-payment
2) Entre na pasta clonada e atualize o Stack com o comando abaixo do Devprime CLI
dp Stack
3) Nesse passo vamos configurar as credenciais do MongoDB, RabbitMQ, Redis
a) Entre na pasta ms-order e abra no Visual Studio Code o arquivo appsettings.json
code src/app/appsettings.json

b) Localize e atualize a configuração do Rabbitmq em Stream.

  “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”,

      “Threads”: “30”,

      “Buffer”: “5”,

      “Subscribe”: []    

}

c) Localize e atualize a configuração do MongoDB (State1) e Redis (State2) no item State.

“DevPrime_State”: [

    {

      “enable”: “true”,

      “alias”: “State1”,

      “type”: “db”,

      “dbtype”: “mongodb”,

      “connection”: “mongodb://mongoadmin:LltF8Nx*yo@localhost:27017”,

      “timeout”: “5”,

      “retry”: “2”,

      “dbname”: “ms-order”,

      “isssl”: “true”,

      “numberofattempts”: “4”,

      “durationofbreak”: “45”

    },

    {

      “enable”: “true”,

      “alias”: “State2”,

      “dbtype”: “redis”,

      “connection”: “127.0.0.1:6379,password=LltF8Nx*yo”,

      “timeout”: “5”,

      “retry”: “2”,

      “durationofbreak”: “45”

    }  

],

Agora que você revisou as configurações básicas execute o microsserviço de “Order” e efetue alguns posts na API para observar se ele está funcionando normalmente e gravando no banco de dados os posts repetidos.

Para executar o microsserviço:
.\run.ps1 ou ./run.sh (Linux, macOS)

Após realizar os primeiros testes citados acima finalize a aplicação para avançar nos próximos passos.

Habilitando a idempotência na API do microsserviço
A configuração da funcionalidade de idempotência é realizada no arquivo “appsettings”, na seção “DevPrime_App”, dentro do item “Idempotency”. Para habilitar a idempotência, você deve modificar a opção “Enabled” para “true”. O próximo parâmetro, “Alias”, é responsável por definir o local de armazenamento para persistência, que é definido como “State2”. O parâmetro “Duration” controla o período de duração da idempotência. Já o parâmetro “Flow” determina a estratégia a ser adotada, como por exemplo “backend”. O parâmetro “Scope” estabelece o alcance da atuação, podendo ser “all”, “web” ou “stream”. Por fim, o parâmetro “Action” configura o modo de operação, podendo ser automático ou manual.

Para editar abra o appsettings no Visual Studio Code
code src/app/appsettings.json

    “Idempotency”: {

      “Enable”: “true”,

      “Alias”: “State2”,

      “Duration”: “86400”,

      “Flow”: “backend”,

      “key”: “idempotency-key”,

      “Scope”: “all”,

      “Action”: “auto”

}

Agora chegou o momento de realizar o primeiro teste executando o microsserviço

a) Execute o novamente o microsserviço e abra no navegador https://localhost:5001
b) Preencha os dados do post pelo Swagger

{

  “customerName”: “Ramon Durães”,

  “customerTaxID”: “ID887612091”,

  “items”: [

    {

      “description”: “Iphone”,

      “amount”: 1,

      “sku”: “IP15”,

      “price”: 1200

    }

  ],

  “total”: 1200

}

Após confirmar nós teremos um post realizado com sucesso conforme demonstrado no log. O momento do “Initialize” o Stack Devprime busca a existência anterior desse objeto de idempotência e não encontrando processa normalmente e depois cria o objeto para identificar um segundo processamento duplicado.

O mesmo resultado pode ser conferido no Swagger.

Ao efetuar um novo POST repetindo o payload anterior nós já teremos uma resposta diferente no Swagger indicando que não foi possível processar o mesmo conforme o código de erro 500 indicado na API.

Uma rápida leitura no log do microsserviço já demonstra que a funcionalidade automática de idempotência oferecida pela plataforma Devprime descartou esse request duplicado sem que nenhuma ação adicional fosse necessária. É importante ressaltar que pelo log mais curto foi possível identificar que não ocorreu o processamento da regra de negócio.

Ao realizar um novo POST só dessa vez trocando algum valor no payload será possível observar que ele será aceito com sucesso, pois a estratégia atua apenas em eventos duplicados.

Habilitando uma chave como parâmetro da idempotência no microsserviço
Agora que você já aprendeu como funciona a idempotência na prática nós vamos customizar o flow para “frondend” para que possamos ter uma chave personalizada que nesse contexto definiremos como  “TransactionID” no campo “Key” conforme demonstrado na configuração abaixo.

    “Idempotency”: {

      “Enable”: “true”,

      “Alias”: “State2”,

      “Duration”: “86400”,

      “Flow”: “Frontend”,

      “key“: “TransactionID“,

      “Scope”: “all”,

      “Action”: “auto”

}

Para editar abra o appsettings no Visual Studio Code
code src/app/appsettings.json

Após editar execute novamente o microsserviço e observe as informações de configuração presentes no log determinado que utilizaremos uma chave personalizada “TransactionID” que deve ser fornecida no Header ao consumir a API.

a) Execute o novamente o microsserviço e abra no navegador https://localhost:5001
b) Desta vez nós faremos um post pelo Postman.
c) Crie um novo post para a url https://localhost:5001/v1/order definindo como raw e JSON e adicione o conteúdo abaixo.

{

  “customerName”: “Ramon”,

  “customerTaxID”: “999871903773”,

  “items”: [

    {

      “description”: “string”,

      “amount”: 0,

      “sku”: “string”,

      “price”: 0

    }

  ],

  “total”: 0

}

Na imagem é possível observar o conteúdo no post no Postman.

Ao realizar um novo POST através do Postman, você receberá imediatamente um erro, indicando que é agora obrigatório incluir uma chave “TransactionID” no Header.

Agora, retorne ao Postman na seção de Headers e adicione a chave “transactionid” com um valor para conseguir realizar um POST. Isso resultará em um sucesso na operação.

Ao tentar efetuar novamente um segundo POST idêntico na API, você encontrará um erro, conforme apresentado abaixo no log, indicando que a idempotência rejeitou esse processamento duplicado. Para conseguir realizá-lo, será necessário incluir um novo valor no campo “TransactionID” no Header.

Neste artigo, demonstramos como utilizar o recurso de idempotência em conjunto com o Stack da plataforma Devprime, que incorpora essa funcionalidade automaticamente, simplificando a experiência do desenvolvedor. No exemplo apresentado, utilizamos a abordagem automática com os parâmetros recebidos na API e, posteriormente, com uma chave personalizada transmitida pelo Header.

Para saber mais e criar uma conta gratuita:
https://devprime.io

[],

Ramon Durães
CEO, Devprime