Step Functions para no morir. Parte 5: Step Manual.

Giuliana Olmos - Mar 2 '22 - - Dev Community

Buenas!! :D
Hago el post hoy porque ayer fue feriado.

Imagen de buen miercoles

En este capítulo vamos a estar viendo los siguientes temas.

  1. Step Functions actualizada.
  2. Lambdas dentro de la step.
  3. Lambda fuera de la step.
  4. Eventos HTTP.
  5. Roles.
  6. Nuevas validaciones.
  7. Pruebas.

En el primer capítulo, hablamos de las task manuales. Las cuales, son las tasks que dependen de una confirmación externa para poder continuar su funcionamiento.

Ejemplo gráfico

Tareas Manuales

En este capítulo vamos a agregar este tipo de task a nuestra máquina de estado actual.

Step Functions actualizada.

En esta imagen tenemos el ejemplo de la step function que vamos a querer crear.

Nueva orquestación de la stepFunction

Para ello vamos a empezar orquestando las lambdas extras que vamos a utilizar.

En el archivo .asl de la máquina de estados, dentro de States y arriba de la task Medios de pago, vamos a agregar el siguiente código.

"States": {
       "Obtener Planes": {
           "Type": "Task",
           "Resource": "arn:aws:states:::lambda:invoke",
           "Parameters": {
               "FunctionName": {
                   "Fn::GetAtt": [
                       "obtenerPlanes",
                       "Arn"
                   ]
               },
               "Payload": {
                   "Input.$": "$"
               }
           },
           "Next": "Elegir planes",
           "Catch": [
               {
                   "ErrorEquals": [
                       "Error"
                   ],
                   "Next": "Lambda Error"
               }
           ]
       },
       "Elegir planes": {
           "Type": "Task",
           "Resource": "arn:aws:states:::lambda:invoke.waitForTaskToken",
           "TimeoutSeconds": 300,
           "Parameters": {
               "FunctionName": {
                   "Fn::GetAtt": [
                       "elegirPlanes",
                       "Arn"
                   ]
               },
               "Payload": {
                   "Input.$": "$",
                   "token.$": "$$.Task.Token"
               }
           },
           "Next": "Medios de Pago",
           "Catch": [
               {
                   "ErrorEquals": [
                       "Error"
                   ],
                   "Next": "Lambda Error"
               }
           ]
       },
       "Medios de Pago": {
           "Type": "Choice",
           "Choices": [
               {
                   "Variable": "$.medioDePago",
                   "StringEquals": "Debito",
                   "Next": "Pago Debito"
               }
           ],
           "Default": "Pago Credito"
       },
Enter fullscreen mode Exit fullscreen mode

Estas van a ser dos states de tipo Task que van a tener como recursos lambdas (aún no creadas).

  • Obtener Planes: Es un state de tipo Task que va a obtener los planes de wifi que ofrece la empresa.
  • Elegir planes : Es un state de tipo Task, qué diferencia de obtener planes, su resource va a agregar en el invoke la siguiente prop .waitForTaskToken. Y este tipo de lambda va a necesitar un task token que se va a agregar en el apartado de Payload.
               "Payload": {
                   "Input.$": "$",
                   "token.$": "$$.Task.Token"
               }
Enter fullscreen mode Exit fullscreen mode

Elegir planes va a ser la encargada de enviar las opciones al cliente.
Mientras espera la respuesta va a quedar en un estado de Pending, hasta que reciba la data necesaria para continuar.

Por eso lleva la propiedad de TimeoutSeconds, para regular el consumo de memoria de nuestra máquina de estado y que no quede en pending eternamente.

"TimeoutSeconds": 300,
Enter fullscreen mode Exit fullscreen mode

Es importante cambiar el valor de la propiedad StartAt por Obtener Planes porque ahora nuestra máquina de estado comienza con un nuevo state.

Todavía no podemos hacer el deploy porque las lambdas que pasamos por parámetro no existen realmente.

Lambdas dentro de la step.

Lambda ObtenerPlanes

Vamos a comenzar creando la lambda obtenerPlanes.js. Lo que quiero que devuelva es un json con los distintos planes a los que puede acceder el cliente. Los voy a importar desde un json porque después quiero usarlos.

const planes = require("./../resources/planes")

const obtenerPlanes = () => {
 return planes
};

exports.handler = (event, context, callback) => {
 callback(null, obtenerPlanes());
};
Enter fullscreen mode Exit fullscreen mode

En el const planes tenemos el require.
Yo guarde el json en resources => planes.js

planes.js

exports.planes = [
   {
       "plan": "1MB",
       "precio": 1000
   },
   {
       "plan": "10MB",
       "precio": 2000
   },
   {
       "plan": "50MB",
       "precio": 5000
   },
   {
       "plan": "100MB",
       "precio": 8000
   }
]
Enter fullscreen mode Exit fullscreen mode

Lambda elegirPlanes

Vamos a crear la lambda elegirPlanes.js. En esta vamos a tener distintos pasos. Primero debemos instalar el paquete de aws-sdk
Es importante instalarlo en el devDependecies para que no sobrecargue a la lambda.

npm install –save-dev aws-sdk
Enter fullscreen mode Exit fullscreen mode

Una vez instalado, ya lo podemos hacer importar y comenzar a trabajar en nuestra lambda.

const AWS = require('aws-sdk');

const getParameters = (event) => {
 const urlQueue = process.env.URL_SQS || '';
 console.log(urlQueue);
 if (urlQueue === '') {
   throw new Error('La URL no existe')
 }

 const params = {
   MessageBody: JSON.stringify({
     planes: event.Input.Payload,
     taskToken: event.token
   }),
   QueueUrl: urlQueue,
 };
 return params;
};
exports.handler = async (event) => {
 try {
   const sqs = new AWS.SQS();
   console.log('event:  ', JSON.stringify(event, null, 2));
   const params = getParameters(event);
   console.log(params);
   await sqs.sendMessage(params).promise();
   return event;
 } catch (e) {
   throw new Error(e);
 }
};
Enter fullscreen mode Exit fullscreen mode

Lo que deseamos hacer en esta lambda, es enviar los planes al cliente mediante la cola de sqs que creamos antes.

Vamos a instanciar el servicio de sqs con aws en el handler.

const sqs = new AWS.SQS();
Enter fullscreen mode Exit fullscreen mode

Luego, para enviar un mensaje a la cola de sqs debemos correr el siguiente código.

await sqs.sendMessage(params).promise();
Enter fullscreen mode Exit fullscreen mode

De dónde sale esa información?
De la documentación de aws-sdk para sqs.

Sabemos que necesitamos los parámetros para que el mensaje sea enviado. Para eso vamos a trabajar sobre la función de getParameters()que nos tiene que devolver estos parámetros.

const params = {
   MessageBody: JSON.stringify({
     planes: event.Input.Payload,
     taskToken: event.token
   }),
   QueueUrl: urlQueue,
 };
Enter fullscreen mode Exit fullscreen mode

Los parámetros a devolver son

  • el mensaje que queremos enviar.
  • el token que vamos a necesitar para referirnos a la instancia de la stepFunction.
  • la url de la cola de sqs.

La url de la cola de sqs, la podíamos importar desde el stack de cloudFormation (tal cual lo hacemos en el archivo asl en el state final donde mandamos el mensaje). Pero a esa variable la vamos a importar a nivel serverless (lo vamos a ver en unos párrafos más adelante).

En la lambda lo importamos de la siguiente manera

const urlQueue = process.env.URL_SQS || '';
Enter fullscreen mode Exit fullscreen mode

Lambdas en serverless

Vamos a agregar las dos funciones junto las que ya estaban creadas. (En el apartado functions)

  obtenerPlanes:
    handler: ./src/lambdas/obtenerPlanes.handler
  elegirPlanes:
    handler: ./src/lambdas/elegirPlanes.handler
Enter fullscreen mode Exit fullscreen mode

La parte importante está en elegirPlanes porque es donde debemos agregar la url de la sqs.
La agregamos en el serverless.yml y sobre la lambda en donde queremos importar.
¿Por qué? Porque es la forma más segura de crear variables de entorno seguras, ya que evitamos que el resto de las lambdas acceda a información que no necesitan.

    environment:
      URL_SQS: ${cf:contratarwifiplan-${opt:stage, 'dev'}.SendQueueURL}
Enter fullscreen mode Exit fullscreen mode

Como ven, lo importamos desde el stack de cloudFormation como en el capítulo pasado.

Y la lambda de elegir planes debería quedar así.

  elegirPlanes:
    handler: ./src/lambdas/elegirPlanes.handler
    environment:
      URL_SQS: ${cf:contratarwifiplan-${opt:stage, 'dev'}.SendQueueURL}
Enter fullscreen mode Exit fullscreen mode

Lambdas fuera de la step.

Cuando enviemos la data al cliente con la lambda elegirPlanes.js, esta Task va a quedar pendiente esperando una respuesta.
Para retomar el flujo de la stepFunction necesitamos de una lambda que, usando el token de único uso que le enviamos al cliente, “reviva” la stepFunction para que continúe su flujo.

Esto lo creamos de la siguiente manera:
Creamos la lambda llamada recibirRespuesta.js, la cual va a recibir la respuesta del cliente, y enviar la señal a la step function para continuar.
Esta lambda no forma parte del flujo que escribimos en el asl.

El código es el siguiente:

const AWS = require('aws-sdk');

const recibirRespuesta = (event) => {
   const eventParse = JSON.parse(event.body);

   console.log(eventParse)

   return {
       output: JSON.stringify(eventParse),
       taskToken: eventParse.taskToken,
   };
};
exports.handler = async (event) => {
   const params = recibirRespuesta(event);
   try {
       const stepfunctions = new AWS.StepFunctions();
       console.log(
           `Llamando a la stepFunction con estos parametros ${JSON.stringify(
               params
           )}`
       );
       await stepfunctions.sendTaskSuccess(params).promise();
       return {
           statusCode: 200,
           body: JSON.stringify(params),
       };
   } catch (error) {
       return {
           statusCode: 500,
           body: JSON.stringify(error),
       };
   }
};
Enter fullscreen mode Exit fullscreen mode

Parecido a la lambda elegirPlanes.js, necesitamos instanciar el servicio de stepFunction de aws, importando el aws-sdk.

La función que se utiliza en estos casos es la de sendTaskSucces() que comunica el mensaje de éxito para que la step Function continúe.

Les dejo la documentación del aws-sdk.

Ahora sabemos cuales son los parámetros necesarios para esta función.

return {
       output: JSON.stringify(eventParse),
       taskToken: eventParse.taskToken,
   };
Enter fullscreen mode Exit fullscreen mode

En la prop output va a ir la data que necesitamos que la task elegiPlanes devuelva como output, y el tasktoken nos sirve para hacer referencia a cuál instancia de StepFunction nos estamos refiriendo.

La razón de estos returns

return {
           statusCode: 200,
           body: JSON.stringify(params),
       };
Enter fullscreen mode Exit fullscreen mode

lo vamos a estar explicando más adelante cuando hablemos de los eventos HTTP que despiertan ciertas lambdas.

Lambda en serverless

Vamos a declarar la lambda en el serverless.yml

recibirRespuesta:
   handler: ./src/lambdas/recibirRespuesta.handler
Enter fullscreen mode Exit fullscreen mode

Eventos HTTP.

En el capítulo anterior habíamos visto como una cola de sqs podía despertar una lambda.
En este, vamos a ver que las lambdas también pueden ser despertadas por eventos http, trabajando con el servicio de ApiGateway.

¿Cómo lo configuramos?
Vamos a querer que nuestra lambda recibirRespuesta sea despertada por un POST con los datos del servicio y método de pago que eligió el cliente.
Debajo del handler de la función, vamos a agregar el siguiente código.

    events:
      - http:
         path: /contratar-wifi/recibirRespuesta
         method: post
Enter fullscreen mode Exit fullscreen mode

El método que queremos utilizar, es un post, y en el path va la ruta del endpoint.

En el servicio de ApiGateway vamos a poder acceder al endpoint. Y haciendo click en pruebas se puede acceder al cuerpo del endpoint.

Pantalla Apigateway

En el cuerpo de la solicitud. Va a ir el JSON que queremos enviarle a la lambda.

Ruta en el ApiGateway

Si lo notan, es el json que utilizábamos en el capítulo pasado pero con el agregado del task token.

{
   "servicio": {
       "plan": "15MB",
           "precio": 1000
   },
   "medioDePago": "Debito",
       "tarjeta": {
       "cantCuotas": "06",
           "nroTarjeta": "1234567898745896"
   },
   "taskToken": "AAAAKgAAAAIAAAAAAAAAAQ9OfVcpRULG9PyaPvbJhBV2NFiha4ILZcflTahDJbdQ/gFRlyzjh7UVvijwZyvXMRz64qH1kF3aUkTX18Dh0EfJWZzMJ0zEhPemHjct6KmkWqSb0+BpFmq3x0HlpOlam9W3tXD1Flp7nnaSPs+hfN6877ele8f0721HaQujSasqrQpsNjTVYpiRxrDOL1sgIpv2UX9oflVkETfsYERnce+ijtxdEQVf/nXyizc7F+AZTzIp0AG4FBmS5yNXgSWLWD0cvNHmz2ngtx1Fv3MfhSyAY/f0hpCY1h53fyYqnuodJH3AQiwii6cDHU1Bdd3oGlMioWU5OYXXv/jrZwAuy7oH1CheD91c+b/xerKEfKmn3KM8w6yebO8wWUosq8mbfGbPvaElj8WHkg7YdEmnixFccevbyX5RrVZOuNAGKJp2zBouEa6RcaowISvMv1NMbbiXKPp1MMzx3bfo5+0S+sOjagmneER6O5Y0cZXpeiji/4vGFIcDrd1bEcHID1FNll1OXhWXO8MUb7PHWH07JxnNyV0nrrTNHE4YZZlg6rR48+gD7IaGko5Kc/pzR84CExw1UbWtLMNaYhlP1GVfMkAbJ3/LX0Zocq5kDfZhu2V50l1tHoMqhNTRGo2o824Q+g=="
}
Enter fullscreen mode Exit fullscreen mode

Y por ultima, la razón de los returns en la lambda .

return {
           statusCode: 200,
           body: JSON.stringify(params),
       };
Enter fullscreen mode Exit fullscreen mode

y

return {
           statusCode: 500,
           body: JSON.stringify(error),
       };
Enter fullscreen mode Exit fullscreen mode

Esto sucede porque el evento http necesita que la respuesta cuente con una prop statusCode con un number, y con un body que contenga la data en formato string.

Si hacemos el return diferente, ¿va a funcionar?
La respuesta es , porque el sendTaskSucces() se envía antes que el return, entonces, la stepFunction va a continuar con su ejecución PERO la respuesta que vamos a obtener por http sera de error, por no tener el formato de respuesta correcto.

Roles

No tenemos que olvidar que nuestros servicios a veces necesitan permisos para funcionar y estos se otorgan mediante roles.
En este caso, vamos a necesitar dos roles (que vamos a escribir en resources => LambdaRole.yml) para dos de nuestras lambdas.

1 - El primer role, va a ser para la lambda que contiene el WaitForTaskToken.
Necesitamos permisos :

  • Para loguear la data de la lambda.
  • Para poder enviar mensajes a la cola de SQS.
ElegirPlanesLambdaRole:
 Type: AWS::IAM::Role
 Properties:
   RoleName: ElegirPlanesLambdaRole
   AssumeRolePolicyDocument:
     Statement:
     - Effect: Allow
       Principal:
         Service:
           - 'lambda.amazonaws.com'
       Action:
         - 'sts:AssumeRole'
   Policies:
     - PolicyName: statePolicy
       PolicyDocument:
         Statement:
           - Effect: Allow
             Action:
               - sqs:SendMessage
             Resource:
               - Fn::GetAtt: [SendQueue, Arn]
           - Effect: Allow
             Action:
               - 'logs:CreateLogGroup'
               - 'logs:CreateLogStream'
               - 'logs:PutLogEvents'
               - 'logs:DescribeLogStreams'
             Resource:
               - 'arn:aws:logs:*:*:*'
Enter fullscreen mode Exit fullscreen mode

2 - El segundo role, va a ser para conceder permisos a la lambda que va a recibir la data desde el endpoint y continuar la ejecución de la stepFunctions.
Esos permisos son:

  • El de loguear la información de la lambda
  • El de poder enviar el éxito de la ejecución.
RecibirRespuestasLambdaRole:
 Type: AWS::IAM::Role
 Properties:
   RoleName: RecibirRespuestasLambdaRole
   AssumeRolePolicyDocument:
     Statement:
     - Effect: Allow
       Principal:
         Service:
           - 'lambda.amazonaws.com'
       Action:
         - 'sts:AssumeRole'
   Policies:
     - PolicyName: statePolicy
       PolicyDocument:
         Statement:
           - Effect: Allow
             Action:
               - states:SendTaskSuccess
               - states:SendTaskFailure
             Resource: "*"
           - Effect: Allow
             Action:
               - 'logs:CreateLogGroup'
               - 'logs:CreateLogStream'
               - 'logs:PutLogEvents'
               - 'logs:DescribeLogStreams'
             Resource:
               - 'arn:aws:logs:*:*:*'
Enter fullscreen mode Exit fullscreen mode

Y por último vamos a importar, los roles en serverless.yml y después vamos a asignarlos en las lambdas correspondientes.

Nuestras importaciones en resources deberían quedar de la siguiente manera, con los dos nuevos roles agregados.

resources: 
  Resources: 
    SendQueue: ${file(./src/resources/SQS.yml):SendQueue}
    SendQueueDLQ: ${file(./src/resources/SQS.yml):SendQueueDLQ}
    ContratarServicioWifiMachineRole: ${file(./src/resources/StepFunctionsRole.yml):ContratarServicioWifiMachineRole}
    ElegirPlanesLambdaRole: ${file(./src/resources/LambdaRole.yml):ElegirPlanesLambdaRole}
    RecibirRespuestasLambdaRole: ${file(./src/resources/LambdaRole.yml):RecibirRespuestasLambdaRole}
Enter fullscreen mode Exit fullscreen mode

Y las lambdas deberían quedar de la siguiente manera.

  elegirPlanes:
    handler: ./src/lambdas/elegirPlanes.handler
    environment:
      URL_SQS: ${cf:contratarwifiplan-${opt:stage, 'dev'}.SendQueueURL}
    role:
      Fn::GetAtt: ['ElegirPlanesLambdaRole', 'Arn']
Enter fullscreen mode Exit fullscreen mode

y

  recibirRespuesta:
    handler: ./src/lambdas/recibirRespuesta.handler
    events:
      - http:
         path: /contratar-wifi/recibirRespuesta
         method: post
    role:
      Fn::GetAtt: ['RecibirRespuestasLambdaRole', 'Arn']
Enter fullscreen mode Exit fullscreen mode

Actualizar roles de las step

Como agregamos nuevas lambdas a nuestra step function debemos ir a el archivo StepFunctionsRole.yml y agregarlas tambien en el role.

- !Join ['-', [ !Join [':', ['arn:aws:lambda',!Ref 'AWS::Region', !Ref 'AWS::AccountId' ,'function', !Ref 'AWS::StackName']], 'obtenerPlanes' ]]
- !Join ['-', [ !Join [':', ['arn:aws:lambda',!Ref 'AWS::Region', !Ref 'AWS::AccountId' ,'function', !Ref 'AWS::StackName']], 'elegirPlanes' ]]
Enter fullscreen mode Exit fullscreen mode

Agregar nuevas validaciones

Antes de terminar la máquina de estado, y poder hacer nuestras pruebas, necesitamos agregar unas nuevas validaciones en las lambdas de pagos.
Queremos asegurarnos que los planes que elige el cliente, pertenecen a la oferta de la empresa.

En ambas lambdas debemos importar los planes ofrecidos.

const planes = require("./../resources/planes")
Enter fullscreen mode Exit fullscreen mode

Y luego la función que va a validar la existencia.

const validarPlan = (data) => {
 const { plan } = data.servicio;
 console.log(plan);
 console.log(planes.planes.length);
 let fueValidado = false;
 let arrayPlanes = planes.planes
 for(let i = 0; i < arrayPlanes.length; i++) {
   console.log('entro');
   console.log( arrayPlanes[i].plan + "   " + plan);
   if (arrayPlanes[i].plan == plan) {
     fueValidado = true;
     return
   }
 }
 console.log(fueValidado);
 if (!fueValidado) throw new Error('El plan no existe')
}
Enter fullscreen mode Exit fullscreen mode

En mi caso agregue esta validación dentro de la función pagoConDebito y pagoConDebito. Debajo de validarPago, agrego:

validarPlan(inputData)
Enter fullscreen mode Exit fullscreen mode

Ahora sí, ya tenemos nuestra StepFunction completa.

Y podemos correr el

sls deploy
Enter fullscreen mode Exit fullscreen mode

Pruebas

Una vez que tengamos nuestra stepFunction en la nube vamos a comenzar con las pruebas.

A la hora de iniciar la ejecución, el json con el que lo iniciamos no es de gran importancia.

Nueva ejecución con json indiferente

Una vez iniciada podemos ver como la lambda que tiene el resource waitForTaskToken queda pendiente después de enviar la data.

StepFunction con ejecución pendiente

Si vamos al historial de ejecuciones, vamos a poder sacar el token que necesitamos para hacer referencia a la misma instancia.

Historial de ejecución con el token

Con esta data, vamos a ir al servicio de apiGateway así continuamos con la ejecución.

CASO DE ERROR

En el cuerpo del endpoint debemos usar el siguiente json.

 {
     "servicio": {
         "plan": "15MB",
             "precio": 1000
     },
     "medioDePago": "Debito",
         "tarjeta": {
         "cantCuotas": "06",
             "nroTarjeta": "1234567898745896"
     },
     "taskToken": "AAAAKgAAAAIAAAAAAAAAAYWwkS4HEc5xR92k3T7sftkXFTOXMIE06rDrmlQ5Fr7rFSgqK+lIC6T2xB5mOydgGAdRNhjJk6zHuMhriHC1YeYmTdRVwx1m6i8t0ZpGgeD+2xDhw7oCE7uomervRzTQshROjUIgyXFuK4zP7EkqDg952/V1vFO/rw4k7eCufoKfnjkrFEwnyWj31V5cIUWSfZyjF5xe4KPrvzACqR2TZFdKu5SPpU5vikBPpmdIVyFMnSudPR1asv7j3hEvjF/ZKrYSPDok27wLjH9shaYysPncEiDbe1AysIq10bbI+YyeeUWm7kWC4xeVJcNqv5aupX2xGifWmolvvXlHFCXAjpoUTkPNpYO1jrgE2/p2QBGURzDaEWgs4ffJLxMGwdVDYeRZPK+y1EmESnbk5zys38MNy3iQVd++vvFD90EzOKAHpGGQ9iXBvp12prXbywUg/CUSxPBS/wKQCSsdYjImfLC+NXgXCDXmi8Bsc980vyXnZfVEc6Aq8h7NKE6rJTBkCb1BD34rox1Rqs4zkp31Gf57E33tC5oJSIStbNx2ltSJPMOKqOeQvaKmzI30lsfudpM56mEWnV8vEykyLfGTwxZymHj1U3RUaLhbIoKI7GzMggFDuwy9uZhDVXzak0A7rQ=="
 }
Enter fullscreen mode Exit fullscreen mode

Recuerden que debemos modificar el token con el valor que obtengan de su ejecución.
Si el endpoint está construido de manera correcta y el json está correcto, el resultado debería ser el siguiente.

Respuesta 200 por HTTP en ApiGateway

Al volver a la ejecución, debemos notar que la stepFunction terminó con error debido a que el plan no existe en la oferta.

Ejecución con plan inexistente

CASO DE ÉXITO

En el caso de éxito el json debería ser el siguiente.

 {
     "servicio": {
         "plan": "1MB",
             "precio": 1000
     },
     "medioDePago": "Debito",
         "tarjeta": {
         "cantCuotas": "06",
             "nroTarjeta": "1234567898745896"
     },
     "taskToken": "AAAAKgAAAAIAAAAAAAAAAYWwkS4HEc5xR92k3T7sftkXFTOXMIE06rDrmlQ5Fr7rFSgqK+lIC6T2xB5mOydgGAdRNhjJk6zHuMhriHC1YeYmTdRVwx1m6i8t0ZpGgeD+2xDhw7oCE7uomervRzTQshROjUIgyXFuK4zP7EkqDg952/V1vFO/rw4k7eCufoKfnjkrFEwnyWj31V5cIUWSfZyjF5xe4KPrvzACqR2TZFdKu5SPpU5vikBPpmdIVyFMnSudPR1asv7j3hEvjF/ZKrYSPDok27wLjH9shaYysPncEiDbe1AysIq10bbI+YyeeUWm7kWC4xeVJcNqv5aupX2xGifWmolvvXlHFCXAjpoUTkPNpYO1jrgE2/p2QBGURzDaEWgs4ffJLxMGwdVDYeRZPK+y1EmESnbk5zys38MNy3iQVd++vvFD90EzOKAHpGGQ9iXBvp12prXbywUg/CUSxPBS/wKQCSsdYjImfLC+NXgXCDXmi8Bsc980vyXnZfVEc6Aq8h7NKE6rJTBkCb1BD34rox1Rqs4zkp31Gf57E33tC5oJSIStbNx2ltSJPMOKqOeQvaKmzI30lsfudpM56mEWnV8vEykyLfGTwxZymHj1U3RUaLhbIoKI7GzMggFDuwy9uZhDVXzak0A7rQ=="
 }
Enter fullscreen mode Exit fullscreen mode

Al igual que en el caso de error la respuesta por http debe dar 200, pero la step function debe continuar su ejecución sin error.

Ejecución de step function correcta

Final

Bueno, llegamos al final de este capítulo.

Gif llegamos al final

Y ya tenemos una stepFunction que tiene una intervención manual por parte del cliente. :D

Espero que les haya sido de ayuda.
Recuerden que si quieren, pueden invitarme un cafecito.

Invitame un café en cafecito.app

Y que si tienen alguna duda pueden dejarla en los comentarios.

. . . . . . . . . . . . .