Impeça pedidos duplicados com gravações condicionais

AWSBeginner
Pratique Agora

Introdução

Uma nova tentativa de envio de pedido não deve sobrescrever um valor existente. Uma atualização desatualizada de pagamento também não deve sobrescrever uma versão mais recente. Você fará solicitações condicionais efetivas à API, observará duplicatas e versões desatualizadas rejeitadas e comprovará que novos pedidos válidos ainda são bem-sucedidos.

Conclua primeiro Crie uma tabela DynamoDB para pedidos, incluindo itens tipados e expressões de atualização. Esta unidade nova não reutiliza tabelas ou credenciais anteriores. Use /home/labex/project, a AWS CLI oficial e AWS View.

Relação com as certificações

Certificação Tarefa do exame Prática
Developer – Associate (DVA-C02) Tarefa 1.3, Tarefa 1.1 Gravações condicionais DynamoDB e concorrência otimista.

Visão geral do laboratório

Diagrama conceitual com ícones AWS4, exportado com draw.io: escritas condicionais verificam atomicamente o estado do pedido, permitem criações e atualizações válidas e rejeitam solicitações duplicadas ou desatualizadas sem sobrescrever dados aceitos.

Proteja a criação do primeiro pedido

Nesta etapa, você criará um pedido apenas quando sua chave primária não estiver em uso. Um PutItem incondicional substitui o item inteiro nessa chave. Uma condição verifica o estado atual atomicamente com a gravação, portanto uma verificação separada de leitura seguida de escrita é desnecessária e permitiria condições de corrida.

Use a conexão descartável preparada e crie uma tabela independente com o ID do pedido como chave de partição String.

cd /home/labex/project
aws sts get-caller-identity
aws dynamodb create-table \
  --table-name labex-d04-orders \
  --attribute-definitions AttributeName=order_id,AttributeType=S \
  --key-schema AttributeName=order_id,KeyType=HASH \
  --billing-mode PAY_PER_REQUEST
aws dynamodb wait table-exists --table-name labex-d04-orders

Escreva um arquivo comum de solicitação da API. O heredoc entre aspas grava JSON literalmente; file:// permite que a CLI o carregue sem precisar colocar um conteúdo grande entre aspas do shell. attribute_not_exists(order_id) permite a gravação apenas se nenhum item tiver essa chave. O campo version: 1 é um valor da aplicação que você usará para proteger atualizações na etapa 3; DynamoDB não o avança automaticamente.

cat > create-order.json <<'JSON'
{
  "TableName": "labex-d04-orders",
  "Item": {
    "order_id": {
      "S": "O100"
    },
    "amount": {
      "N": "145"
    },
    "status": {
      "S": "NEW"
    },
    "version": {
      "N": "1"
    }
  },
  "ConditionExpression": "attribute_not_exists(order_id)"
}
JSON
aws dynamodb put-item --cli-input-json file://create-order.json
aws dynamodb get-item \
  --table-name labex-d04-orders \
  --key '{"order_id":{"S":"O100"}}' \
  --consistent-read

O100 contém amount 145, NEW e version 1. A condição de ausência passa para esta chave nova. Observe AWS View na parte superior e expanda O100 para inspecionar o item efetivo.

Rejeite uma duplicata sem bloquear novos pedidos

Nesta etapa, você repetirá um ID de pedido com outro valor. Uma sobrescrita insegura mudaria O100 para 999; a condição deve rejeitá-la e preservar 145. Uma duplicata rejeitada é uma resposta de erro, não automaticamente uma resposta idempotente bem-sucedida da aplicação. A aplicação deve decidir como tratar esse erro.

Uma nova tentativa de criação não deve substituir o valor já aceito para um pedido. A condição de ausência protege essa decisão na gravação. Um filtro de leitura altera os itens retornados; uma condição de gravação decide se a alteração pode ocorrer. Cada solicitação concorrente precisa da própria condição.

Escreva a solicitação concorrente com a mesma chave e condição de ausência.

cat > duplicate-order.json <<'JSON'
{
  "TableName": "labex-d04-orders",
  "Item": {
    "order_id": {
      "S": "O100"
    },
    "amount": {
      "N": "999"
    },
    "status": {
      "S": "NEW"
    },
    "version": {
      "N": "1"
    }
  },
  "ConditionExpression": "attribute_not_exists(order_id)"
}
JSON

Repita a gravação com a mesma chave. || true permite que o terminal continue após esta falha esperada. Confira ConditionalCheckFailedException; um erro de conexão ou autorização exigiria outro diagnóstico.

aws dynamodb put-item --cli-input-json file://duplicate-order.json || true
aws dynamodb get-item \
  --table-name labex-d04-orders \
  --key '{"order_id":{"S":"O100"}}' \
  --consistent-read

