Um dos aspectos mais importantes de REST (ou pelo menos do protocolo HTTP) é o conceito de que algumas operações (verbos) são idempotentes. Como Gregor Roth disse muitos anos atrás:
O método PUT é idempotente. Um método é considerado idempotente se o resultado de uma requisição realizada com sucesso é independente do número de vezes que é executada.
O conceito de "idempotência" é também bastante discutido em Padrões de Projeto SOA. Ao longo dos anos, muito foi falado a respeito de REST e seus benefícios em relação a outras abordagens. Embora o conceito seja considerado como compreendido, não é investigado a fundo. Um post recente em uma lista de discussão sobre Arquitetura Orientada a Serviços gerou muita discussão a respeito. O post inicial começa da seguinte forma:
Atualmente estou analisando a idempotência dos nossos serviços e fazendo um guia prescritivo para a empresa, tratando dos casos em que devem ser criados serviços idempotentes. A questão é: todos os serviços precisam ser idempotentes? Isso é realista?
O post faz referência a um outro blog, no qual o autor faz as seguintes afirmações:
É claro que implementar idempotência pode ser complexo para o provedor do serviços. E complexidade custa dinheiro. Provavelmente essa é a principal razão que faz com que os serviços idempotentes sejam tão raros. [...] Mas uma vez que um serviço é feito idempotente, ele é infalível e pode garantir a consistência dos dados através de fronteiras de integração.
Serviços devem ser idempotentes. Isso deveria ser um princípio de arquitetura e de design de serviços, especialmente para serviços usados por muitos consumidores. É incorreto empurrar a complexidade de manter a consistência de dados para os consumidores dos serviços. Os consumidores são, na verdade, clientes, e devem ser tratados como tal. Ganha-se mais clientes ao tornar as coisas mais simples para eles.
Houve muitas respostas, incluindo uma de Steve Jones da Cap Gemini, que disse:
Idempotência é uma das grandes enganações da TI. O que significa mesmo é "idempotência em memória". Se há um serviço com uma funcionalidade de atualização, então claramente sob a perspectiva do cliente, é o serviço que tem de manter e gerenciar estado. O que se quer saber é se "o serviço pode ser escalado horizontalmente em memória". Se o serviço altera o estado, inclusive no banco de dados, então não é idempotente. Tem-se idempotência quando se chama a mesma funcionalidade com o mesmo valor e o resultado é exatamente o mesmo (essa é a definição matemática). Se o serviço altera o estado (por exemplo escrevendo no banco de dados), então ele não é idempotente.
Mark Baker, que já postou muito sobre REST no passado, responde o comentário de Steve:
Existem atualizações de estado não-idempotentes, como o exemplo que você citou. E também existem atualizações idempotentes que alteram um dado para um valor específico. Obviamente, se isso é feito múltiplas vezes com o mesmo valor de entrada, o resultado será sempre o mesmo.
Steve concorda com Mark em um ponto: uma operação é matematicamente idempotente se ela não altera nada, se o valor de entrada for o mesmo. Mas se há uma atualização causada pela operação alterando qualquer outro estado (por exemplo, se ela grava o horário da última requisição), então ela não é idempotente.
Fico impressionado como as pessoas fazem mau uso desse term. Já vi várias pessoas afirmando que uma coisa seria "idempotente" porque o estado não era mantido em memória, embora o efeito da chamada do consumidor fosse gravar transações.
Mark concorda com a definição rigorosa de Steve, mas acredita que isso não se aplica ao contexto do início da discussão: idempotência em sistemas distribuídos:
O termo só pode fazer referência à interface e não a sua implementação. Sendo assim, poderia ser criada uma operação "set" e definí-la como idempotente. Para os clientes, é apenas isso o que importa, mesmo que uma entrada de log fosse gerada como parte da implementação daquela interface.
Ashaf Galal responde que todos os "serviços de leitura" são idempotentes, visto que os serviços estão apenas retornando dados. No entanto, como Steve aponta, esta é uma visão muito simplista. Embora uma operação pareça ser idempotente, sua implementação pode ser tudo menos idempotente (levantando novamente a questão sobre se esta distinção é importante):
operações de leitura não precisam ser necessariamente idempotentes. 'getNextIterator()' é uma operação de leitura que não é idempotente, pois incrementa o iterador. Uma operação bancária de consulta de saldo não é idempotente se criar um log de auditoria. O resultado retornado poderia ser o mesmo para a duas chamadas subsequentes (se nenhuma mudança acontecer na conta), mas o registro de log seria diferente.
Ashaf responde o comentário de Steve afirmando que idempotência é uma propriedade de um serviço e não de sua interface.
O cliente precisa receber uma resposta apropriada do serviço; ele não quer saber se o serviço é idempotente ou não, e se grava no log ou não.
Qual sua opinião? Você acha que ainda existem mal-entendidos sobre idempotência? Faz diferença se uma operação que deveria ser idempotente altera algum estado indiretamente?