O que e Idempotencia
Idempotencia (Idempotency) e a propriedade onde a mesma operacao produz o mesmo resultado, nao importa quantas vezes seja executada. E um conceito importante para garantir confiabilidade em sistemas distribuidos e design de APIs.
Definicao matematica: f(f(x)) = f(x) Aplicar a mesma funcao multiplas vezes produz o mesmo resultado que aplica-la uma vez
Por que a Idempotencia e Importante
Na comunicacao de rede, problemas como os seguintes podem ocorrer.
sequenceDiagram
participant C as Cliente
participant S as Servidor
C->>S: Requisicao
Note over C,S: Timeout<br/>(A resposta nao chegou)
Note over C: A requisicao foi processada?<br/>Devo tentar novamente?
Se a operacao for idempotente, e possivel fazer retry com seguranca.
Metodos HTTP e Idempotencia
| Metodo | Idempotencia | Descricao |
|---|---|---|
| GET | ✓ | Obtencao de recurso, sem efeitos colaterais |
| HEAD | ✓ | Igual ao GET (sem corpo) |
| PUT | ✓ | Substituicao completa do recurso |
| DELETE | ✓ | Exclusao do recurso |
| POST | ✗ | Criacao de recurso, com efeitos colaterais |
| PATCH | ✗ | Atualizacao parcial (depende da implementacao) |
PUT vs POST
| Metodo | Resultado |
|---|---|
| PUT /users/123 | Nao importa quantas vezes execute, o user 123 permanece no mesmo estado |
| POST /users | Cada execucao pode criar um novo usuario |
Chave de Idempotencia (Idempotency Key)
E uma tecnica para alcançar idempotencia mesmo em requisicoes POST.
POST /payments
Idempotency-Key: pay_abc123xyz
Content-Type: application/json
{
"amount": 5000,
"currency": "JPY"
}
Exemplo de Implementacao
async function processPayment(req, res) {
const idempotencyKey = req.headers['idempotency-key'];
if (!idempotencyKey) {
return res.status(400).json({ error: 'Idempotency-Key required' });
}
// Verificar resultado existente
const existing = await redis.get(`idempotency:${idempotencyKey}`);
if (existing) {
return res.status(200).json(JSON.parse(existing));
}
// Obter lock (prevenir execucao simultanea)
const lock = await acquireLock(idempotencyKey);
if (!lock) {
return res.status(409).json({ error: 'Request in progress' });
}
try {
// Executar processamento
const result = await executePayment(req.body);
// Salvar resultado (valido por 24 horas)
await redis.setex(
`idempotency:${idempotencyKey}`,
86400,
JSON.stringify(result)
);
return res.status(201).json(result);
} finally {
await releaseLock(idempotencyKey);
}
}
Padroes de Implementacao de Idempotencia
1. Verificacao de Duplicidade por Identificador Unico
async function createOrder(orderData, requestId) {
// Verificar existente
const existing = await db.orders.findByRequestId(requestId);
if (existing) {
return existing; // Retornar o mesmo resultado
}
// Criar novo
const order = await db.orders.create({
...orderData,
requestId // Salvar identificador unico
});
return order;
}
2. Verificacao de Estado
async function cancelOrder(orderId) {
const order = await db.orders.findById(orderId);
// Se ja estiver cancelado, nao faz nada
if (order.status === 'cancelled') {
return order; // Idempotente
}
// Verificar se pode ser cancelado
if (order.status === 'shipped') {
throw new Error('Cannot cancel shipped order');
}
return await db.orders.update(orderId, { status: 'cancelled' });
}
3. Lock Otimista
async function updateInventory(productId, quantity, version) {
const result = await db.query(
`UPDATE inventory
SET quantity = quantity - $1, version = version + 1
WHERE product_id = $2 AND version = $3`,
[quantity, productId, version]
);
if (result.rowCount === 0) {
throw new Error('Concurrent modification detected');
}
}
Tornando Operacoes Nao-Idempotentes em Idempotentes
Operacao de Incremento
// Nao idempotente
UPDATE balance SET amount = amount + 100 WHERE user_id = 123
// Tornar idempotente (definir valor absoluto)
UPDATE balance SET amount = 5100 WHERE user_id = 123
// Ou gerenciar por ID de transacao
INSERT INTO transactions (id, user_id, amount)
VALUES ('tx_abc', 123, 100)
ON CONFLICT (id) DO NOTHING;
UPDATE balance
SET amount = (SELECT SUM(amount) FROM transactions WHERE user_id = 123)
WHERE user_id = 123;
Envio de Email
async function sendWelcomeEmail(userId, requestId) {
// Verificar se ja foi enviado
const sent = await db.emailLogs.findOne({
userId,
type: 'welcome',
requestId
});
if (sent) {
return { status: 'already_sent' };
}
// Enviar
await emailService.send(/* ... */);
// Registrar log
await db.emailLogs.create({
userId,
type: 'welcome',
requestId,
sentAt: new Date()
});
return { status: 'sent' };
}
Estrategias de Retry
Backoff Exponencial
async function retryWithBackoff(fn, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (i === maxRetries - 1) throw error;
// Backoff exponencial + jitter
const delay = Math.pow(2, i) * 1000 + Math.random() * 1000;
await sleep(delay);
}
}
}
Erros que Devem ou Nao Ser Retentados
| Retry | Tipo |
|---|---|
| ✓ Possivel | Erros 5xx (erro de servidor), timeout, erros temporarios de rede |
| ✗ Nao possivel | Erros 4xx (erro do cliente), erros de logica de negocio, erros de autenticacao |
Resumo
A idempotencia e um principio de design importante para garantir a confiabilidade de sistemas distribuidos e APIs. Utilize padroes como chaves de idempotencia, verificacao de estado e lock otimista para projetar operacoes que possam ser retentadas com seguranca.
← Voltar para a lista