Espere ConditionalCheckFailedException e depois amount 145 e version 1 inalterados. Comprove que a mesma proteção permite outro ID de pedido em vez de negar todas as gravações.

cat > second-order.json <<'JSON'
{
  "TableName": "labex-d04-orders",
  "Item": {
    "order_id": {
      "S": "O101"
    },
    "amount": {
      "N": "200"
    },
    "status": {
      "S": "NEW"
    },
    "version": {
      "N": "1"
    }
  },
  "ConditionExpression": "attribute_not_exists(order_id)"
}
JSON
aws dynamodb put-item --cli-input-json file://second-order.json
aws dynamodb get-item \
  --table-name labex-d04-orders \
  --key '{"order_id":{"S":"O101"}}' \
  --consistent-read

O101 é criado com amount 200, NEW e version 1. AWS View mostra ambos os pedidos enquanto amount de O100 permanece 145. Essas operações impedem uma sobrescrita não pretendida deste registro identificado por chave; elas não implementam transações de vários itens nem desduplicação de pagamentos.

Proteja uma atualização com a versão esperada

Nesta etapa, você aplicará uma transição de estado apenas à versão que leu. Uma condição de versão otimista recusa uma solicitação desatualizada em vez de sobrescrever silenciosamente uma decisão mais recente. A gravação verifica atomicamente a versão 1, define PAID e avança a versão para 2.

A primeira atualização corresponde à versão 1; repetir essa expectativa falha quando o item está na versão 2.

Escreva e execute esta solicitação UpdateItem. Aliases de nomes de expressão tratam nomes de atributos com segurança, e valores tipados fornecem as versões esperada e seguinte.

cat > pay-order.json <<'JSON'
{
  "TableName": "labex-d04-orders",
  "Key": {
    "order_id": {
      "S": "O100"
    }
  },
  "UpdateExpression": "SET #state = :paid, #version = :next",
  "ConditionExpression": "#version = :expected",
  "ExpressionAttributeNames": {
    "#state": "status",
    "#version": "version"
  },
  "ExpressionAttributeValues": {
    ":paid": {
      "S": "PAID"
    },
    ":next": {
      "N": "2"
    },
    ":expected": {
      "N": "1"
    }
  },
  "ReturnValues": "ALL_NEW"
}
JSON
aws dynamodb update-item --cli-input-json file://pay-order.json

ALL_NEW mostra PAID e version 2 com amount 145. Repita a solicitação inalterada, que ainda espera a versão 1.

aws dynamodb update-item --cli-input-json file://pay-order.json || true
aws dynamodb get-item \
  --table-name labex-d04-orders \
  --key '{"order_id":{"S":"O100"}}' \
  --consistent-read

Espere ConditionalCheckFailedException. A leitura final mantém PAID, version 2 e amount 145; O101 permanece NEW/version 1. Um solicitante em produção releria o estado atual e decidiria se outra atualização é apropriada, em vez de repetir a expectativa desatualizada para sempre.

O item efetivo mantém seu valor após a criação protegida e registra a transição de pagamento com versão.

Remova apenas a tabela de pedidos do exercício

Nesta etapa, você confirmará o estado final e limpará a tabela do exercício. Os arquivos JSON salvos descrevem solicitações; os itens do serviço comprovam seus efeitos efetivos. Não use o histórico de comandos nem um log de erros como prova de que a tabela foi removida.

aws dynamodb scan --table-name labex-d04-orders --consistent-read

O100 está PAID/version 2/amount 145; O101 está NEW/version 1/amount 200. Exclua apenas esta tabela e espere por sua ausência.

aws dynamodb delete-table --table-name labex-d04-orders
aws dynamodb wait table-not-exists --table-name labex-d04-orders
aws dynamodb list-tables
aws dynamodb get-item \
  --table-name labex-d04-reference \
  --key '{"id":{"S":"platform"}}' \
  --consistent-read

Resta apenas a tabela de referência, com nota keep unchanged. Essas consultas bem-sucedidas distinguem exclusão de uma conexão malsucedida. AWS View remove a tabela de pedidos. A conexão descartável termina com esta VM.

Resumo

Você protegeu a criação de um novo pedido com uma condição de ausência, observou a rejeição de uma duplicata sem alterar o valor armazenado e permitiu outra chave nova. Depois, usou uma condição de versão esperada para aplicar uma transição de estado e rejeitar uma repetição desatualizada. Você inspecionou os valores finais efetivos dos itens antes de excluir apenas a tabela do exercício.

Para aprofundar o tema, consulte a documentação da AWS: Expressões de condição para gravações protegidas.