Introduction
Un traitement enregistre une commande, mais échoue avant que son résultat atteigne le workflow. Vous protégerez cette écriture pour qu'une reprise ou une nouvelle exécution préserve la commande initiale, tout en permettant à une autre commande de réussir.
Terminez d'abord Réessayer une étape échouée et gérer les erreurs permanentes, Empêcher les commandes en double avec des écritures conditionnelles et Configurer et diagnostiquer une fonction Lambda. Cette VM indépendante fournit un traitement non protégé ; vous ajoutez la protection.
Lien avec les certifications
Ce laboratoire propose une pratique des sujets d’examen suivants.
- Solutions Architect – Associate (SAA-C03) · Tâche 2.1: Workflows sûrs en cas de répétition et préservation des résultats stockés.
- Developer – Associate (DVA-C02) · Tâche 1.1: Workflows sûrs en cas de répétition et préservation des résultats stockés.
- DevOps Engineer – Professional (DOP-C02) · Tâche 5.1: Pratique des fondamentaux : Workflows sûrs en cas de répétition et préservation des résultats stockés.
- Solutions Architect – Professional (SAP-C02) · Tâche 2.4: Pratique des fondamentaux : Workflows sûrs en cas de répétition et préservation des résultats stockés.
Protéger l'écriture métier avec une clé stable
Dans cette étape, déployez un traitement qui considère une commande identique répétée comme une écriture déjà terminée.

