A importância da Idempotência

O conceito de idempotência, tanto na matemática quanto na computação, se dá quando o resultado de uma operação repetida várias vezes gera o mesmo resultado da primeira operação.

Um exemplo básico disso é a equação abaixo:

$$ x = 5 $$

Ela pode ser executada várias vezes e em todas elas o resultado será sempre o mesmo.

Este conceito é importante na engenharia de software pois há sistemas que precisam ter este comportamento para funcionar corretamente. Uma aplicação de cobrança, por exemplo, deve de garantir que a cobrança será debitada apenas uma vez do cliente, pois essa é uma operação que não pode ser desfeita, sendo necessário uma nova operação de reembolso para devolver o dinheiro debitado indevidamente do cliente, que não ficará nada contente.

Em um cenário de microsserviços é comum ocorrer falhas transientes durante a comunicação entre eles, que nada mais são do que falhas temporárias causadas por perda momentânea de conexão com outros componentes e serviços, indisponibilidade temporária e timeouts.

Muitas aplicações lidam com essas falhas através de um ou mais mecanismos de resiliência, uma abordagem muito conhecida e utilizada é o de efetuar re-tentativas (retries), enviando novamente a requisição para o serviço, na expectativa de receber uma resposta bem-sucedida desta vez.

O problema é que o serviço pode ter recebido e processado a requisição com sucesso, mas na hora de responder a requisição, ocorreu uma falha transiente que impossibilitou o envio da resposta. Isso gera erros de timeout que invocam o mecanismo de resiliência da outra aplicação, que irá enviar novamente a mesma requisição, duplicando essa operação, e dessa vez espera uma resposta bem-sucedida.

Agora que sabemos o que é idempotência e como ela pode afetar sua aplicação, como podemos implementar este conceito em nossas aplicações e protegê-las de efeitos colaterais? Como é possível identificar requisições duplicadas em um cenário onde temos diversas aplicações distribuídas em vários servidores, realizando múltiplas requisições ao mesmo tempo?

Existem algumas abordagens para lidar com esse problema, e qual delas você deve utilizar depende exclusivamente do comportamento da sua aplicação, do ambiente de execução e de regras de negócio.

Para alguns casos, basta realizarmos uma verificação antes de alterarmos o estado do recurso. No cadastro de um novo usuário, por exemplo, é necessário verificar se o e-mail informado pelo cliente não está em uso por nenhum outro usuário já cadastrado. Isso é uma maneira de ser idempotente, pois o comportamento do cadastro é o de alterar o estado do recurso apenas se ele não existir na base, dessa forma garantimos que em uma ou múltiplas operações de cadastro teremos sempre o resultado esperado.

Podemos assumir que uma requisição é duplicada se ela é idêntica à requisição anterior em curto período de tempo, sendo que o tempo varia conforme o negócio e a aplicação. Isso inclui todos os parâmetros, cabeçalhos (headers) e corpo (payload) informados em ambas as requisições.

Isso pode ser válido para alguns cenários, mas definitivamente não funciona para todos os casos. Um serviço de autoscaling, por exemplo, pode receber milhares de requisições idênticas em um curto espaço de tempo devido a um pico repentino de acessos. Já um serviço de e-mail pode aceitar enviar dois e-mails idênticos ao cliente, pois isso não ocasiona grandes problemas. Cada caso deve ser analisado com cuidado e sempre levando em consideração os riscos envolvidos para o negócio.

Uma boa estratégia para ser utilizada em casos mais complexos é a geração de identificadores únicos pela aplicação que irá consumir um determinado serviço. Isso possibilita que o serviço receba esse identificador e valide se a requisição já foi processada anteriormente, identificando de uma maneira confiável se a requisição de fato está duplicada. Além disso, é possível mantermos uma boa rastreabilidade de tudo que foi processado pelo servidor, a partir de um único identificador presente durante todo o ciclo de vida da requisição.

Estratégia de Idempotência

Na imagem acima, o cliente realiza a requisição para o serviço e informa um identificador único junto à mesma. Quando o serviço receber a requisição, a primeira coisa que ele faz é verificar se esse identificador já foi processado anteriormente. Caso positivo, a requisição será respondida imediatamente para o cliente, pois trata-se de uma requisição duplicada. Em casos negativos, a requisição começa a ser processada e o estado dela será armazenado como sendo processado com sucesso, somente se todo o processamento for bem-sucedido. Se alguma falha transiente ocorrer durante esse fluxo, a resiliência por parte do cliente irá realizar uma re-tentativa (retry) e o servidor estará preparado para identificar essa requisição como duplicada e já processada anteriormente.

Observe que nas legendas da imagem há a ordem em que os eventos acontecem para que você possa entender de forma mais clara e visual o fluxo como um todo.

Onde devo usar a Idempotência?

A idempotência é um conceito importante que você precisa sempre ter em mente. Pode ser necessário a utilização dela em todos os tipos de aplicações, seja um legado que recebe apenas manutenções, bem como aplicações novas e existentes. Tudo vai depender de como sua aplicação precisa se comportar diante dos cenários em que ela está exposta e dos desafios do negócio.

É importante mencionar também que alguns serviços da AWS possuem um mecanismo nativo de resiliência e tratamento de falhas. Isso pode acarretar em retries automáticos pela AWS quando seu serviço não responde da maneira adequada, causando efeitos colaterais se sua aplicação não estiver preparada para ser idempotente. Um bom exemplo disso é uma Lambda que finaliza sua execução com erro. Recomendo se aprofundar mais no assunto neste link.

Se você utiliza um serviço de mensageria (Apache Kafka, RabbitMQ, SQS), e as mensagens podem ser reprocessadas por qualquer motivo, você também deve pensar em idempotência e adequar suas aplicações o quanto antes.

Conclusão

Não há dúvidas de que a idempotência é um conceito que nos ajuda a modelar e garantir o correto funcionamento das nossas aplicações, fornecendo mais confiabilidade e tratando efeitos colaterais de forma eficiente.

Não existe nenhuma fórmula pronta para aplicar em seu negócio e resolver todos os seus problemas, mas existem diferentes abordagens que se encaixam melhor no contexto da sua aplicação, e possuem o mesmo objetivo: entregar valor e a melhor experiência possível para o cliente.