Des noms d'exécution différents peuvent désigner la même commande métier. Un conflit d'écriture conditionnelle sur des données identiques renvoie un résultat de doublon sans remplacer cette commande.
Utilisez AWS View, à côté de Terminal, pour comparer les requêtes CLI aux ressources et résultats réels de ce lab. Préservez les données de référence fournies.
L'identifiant de commande est une clé métier : il identifie la commande voulue même si les tentatives de tâche ou les noms d'exécution du workflow diffèrent. attribute_not_exists(id) autorise uniquement le premier PutItem. DynamoDB évalue cette condition de manière atomique. ReturnValuesOnConditionCheckFailure:ALL_OLD fournit l'élément existant lorsqu'une reprise perd la course à l'écriture conditionnelle. Ne renvoyez un résultat de doublon que si cet élément existant est égal à la commande voulue ; un contenu différent doit toujours provoquer un échec au lieu de remplacer silencieusement une commande.
Le code de départ fourni effectue une écriture sans condition. Remplacez-le par le gestionnaire complet protégé ci-dessous. Il utilise l'environnement SDK configuré, sans identifiants personnels. Les tentatives de diagnostic suivent les appels réels séparément des écritures métier. Dans le mode artificiel after-write, la première tentative déclenche une erreur après l'écriture native, créant l'incertitude que la reprise doit gérer. Le mode summary lit l'élément réellement enregistré.
Un here-document dont le délimiteur est entre guillemets écrit le fichier Python littéralement. Le gestionnaire Lambda existant reste app.handler.
cd /home/labex/project
cat > app.py <<'PYTHON'
import json
import os
import boto3
from botocore.exceptions import ClientError
class TransientOrderError(Exception):
pass
class InvalidOrder(Exception):
pass
def handler(event, context):
print('WORKFLOW '+json.dumps(event,sort_keys=True))
db=boto3.client('dynamodb')
if event.get('stage')=='summary':
item=db.get_item(
TableName=os.environ['ORDERS_TABLE'],
Key={'id':{'S':event['id']}},
)['Item']
result={'id':event['id'],'total_cents':int(item['total_cents']['N']),'completed':True}
print('RESULT '+json.dumps(result,sort_keys=True))
return result
if event.get('mode')=='permanent':
raise InvalidOrder('Order cannot be completed')
quantity=event['quantity']
if isinstance(quantity,bool) or not isinstance(quantity,int) or not 1<=quantity<=10:
raise InvalidOrder('Invalid quantity')
attempt=db.update_item(
TableName=os.environ['ATTEMPTS_TABLE'],
Key={'id':{'S':event['id']}},
UpdateExpression='ADD attempts :one',
ExpressionAttributeValues={':one':{'N':'1'}},
ReturnValues='UPDATED_NEW',
)['Attributes']['attempts']['N']
if event.get('mode')=='flaky' and attempt=='1':
raise TransientOrderError('Dependency temporarily unavailable')
item={'id':{'S':event['id']},'quantity':{'N':str(quantity)},'total_cents':{'N':str(quantity*250+100)}}
duplicate=False
try:
db.put_item(
TableName=os.environ['ORDERS_TABLE'],
Item=item,
ConditionExpression='attribute_not_exists(id)',
ReturnValuesOnConditionCheckFailure='ALL_OLD',
)
except ClientError as error:
if (
error.response['Error']['Code']!='ConditionalCheckFailedException'
or error.response.get('Item')!=item
):
raise
duplicate=True
if event.get('mode')=='after-write' and attempt=='1':
raise TransientOrderError('Result delivery failed after the write')
result={'id':event['id'],'quantity':quantity,'total_cents':quantity*250+100,'duplicate':duplicate}
print('RESULT '+json.dumps(result,sort_keys=True))
return result
PYTHON
Le bloc try effectue l'écriture conditionnelle native. Seule une véritable ConditionalCheckFailedException avec un ancien élément identique est traitée comme un doublon ; les autres erreurs du SDK sont relancées. L'exception temporaire survient après cette décision d'écriture : le workflow réessaiera donc réellement un travail dont l'élément métier existe déjà.
Empaquetez le fichier avec zip -j, qui omet les chemins de répertoire, et mettez à jour le traitement fourni avec --zip-file fileb:// pour transmettre les octets de l'archive binaire. Le champ CodeSha256 de la réponse identifie l'archive déployée. Le déploiement seul ne prouve pas la protection contre les doublons ; l'étape d'exécution la testera.
zip -j function.zip app.py
aws lambda update-function-code \
--function-name labex-ev05-worker \
--zip-file fileb://function.zip \
--query CodeSha256 \
--output text
Lancez la vérification du déploiement avant de créer des exécutions.
Connecter un workflow aux autorisations ciblées avec des reprises limitées
Dans cette étape, créez un rôle et une machine de workflow indépendants capables de réessayer après l'échec du traitement survenu après l'écriture.
Step Functions a besoin d'une relation de confiance avec le service states et d'une autorisation InvokeFunction distincte ciblant exactement la fonction. Le traitement conserve ses propres autorisations DynamoDB ; le rôle du workflow se limite à l'appeler. Les affectations du shell enregistrent les identifiants renvoyés. --query et --output text sélectionnent des valeurs réutilisables, file:// lit le JSON littéral de confiance, et le shell insère l'ARN du traitement dans le document d'autorisations.
cd /home/labex/project
WORKER_NAME=labex-ev05-worker
WORKER_ARN=$(aws lambda get-function-configuration \
--function-name labex-ev05-worker \
--query FunctionArn \
--output text)
aws lambda get-function-configuration \
--function-name labex-ev05-worker \
--query '{Name:FunctionName,Role:Role,Runtime:Runtime,Timeout:Timeout}'
aws stepfunctions list-state-machines
cat > workflow-trust.json <<'JSON'
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "states.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
JSON
ROLE_ARN=$(aws iam create-role \
--role-name labex-ev05-workflow-role \
--assume-role-policy-document file://workflow-trust.json \
--query Role.Arn \
--output text)
cat > workflow-invoke.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "lambda:InvokeFunction",
"Resource": "$WORKER_ARN"
}
]
}
EOF
aws iam put-role-policy \
--role-name labex-ev05-workflow-role \
--policy-name InvokeWorker \
--policy-document file://workflow-invoke.json
aws iam get-role-policy --role-name labex-ev05-workflow-role --policy-name InvokeWorker
La liste vide des machines confirme qu'aucun workflow d'une VM précédente n'est réutilisé. L'autorisation utilise l'ARN de la fonction, tandis que chaque état Task utilise son nom ordinaire. Retry traite uniquement TransientOrderError, avec un intervalle d'une seconde, une attente qui double et au plus deux reprises après la première tentative ; Catch dirige InvalidOrder vers un état Fail explicite. ResultSelector conserve le Payload réel, ResultPath le préserve sous saved et OutputPath renvoie le récapitulatif réel.
Un here-document dont le délimiteur est entre guillemets écrit l'ASL littéralement. jq --arg remplace les valeurs de substitution du traitement avant la création de la machine.
cat > workflow-template.json <<'JSON'
{
"StartAt": "CheckQuantity",
"States": {
"CheckQuantity": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.quantity",
"NumericGreaterThan": 0,
"Next": "StoreOrder"
}
],
"Default": "Rejected"
},
"StoreOrder": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": {
"FunctionName": "WORKER_NAME",
"Payload.$": "$"
},
"ResultSelector": {
"result.$": "$.Payload"
},
"ResultPath": "$.saved",
"Retry": [
{
"ErrorEquals": [
"TransientOrderError"
],
"IntervalSeconds": 1,
"BackoffRate": 2,
"MaxAttempts": 2
}
],
"Catch": [
{
"ErrorEquals": [
"InvalidOrder"
],
"Next": "Rejected",
"ResultPath": "$.failure"
}
],
"Next": "ReadSummary"
},
"ReadSummary": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": {
"FunctionName": "WORKER_NAME",
"Payload": {
"stage": "summary",
"id.$": "$.saved.result.id"
}
},
"OutputPath": "$.Payload",
"End": true
},
"Rejected": {
"Type": "Fail",
"Error": "OrderRejected",
"Cause": "Order could not be completed"
}
}
}
JSON
jq --arg worker "$WORKER_NAME" '.States.StoreOrder.Parameters.FunctionName=$worker | .States.ReadSummary.Parameters.FunctionName=$worker' workflow-template.json > workflow.json
MACHINE_ARN=$(aws stepfunctions create-state-machine \
--name labex-ev05-orders \
--type STANDARD \
--role-arn "$ROLE_ARN" \
--definition file://workflow.json \
--query stateMachineArn \
--output text)
aws stepfunctions describe-state-machine \
--state-machine-arn "$MACHINE_ARN" \
--query '{Name:name,Definition:definition}'
La définition native contient les règles Retry/Catch sélectives et les deux tâches réelles. AWS View affiche la machine, sans exécution ni commande pour l'instant. Lancez la vérification de la configuration du workflow.
Prouver une seule écriture métier malgré une reprise et une nouvelle exécution
Dans cette étape, provoquez un échec après l'écriture, répétez la même commande et créez une commande différente.
Les noms d'exécution identifient les exécutions du workflow ; les identifiants de commande identifient les effets métier. La première exécution utilise le mode after-write. Une boucle limitée lit le statut jusqu'à ce qu'il ne soit plus RUNNING ; $(...) capture la sortie et break quitte la boucle. Examinez l'exécution si elle reste RUNNING après la boucle.
FIRST_ARN=$(aws stepfunctions start-execution \
--state-machine-arn "$MACHINE_ARN" \
--name after-write-order \
--input '{"id":"protected-order","quantity":4,"mode":"after-write"}' \
--query executionArn \
--output text)
for attempt in $(seq 1 60); do
STATUS=$(aws stepfunctions describe-execution \
--execution-arn "$FIRST_ARN" \
--query status \
--output text)
if test "$STATUS" != RUNNING; then break; fi
sleep 2
done
aws stepfunctions describe-execution \
--execution-arn "$FIRST_ARN" \
--query '{Status:status,Output:output}'
aws stepfunctions get-execution-history \
--execution-arn "$FIRST_ARN" \
--query 'events[?type==`TaskFailed` || type==`TaskSucceeded`].{Type:type,Error:taskFailedEventDetails.error,Output:taskSucceededEventDetails.output}'
aws dynamodb get-item \
--table-name labex-ev05-orders \
--key '{"id":{"S":"protected-order"}}' \
--query Item
Le premier événement TaskFailed porte TransientOrderError après l'écriture réelle. La reprise réussie renvoie duplicate:true ; le récapitulatif se termine avec un total de 1100. L'unique élément protected-order a une quantité de 4 et un total de 1100. La reprise de la tâche a réussi sans second PutItem accepté pour cette clé métier.
Démarrez une exécution avec un nouveau nom, mais le même identifiant de commande et les mêmes valeurs :
REPEAT_ARN=$(aws stepfunctions start-execution \
--state-machine-arn "$MACHINE_ARN" \
--name repeated-order \
--input '{"id":"protected-order","quantity":4,"mode":"after-write"}' \
--query executionArn \
--output text)
for attempt in $(seq 1 60); do
STATUS=$(aws stepfunctions describe-execution \
--execution-arn "$REPEAT_ARN" \
--query status \
--output text)
if test "$STATUS" != RUNNING; then break; fi
sleep 2
done
aws stepfunctions describe-execution \
--execution-arn "$REPEAT_ARN" \
--query '{Status:status,Output:output}'
aws stepfunctions get-execution-history \
--execution-arn "$REPEAT_ARN" \
--query 'events[?type==`TaskSucceeded`].taskSucceededEventDetails.output'
Cette nouvelle exécution renvoie également un résultat d'enregistrement en doublon et lit le récapitulatif initial de 1100. Un nouveau nom d'exécution ne transforme pas la commande en une nouvelle demande métier. Les échecs réels de la condition préservent l'élément initial au lieu de l'écraser.
Utilisez un identifiant de commande différent pour prouver que la protection ne rejette pas un travail indépendant :
NEW_ARN=$(aws stepfunctions start-execution \
--state-machine-arn "$MACHINE_ARN" \
--name different-order \
--input '{"id":"different-order","quantity":1,"mode":"normal"}' \
--query executionArn \
--output text)
for attempt in $(seq 1 60); do
STATUS=$(aws stepfunctions describe-execution \
--execution-arn "$NEW_ARN" \
--query status \
--output text)
if test "$STATUS" != RUNNING; then break; fi
sleep 2
done
aws stepfunctions describe-execution \
--execution-arn "$NEW_ARN" \
--query '{Status:status,Output:output}'
aws dynamodb scan --table-name labex-ev05-orders --query Items
aws logs filter-log-events \
--log-group-name /aws/lambda/labex-ev05-worker \
--query 'events[].message'
La commande différente réussit avec une quantité de 1 et un total de 350. Il existe deux éléments métier, alors que sept appels réels du traitement ont eu lieu entre les tentatives d'enregistrement et les lectures de récapitulatif. Les journaux et les résultats natifs des tâches montrent les décisions de doublon pour la clé initiale. AWS View affiche trois récapitulatifs réussis et les deux commandes préservées.
L'exemple ci-dessous montre les récapitulatifs réels de la reprise et de la nouvelle exécution, ainsi que la commande initiale préservée et la nouvelle commande distincte.
Il s'agit d'une écriture idempotente pour la demande identique testée, et non d'une promesse que les tâches s'exécutent exactement une fois.
Lancez la vérification de la protection métier.
Supprimer les ressources du workflow et les résultats artificiels
Dans cette étape, supprimez votre machine terminée, le rôle du workflow, les commandes et les journaux, tout en préservant les ressources fournies.
Les trois exécutions sont terminées. Supprimer la machine la retire de la liste des machines actives. Retirez la politique du rôle qui vous appartient avant de supprimer le rôle, puis retirez les commandes artificielles et le groupe de journaux du traitement créés par vos exécutions.
aws stepfunctions delete-state-machine --state-machine-arn "$MACHINE_ARN"
aws iam delete-role-policy --role-name labex-ev05-workflow-role --policy-name InvokeWorker
aws iam delete-role --role-name labex-ev05-workflow-role
aws dynamodb delete-item \
--table-name labex-ev05-orders \
--key '{"id":{"S":"protected-order"}}'
aws dynamodb delete-item \
--table-name labex-ev05-attempts \
--key '{"id":{"S":"protected-order"}}'
aws dynamodb delete-item \
--table-name labex-ev05-orders \
--key '{"id":{"S":"different-order"}}'
aws dynamodb delete-item \
--table-name labex-ev05-attempts \
--key '{"id":{"S":"different-order"}}'
aws logs delete-log-group --log-group-name /aws/lambda/labex-ev05-worker
Consultez des inventaires obtenus avec succès pour prouver ce qui reste :
aws stepfunctions list-state-machines
aws iam list-roles --query 'Roles[].RoleName'
aws dynamodb scan --table-name labex-ev05-orders --query Items
aws logs describe-log-groups --query logGroups
aws dynamodb scan --table-name labex-ev05-reference --query Items
Il ne reste aucune machine active, commande ni groupe de journaux. Seul le rôle du traitement fourni subsiste, et l'élément de référence est inchangé. Conservez le traitement et les tables fournis : ils appartiennent aux ressources de préparation, distinctes du workflow et des ressources que vous avez créés. Les erreurs réseau ou d'authentification ne prouvent jamais une suppression.
Supprimez les fichiers ordinaires créés dans ce lab :
rm -f app.py function.zip workflow-trust.json workflow-invoke.json workflow-template.json workflow.json
AWS View affiche des listes vides pour les machines, les exécutions et les commandes, ainsi que la référence préservée. Lancez la vérification du nettoyage avant de terminer la VM.
Résumé
Vous avez protégé les écritures métier natives avec une clé de commande stable, traité uniquement les conflits conditionnels identiques comme des doublons et testé une défaillance réelle après la première écriture. La reprise et une nouvelle exécution du workflow ont préservé la commande initiale ; une clé métier différente a créé son propre résultat. Vous avez vérifié les historiques et les données natives avant de supprimer les ressources du workflow et les résultats artificiels qui vous appartenaient.



