<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Wojciech Matuszewski</title>
    <description>The latest articles on Forem by Wojciech Matuszewski (@wojciechmatuszewski).</description>
    <link>https://forem.com/wojciechmatuszewski</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F163656%2Fe93f9923-06f4-4629-91db-5160fa1ee51f.jpeg</url>
      <title>Forem: Wojciech Matuszewski</title>
      <link>https://forem.com/wojciechmatuszewski</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/wojciechmatuszewski"/>
    <language>en</language>
    <item>
      <title>Automatic AWS CloudFormation rollbacks upon a test failure in your CI pipelines</title>
      <dc:creator>Wojciech Matuszewski</dc:creator>
      <pubDate>Thu, 10 Nov 2022 04:38:00 +0000</pubDate>
      <link>https://forem.com/aws-builders/automatic-aws-cloudformation-rollbacks-upon-a-test-failure-in-your-ci-pipelines-pfh</link>
      <guid>https://forem.com/aws-builders/automatic-aws-cloudformation-rollbacks-upon-a-test-failure-in-your-ci-pipelines-pfh</guid>
      <description>&lt;p&gt;As serverless usage grows within an organization, developers deploy an ever-increasing number of microservices. In a healthy company culture, these services require tests and validations to indicate that the service is ready for deployment. Since we are only humans, mistakes happen, and sometimes, a given deployment requires a rollback due to failure in said checks.&lt;/p&gt;

&lt;p&gt;Luckily for us, the advent of the &lt;em&gt;infrastructure as code&lt;/em&gt; tools and the cloud made it much easier to incorporate testing, validation checks, and rollback mechanisms in your deployment pipeline.&lt;/p&gt;

&lt;p&gt;This article describes four methods one could use to perform validations on their deployed infrastructure and roll the changes back if necessary in the context of AWS and serverless services.&lt;/p&gt;

&lt;h2&gt;
  
  
  Many ways to test the AWS serverless stack
&lt;/h2&gt;

&lt;p&gt;In my professional career, I've used many techniques to confirm the code I deploy meets the business and customer demands.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Having a vast suite of unit tests for business logic.&lt;/li&gt;
&lt;li&gt;Writing end-to-end and integration tests to ensure the different services work with each other as expected.&lt;/li&gt;
&lt;li&gt;Maintaining a set of canary tests that constantly exercise the API to ensure the service is available to the users.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While having different &lt;em&gt;return on investment&lt;/em&gt; characteristics (for example, the end-to-end and integration tests usually have higher ROI than the unit tests), these methods have been proven reliable through many applications I had the pleasure to work on.&lt;/p&gt;

&lt;p&gt;There is a small catch to consider – &lt;strong&gt;if the tests (be it end-to-end or any other relevant to your situation) do not integrate with &lt;a href="https://aws.amazon.com/cloudformation/" rel="noopener noreferrer"&gt;AWS CloudFormation&lt;/a&gt; deployment lifecycle&lt;/strong&gt;, rolling back the deployment is very hard when they fail. The trick is to ensure that we can safely rollback to the previous application deployment when a test fails. I will show you how to achieve that in the following sections.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmcyn0cpf6ujt4m6a916i.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmcyn0cpf6ujt4m6a916i.jpg" alt="If we do not integrate with AWS CloudFormation, it is very hard to rollback the changes we have made"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the above example, the tests run after the deployment happens. If the tests detect a regression, the developers must manually revert the change to bring the system back to the previous, presumably correct, working state. Such a setup is suboptimal from the operational perspective – it brings toil and frustration, especially in high-stress situations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using CodeDeploy deployment group
&lt;/h3&gt;

&lt;p&gt;AWS's developer suite of products includes the &lt;a href="https://aws.amazon.com/codedeploy/" rel="noopener noreferrer"&gt;AWS CodeDeploy&lt;/a&gt; offering, which can help developers deploy AWS Lambda functions and other compute-related services.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Since this blog focuses on AWS serverless services, we will look at AWS Lambda integration with AWS CodeDeploy and omit other services it integrates with.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The AWS CodeDeploy has the &lt;strong&gt;ability to shift traffic to our AWS Lambda functions gradually, in a specific time window (so-called rolling window)&lt;/strong&gt;. The &lt;a href="https://docs.aws.amazon.com/codedeploy/latest/userguide/deployment-groups.html" rel="noopener noreferrer"&gt;deployment group&lt;/a&gt; functionality &lt;strong&gt;integrates well with AWS CloudFormation&lt;/strong&gt; – the AWS CloudFormation deployment will not finish until we either shift all traffic to our updated AWS Lambda function. If the AWS CodeBuild detects a regression (usually an alarm alarming), it will instruct the AWS CloudFormation to rollback the current deployment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhtty7j8471x11w3urhaw.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhtty7j8471x11w3urhaw.jpg" alt="Using CodeDeploy rolling window deployment"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Keep in mind that we are not constrained to a single alarm. If you wish to monitor multiple alarms, you can create a &lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Create_Composite_Alarm.html" rel="noopener noreferrer"&gt;&lt;em&gt;composite alarm&lt;/em&gt;&lt;/a&gt; for the AWS CodeBuild to keep an eye for.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;main benefit of using the AWS CodeDeploy&lt;/strong&gt; service is that it is a native AWS primitive. It works well for most common use cases and is relatively easy to set up.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;main drawback&lt;/strong&gt; for me personally is the inflexibility of the service. To the best of my knowledge, creating a custom rolling window configuration is impossible. If you wish to do so, you must develop a bit of custom infrastructure (see the &lt;em&gt;"Using the IntrinsicValidator package&lt;/em&gt; section).&lt;/p&gt;

&lt;h3&gt;
  
  
  Using the IntrinsicValidator package
&lt;/h3&gt;

&lt;p&gt;Specific to &lt;a href="https://docs.aws.amazon.com/cdk/api/v2/" rel="noopener noreferrer"&gt;AWS CDK&lt;/a&gt;, the &lt;a href="https://github.com/wheatstalk/cdk-intrinsic-validator" rel="noopener noreferrer"&gt;IntrinsicValidator&lt;/a&gt; is a third-party package that works similarly to the AWS CodeBuild offering. But it is, in my humble opinion, more extendable and flexible than the service mentioned before. The underlying implementation relies on AWS StepFunctions to poll for various checks and alarm statuses.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6nezf93e5yjepwmnj6ll.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6nezf93e5yjepwmnj6ll.jpg" alt="Using the IntrinsicValidator package"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;main benefit of using the IntrinsicValidator over a service like AWS CodeDeploy&lt;/strong&gt; is flexibility. Complete control over how long we probe for an alarm or failure during the deployment is excellent – it allows us to fine-tune the speed of the CI pipelines. If your team is unhappy with monitoring options, you can always fork the construct and amend it to fit your needs.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;main drawback&lt;/strong&gt; I see here is that the &lt;strong&gt;AWS Lambda traffic shifting, which is native to AWS CodeBuild, is not that easy to implement with the IntrinsicValidator&lt;/strong&gt;. I'm unsure how to replicate such functionality using this construct, as the AWS Lambda deployments are "all or nothing" by default (I'm happy to be corrected here).&lt;/p&gt;

&lt;h3&gt;
  
  
  Using a AWS CloudFormation Custom resource
&lt;/h3&gt;

&lt;p&gt;Another way to run code during the AWS CloudFormation deployment is to use the &lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html" rel="noopener noreferrer"&gt;AWS CloudFormation Custom resource&lt;/a&gt;. Here, similar to how the IntrinsicValidator package operates, we instruct the AWS CloudFormation to run a bit of code (in our case an &lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources-lambda.html" rel="noopener noreferrer"&gt;AWS Lambda Function&lt;/a&gt;) during the deployment. But, instead of relying on a collection of resources from a third party, we stick to native AWS primitives.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdsj009vmwg2bhqql3sbd.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdsj009vmwg2bhqql3sbd.jpg" alt="Using the AWS CloudFormation Custom resource Lambda Function"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;main difference between the IntrinsicValidator and the AWS CloudFormation Custom resource&lt;/strong&gt; is developer experience and ergonomics. As mentioned before, the IntrinsicValidator is, at its core, an AWS CloudFormation Custom resource with several layers of abstractions baked in. It handles returning the correct response to the AWS CloudFormation service and other details required to make custom resources work.&lt;/p&gt;

&lt;p&gt;I &lt;strong&gt;would recommend&lt;/strong&gt; using the custom resource for relatively simple one-off checks or &lt;strong&gt;if you do not wish to bring a dependency related to deployments to your project&lt;/strong&gt; (which is understandable and very reasonable). If you go the "raw" custom resource route, make sure to &lt;strong&gt;use any deployment frameworks or Lambda Function-related libraries&lt;/strong&gt;. They usually make it much easier to integrate with the AWS CloudFormation lifecycle correctly (for example, the &lt;a href="https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.custom_resources-readme.html" rel="noopener noreferrer"&gt;&lt;em&gt;custom_resources&lt;/em&gt; AWS CDK package&lt;/a&gt;).&lt;/p&gt;

&lt;h4&gt;
  
  
  Using the cdk-triggers package
&lt;/h4&gt;

&lt;p&gt;Since we are discussing the AWS CloudFormation Custom resources, the &lt;a href="https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.triggers-readme.html" rel="noopener noreferrer"&gt;&lt;em&gt;cdk-triggers&lt;/em&gt;&lt;/a&gt; deserves an honorable mention. The package provides an abstraction layer over the &lt;em&gt;custom_resources&lt;/em&gt; module with a couple of niceties on top.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I have not seen much development work going into this package, so I would be wary of using it in production&lt;/strong&gt;. Still, nevertheless, I think it is a vital alternative to any other way of couping tests with the AWS CloudFormation lifecycle we have seen so far.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bottom line
&lt;/h2&gt;

&lt;p&gt;In the end, what matters is that you test your application. Running tests after the deployment, albeit not very optimal due to difficulties with rolling back, is still much better than not doing so. Consider coupling some of your tests with the AWS CloudFormation deployment lifecycle as your product matures. Automatic rollback upon a test failure is a great way to prevent pushing broken builds to production environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing words
&lt;/h2&gt;

&lt;p&gt;In this article, we have discussed three and a half ways to couple your tests with the AWS CloudFormation lifecycle. The list is far from exhaustive. I'm confident that there are other ways to do so. If you have an experience in this space and want to share some details, please feel free to reach out to me.&lt;/p&gt;

&lt;p&gt;For more AWS / serverless content, consider following me on Twitter - &lt;a href="https://twitter.com/wm_matuszewski" rel="noopener noreferrer"&gt;@wm_matuszewski&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thank you for your precious time.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>cdk</category>
    </item>
    <item>
      <title>AWS CDK and Amazon DynamoDB global tables</title>
      <dc:creator>Wojciech Matuszewski</dc:creator>
      <pubDate>Fri, 02 Sep 2022 03:55:20 +0000</pubDate>
      <link>https://forem.com/aws-builders/aws-cdk-and-amazon-dynamodb-global-tables-3p0b</link>
      <guid>https://forem.com/aws-builders/aws-cdk-and-amazon-dynamodb-global-tables-3p0b</guid>
      <description>&lt;p&gt;Amazon DynamoDB is THE database for serverless AWS applications. Its HTTP-based connection model makes integrating with serverless computing services like AWS Lambda easy. One of the additional capabilities of Amazon DynamoDB is the &lt;a href="https://aws.amazon.com/dynamodb/global-tables/" rel="noopener noreferrer"&gt;&lt;em&gt;global tables&lt;/em&gt;&lt;/a&gt;, which allows you to run replica instances of the database in other regions.&lt;/p&gt;

&lt;p&gt;In this blog post, I will touch on Amazon DynamoDB global tables in the context of the &lt;a href="https://aws.amazon.com/cdk/" rel="noopener noreferrer"&gt;AWS CDK&lt;/a&gt; framework – there are a couple of nuances with huge implications you should be aware of.&lt;/p&gt;

&lt;p&gt;Let us dive in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Traditional way of creating global tables
&lt;/h2&gt;

&lt;p&gt;There are two ways I'm aware of to create global tables using AWS CDK, with one of them being, in my humble opinion, much better for the maintainability of your application.&lt;/p&gt;

&lt;p&gt;Let us start with the "traditional" way of creating the Amazon DynamoDB global tables via the AWS CDK – by using the &lt;a href="https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_dynamodb.Table.html" rel="noopener noreferrer"&gt;&lt;code&gt;aws_dynamodb.Table&lt;/code&gt; construct&lt;/a&gt; and specifying the &lt;code&gt;replicationRegions&lt;/code&gt; property.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;globalTable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Table&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;partitionKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AttributeType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STRING&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;replicationRegions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;us-east-1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;us-east-2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;us-west-2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;billingMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BillingMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PROVISIONED&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here I've defined a table with three replication regions. If you deploy this code snippet in the AWS DynamoDB web console, you will see that the table has three replicas – one for each region.&lt;/p&gt;

&lt;p&gt;On the surface, all looks good and works as expected. But as soon as you peek "under the curtains" of the construct, you will see something unexpected – that deploying the "Table" created an &lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html" rel="noopener noreferrer"&gt;AWS CloudFormation custom resource&lt;/a&gt; alongside the DynamoDB resource.&lt;/p&gt;

&lt;h3&gt;
  
  
  The problem with the custom resource
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;You can find the code the custom resource in question uses &lt;a href="https://github.com/aws/aws-cdk/blob/c607ca51be1042f091b4e4419f20bec75863055c/packages/@aws-cdk/aws-dynamodb/lib/replica-handler/index.ts" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The custom resource is responsible for creating the replica tables. It uses the AWS SDK &lt;code&gt;updateTable&lt;/code&gt; API and provides the appropriate value for the &lt;code&gt;ReplicaUpdates&lt;/code&gt; property. On the surface, it does not sound bad, but &lt;strong&gt;by creating the replicas via the AWS SDK call, we effectively detach those resources from your AWS CloudFormation template and AWS CDK code&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8a3r9roj77cgia53j6zz.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8a3r9roj77cgia53j6zz.jpg" alt="Simplified diagram"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The implications of this architecture are enormous from the maintainability perspective. Here are the two cases of confusion and toil I had to deal with multiple times when operating global tables deployed via the &lt;code&gt;replicationRegions&lt;/code&gt; property.&lt;/p&gt;

&lt;h4&gt;
  
  
  The &lt;code&gt;DeletionPolicy&lt;/code&gt; attribute
&lt;/h4&gt;

&lt;p&gt;If you are running a production workload, it is considered good practice (I would argue a must-have) to specify &lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-deletionpolicy.html" rel="noopener noreferrer"&gt;the &lt;code&gt;DeletionPolicy&lt;/code&gt;&lt;/a&gt; (in AWS CDK, the name is &lt;code&gt;removalPolicy&lt;/code&gt;) of &lt;code&gt;RETAIN&lt;/code&gt; for resources that are stateful and hold production data.&lt;/p&gt;

&lt;p&gt;If you delete the CloudFormation stack, the CloudFormation will &lt;strong&gt;not&lt;/strong&gt; delete the resources when we set the &lt;code&gt;DeletionPolicy&lt;/code&gt; to &lt;code&gt;RETAIN&lt;/code&gt;. As you can imagine, this can save you from a disaster.&lt;/p&gt;

&lt;p&gt;Usually, in AWS CDK, this is done by using the &lt;code&gt;applyRemovalPolicy&lt;/code&gt; method on a given construct.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;globalTable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Table&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;globalTable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;applyRemovalPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RemovalPolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RETAIN&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The problem is that &lt;strong&gt;replicas created via the custom resource will not inherit that &lt;code&gt;removalPolicy&lt;/code&gt;. This setting will only apply to the "root" table&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So, If I were to delete a CloudFormation stack, the CloudFormation will preserve the "root" table but delete the custom resource. &lt;strong&gt;Since the custom resource triggered with a &lt;code&gt;Delete&lt;/code&gt; event, it would issue the &lt;code&gt;Delete&lt;/code&gt; action for all the replica tables&lt;/strong&gt;. And with that, all of your replica tables are gone.&lt;/p&gt;

&lt;p&gt;To make sure the &lt;code&gt;Delete&lt;/code&gt; event never gets to the custom resource the AWS CDK uses, you have to set the &lt;code&gt;DeletionPolicy&lt;/code&gt; on the custom resource itself. That is not so easy as you have to "find" the custom resource node in the AWS CDK tree and override its &lt;code&gt;DeletionPolicy&lt;/code&gt; attribute. Here is how I would do it.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;globalTable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * This only applies to the "root table" and NOT THE REPLICAS!
 */&lt;/span&gt;
&lt;span class="nx"&gt;globalTable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;applyRemovalPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;removalPolicy&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Make sure we do not remove the custom resource
 */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;customReplicaResource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;globalTable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;child&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;child&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;cfnResourceType&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Custom::DynamoDBReplica&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CfnResource&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;customReplicaResource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;applyRemovalPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;removalPolicy&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h4&gt;
  
  
  Enabling point-in-time recovery on the replica tables
&lt;/h4&gt;

&lt;p&gt;For production usage, enabling &lt;em&gt;point-in-time recovery&lt;/em&gt; (PITR) is arguably a must. It is not only helpful in data-corruption scenarios but also for disaster recovery. In CDK, applying the PITR on a global table (created via the &lt;code&gt;replicationRegions&lt;/code&gt; property) might initially seem easy.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;globalTable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;pointInTimeRecovery&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The problem is, similarly to the &lt;code&gt;DeletionPolicy&lt;/code&gt; we have looked at earlier, that &lt;strong&gt;the &lt;code&gt;pointInTimeRecovery&lt;/code&gt; property only applies to the "root" table. It will NOT be "forwarded" to the replica tables&lt;/strong&gt;. An auditing tool will catch that for you in the best-case scenario. In the worst, you will not be able to recover your replica tables if something goes wrong.&lt;/p&gt;

&lt;p&gt;All hope is not lost, though. You could also use a workaround to ensure that the replica tables have the same PITR setting as your "root" table. This involves calling &lt;code&gt;updateContinuousBackups&lt;/code&gt; yourself via the &lt;code&gt;AwsCustomResource&lt;/code&gt; construct for each region where the replica resides.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;replicationRegions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eu-center-1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eu-west-1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;globalTable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;pointInTimeRecovery&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;replicationRegions&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;replicationRegion&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;replicationRegions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;custom_resources&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AwsCustomResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;onUpdate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DynamoDB&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;updateContinuousBackups&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;TableName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;globalTable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;PointInTimeRecoverySpecification&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;PointInTimeRecoveryEnabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;replicationRegion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;physicalResourceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;custom_resources&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PhysicalResourceId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;custom_resources&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AwsCustomResourcePolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromSdkCalls&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;globalTable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tableArn&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  A different way of creating global tables
&lt;/h2&gt;

&lt;p&gt;Instead of using the &lt;code&gt;aws_dynamodb.Table&lt;/code&gt; construct, &lt;strong&gt;consider using the &lt;code&gt;aws_dynamodb.CfnGlobalTable&lt;/code&gt;&lt;/strong&gt;. To the best of my knowledge, this resource was made available to us in May 2021, and I would highly encourage you for it to be your default, &lt;strong&gt;even if you do not anticipate using replica tables in the immediate future in your architecture&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you want to dive deeper into technical details about the resource to which &lt;code&gt;CfnGlobalTable&lt;/code&gt; corresponds, look no further than &lt;a href="https://acloudguru.com/blog/engineering/you-should-always-use-dynamodb-global-tables-now" rel="noopener noreferrer"&gt;this excellent blog post&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I advocate for the &lt;code&gt;CfnGlobalTable&lt;/code&gt; usage because the gotchas mentioned in the previous section do not apply to this construct. For example, to specify the &lt;code&gt;DeletionPolicy&lt;/code&gt;, which would apply to &lt;strong&gt;all tables, even the replicas&lt;/strong&gt;, all you need to do is to use the &lt;code&gt;applyRemovalPolicy&lt;/code&gt; method available on the construct.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;globalTable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CfnGlobalTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;globalTable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;applyRemovalPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RemovalPolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RETAIN&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;What about the PITR? To enable it on all the tables (replicas and the "root" table) do not need to fiddle with AWS SDK calls through a custom resource (or some other way). You can set the PITR for the "root" table and the replica tables.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;globalTable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CfnGlobalTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eu-center-1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;pointInTimeRecoverySpecification&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;pointInTimeRecoveryEnabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="c1"&gt;// Let us say that, the root table lives in "eu-west-1"&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eu-west-1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;pointInTimeRecoverySpecification&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;pointInTimeRecoveryEnabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Another significant reason for using the &lt;code&gt;CfnGlobalTable&lt;/code&gt; is that &lt;strong&gt;this resource uses a newer version of Amazon DynamoDB global tables&lt;/strong&gt;. You can read about different versions of global tables &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/globaltables.DetermineVersion.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Migration
&lt;/h3&gt;

&lt;p&gt;I'm not yet ready to publish a migration story here (I'm still evaluating different avenues to do so), so please keep an eye on my next article. If you want to perform migration now, I would base the steps necessary on &lt;a href="https://awstip.com/converting-a-dynamodb-single-region-table-to-a-global-table-with-zero-downtime-151c018cbef0" rel="noopener noreferrer"&gt;this&lt;/a&gt; and &lt;a href="https://matthewbonig.com/2021/08/30/importing-with-the-cdk/" rel="noopener noreferrer"&gt;this&lt;/a&gt; article.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing words
&lt;/h2&gt;

&lt;p&gt;I hope that the information in this article will save you some toil and frustrations I had to go through while working with global tables created via the &lt;code&gt;replicationRegions&lt;/code&gt; property in CDK.&lt;/p&gt;

&lt;p&gt;As I mentioned, the &lt;code&gt;CfnGlobalTable&lt;/code&gt; is objectively better suited for the job. Yes, you might lose some of the niceties of the L2 (L3?) CDK construct, but using the newer version of the Amazon DynamoDB global tables will pay dividends in the long run.&lt;/p&gt;

&lt;p&gt;For more similar content, please consider following me on Twitter – &lt;a href="https://twitter.com/wm_matuszewski" rel="noopener noreferrer"&gt;@wm_matuszewski&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thank you for your time.&lt;/p&gt;

</description>
      <category>cdk</category>
      <category>dynamodb</category>
      <category>serverless</category>
      <category>aws</category>
    </item>
    <item>
      <title>Strategies to test AWS AppSync VTL templates</title>
      <dc:creator>Wojciech Matuszewski</dc:creator>
      <pubDate>Sat, 06 Aug 2022 07:40:55 +0000</pubDate>
      <link>https://forem.com/aws-builders/strategies-to-test-aws-appsync-vtl-templates-1ede</link>
      <guid>https://forem.com/aws-builders/strategies-to-test-aws-appsync-vtl-templates-1ede</guid>
      <description>&lt;p&gt;&lt;a href="https://aws.amazon.com/appsync/"&gt;AWS AppSync&lt;/a&gt; is a serverless GraphQL and Pub/Sub API service that many developers love. Turing the complex job of creating and managing a GraphQL API with real-time capabilities into a few lines of IaC (or clicks in the console) is a great win for AWS serverless developers.&lt;/p&gt;

&lt;p&gt;The AWS AppSync enables you to integrate with other AWS services via &lt;a href="https://aws.amazon.com/lambda/"&gt;AWS Lambda functions&lt;/a&gt; or &lt;a href="https://docs.aws.amazon.com/appsync/latest/devguide/resolver-mapping-template-reference-programming-guide.html"&gt;VTL &lt;em&gt;mapping templates&lt;/em&gt;&lt;/a&gt;. Historically it was pretty hard to test the logic written in the VTL templates. As the service matured, AWS added more and more ways to do so, enabling us, developers, to ensure the service we are developing is working correctly.&lt;/p&gt;

&lt;p&gt;In this blog post, I will showcase all the strategies for testing AWS AppSync VTL templates I've seen in the wild. This, by no means, is a comprehensive list – these are the ways &lt;strong&gt;I'm familiar with&lt;/strong&gt;. If you know a different way, please feel free to reach out!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can find the code for all the strategies listed in the article in &lt;a href="https://github.com/WojciechMatuszewski/testing-appsync-vtl-templates-strategies"&gt;this GitHub repository&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Testing VTL templates using the AWS SDK
&lt;/h2&gt;

&lt;p&gt;This is a technique I've discovered recently, and it quickly became my favorite. The AppSync SDK exposes the &lt;a href="https://awscli.amazonaws.com/v2/documentation/api/latest/reference/appsync/evaluate-mapping-template.html"&gt;&lt;code&gt;evaluate-mapping-template&lt;/code&gt; CLI call&lt;/a&gt; for parsing AppSync VTL templates using the AppSync SDK.&lt;/p&gt;

&lt;p&gt;When I read about this &lt;a href="https://aws.amazon.com/blogs/mobile/introducing-template-evaluation-and-unit-testing-for-aws-appsync-resolvers/"&gt;in this AWS blog post&lt;/a&gt; I was thrilled as I was not very pleased with how I've been testing the templates so far (another strategy that I will touch on in this article).&lt;/p&gt;

&lt;p&gt;The test boils down to evaluating the VTL template via the AWS SDK, then asserting on the result. &lt;strong&gt;You have only one dependency to maintain – the AWS SDK for AppSync&lt;/strong&gt;, which is a huge win. The following is a snippet that evaluates a VTL template via the AppSync SDK responsible for fetching a user from &lt;a href="https://aws.amazon.com/dynamodb/"&gt;AWS DynamoDB&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can find the full &lt;a href="https://github.com/WojciechMatuszewski/testing-appsync-vtl-templates-strategies/tree/main/via-aws-sdk"&gt;source code for this testing strategy here&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;AppSyncClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;EvaluateMappingTemplateCommand&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-sdk/client-appsync&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;AppSyncClient&lt;/span&gt;&lt;span class="p"&gt;({});&lt;/span&gt;

&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;template test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`{
    "version": "2018-05-29",
    "operation": "GetItem",
    "key": {
        "id": $util.dynamodb.toDynamoDBJson($context.arguments.id),
    }
}`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;USER_ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;templateEvaluationResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;EvaluateMappingTemplateCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;context&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;templateEvaluationResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;evaluationResult&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toMatchInlineSnapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`
    "{
        \\"version\\": \\"2018-05-29\\",
        \\"operation\\": \\"GetItem\\",
        \\"key\\": {
            \\"id\\": {\\"S\\":\\"USER_ID\\"},
        }
    }"
  `&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since we are using the official AWS SDK, we can be pretty confident that if we were to deploy an API and use this template, this is how the AWS AppSync service would render it internally.&lt;/p&gt;

&lt;p&gt;One drawback of this way of testing VTL templates is that the environment in which the test is running has to have access to AWS credentials. That never was a problem for me, but it all depends on your situation, so be mindful of that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing VTL templates using VTL parsers
&lt;/h2&gt;

&lt;p&gt;This is the technique I've used before I learned about the AppSync SDK's ability to render templates. Instead of relying on the SDK, we use libraries the &lt;a href="https://aws.amazon.com/amplify/"&gt;AWS Amplify&lt;/a&gt; uses internally to render the template.&lt;/p&gt;

&lt;p&gt;The test setup is a bit more involved, but the result is pretty much the same as in the previous example. The following is the snippet rendering the VTL template using Amplify VTL parsers.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can find the full &lt;a href="https://github.com/WojciechMatuszewski/testing-appsync-vtl-templates-strategies/tree/main/via-vtl-parsers"&gt;source code for this testing strategy here&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;velocityUtil&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;amplify-appsync-simulator/lib/velocity/util&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;velocityTemplate&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;amplify-velocity-template&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;velocityMapper&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;amplify-appsync-simulator/lib/velocity/value-mapper/mapper&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;template test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`{
    "version": "2018-05-29",
    "operation": "GetItem",
    "key": {
        "id": $util.dynamodb.toDynamoDBJson($context.arguments.id),
    }
}`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;graphQLResolveInfo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appSyncGraphQLExecutionContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;vtlUtil&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;velocityUtil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;graphQLResolveInfo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;appSyncGraphQLExecutionContext&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;vtlAST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;velocityTemplate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;vtlCompiler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;velocityTemplate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vtlAST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;valueMapper&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;velocityMapper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;escape&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;USER_ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;USER_ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;renderedTemplate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vtlCompiler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;util&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;vtlUtil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;utils&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;vtlUtil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;context&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;renderedTemplate&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toMatchInlineSnapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`
    "{
        \\"version\\": \\"2018-05-29\\",
        \\"operation\\": \\"GetItem\\",
        \\"key\\": {
            \\"id\\": {\\"S\\":\\"USER_ID\\"},
        }
    }"
  `&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As I mentioned earlier, I'm not a massive fan of this technique. My main gripe is that I have to pull additional dependencies, which I have no confidence are well maintained, to my project.&lt;/p&gt;

&lt;p&gt;Another issue I have is that the dependencies might be out of date and yield a template that might be different than the one produced internally inside the AWS AppSync service.&lt;/p&gt;

&lt;p&gt;As for positives – since we are not making any calls to AWS, this technique does not require you to have AWS credentials in place. It might be a huge plus for some, but not so much for others.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing VTL templates using AWS AppSync simulator
&lt;/h2&gt;

&lt;p&gt;Next up, going up in the complexity spectrum, this technique sits in a weird spot between local emulation and exercising deployed AWS infrastructure.&lt;/p&gt;

&lt;p&gt;Instead of rendering the template and asserting its shape, we employ the &lt;code&gt;amplify-appsync-simulator&lt;/code&gt; package to process the template and make the request to an actual (or local instance of) AWS service. This strategy stands on the shoulders of the &lt;a href="https://docs.amplify.aws/cli/usage/mock/"&gt;AWS Amplify mocking and testing&lt;/a&gt; capabilities.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can find the full &lt;a href="https://github.com/WojciechMatuszewski/testing-appsync-vtl-templates-strategies/tree/main/via-appsync-simulation"&gt;source code for this testing strategy here&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AppSyncUnitResolver&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;amplify-appsync-simulator/lib/resolvers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;AmplifyAppSyncSimulator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;AmplifyAppSyncSimulatorAuthenticationType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;RESOLVER_KIND&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;amplify-appsync-simulator&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;template test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;simulator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;AmplifyAppSyncSimulator&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;simulator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;dataSources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AMAZON_DYNAMODB&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dynamodb&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tableEndpoint&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;appSync&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;testAppSyncAPI&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;additionalAuthenticationProviders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
      &lt;span class="na"&gt;defaultAuthenticationType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;authenticationType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AmplifyAppSyncSimulatorAuthenticationType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;API_KEY&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
        schema {
          query: Query
        }

        type Query {
          getUser(id: ID!): User!
        }

        type User {
          id: ID!
          name: String!
        }
      `&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requestTemplate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`{
    "version": "2018-05-29",
    "operation": "GetItem",
    "key": {
        "id": $util.dynamodb.toDynamoDBJson($context.arguments.id)
    }
}`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;responseTemplate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`$util.toJson($context.result)`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resolver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;AppSyncUnitResolver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;requestMappingTemplate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;requestTemplate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;responseMappingTemplate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;responseTemplate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RESOLVER_KIND&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;UNIT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;fieldName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mockFieldName&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;typeName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mockTypeName&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;dataSourceName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dynamodb&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;simulator&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;resolver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SOURCE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;USER_ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;appsyncErrors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;fieldNodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toMatchInlineSnapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`
    {
      "id": "USER_ID",
      "name": "CREATED_AT_INFRASTRUCTURE_DEPLOY_TIME",
    }
  `&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To be perfectly honest, I'm not a massive fan of this testing technique. My biggest problem is the amount of effort required to make the test work. In addition to pulling additional dependencies into my project, I have to either set up a local mock of a given AWS service or deploy a given service before the test run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing VTL templates by making GraphQL requests
&lt;/h2&gt;

&lt;p&gt;And the last testing strategy I've used are the end-to-end tests. Instead of rendering the VTL template or using simulation, one could fire a GraphQL request to the API endpoint and assert the result. These tests usually run for much longer than the ones I've previously talked about but &lt;strong&gt;the confidence you gain that your system is working by utilizing end-to-end tests is massive&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The test body is tiny. We shift the complexity away from the test setup to the infrastructure configuration. To use this testing technique effectively, you must already have all the cloud resources related to the AWS AppSync deployed.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can find the full &lt;a href="https://github.com/WojciechMatuszewski/testing-appsync-vtl-templates-strategies/tree/main/via-graphql-calls"&gt;source code for this testing strategy here&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;gql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;graphql-request&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;template test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;gql&lt;/span&gt;&lt;span class="s2"&gt;`
    query {
      getUser(id: "USER_ID") {
        id
        name
      }
    }
  `&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;endpointUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-api-key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;endpointAPIkey&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toMatchInlineSnapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`
    {
      "getUser": {
        "id": "USER_ID",
        "name": "CREATED_AT_INFRASTRUCTURE_DEPLOY_TIME",
      },
    }
  `&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This testing strategy is my go-to when I want to exercise the whole system I'm working on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing words
&lt;/h2&gt;

&lt;p&gt;I've used these four AWS AppSync VTL testing strategies with varying degrees of success. Hopefully, after reading this article, you found a technique that fits your needs.&lt;/p&gt;

&lt;p&gt;I'm always open to feedback and learning new things. If you are aware of a different way of, directly or indirectly, testing VTL templates – please let me know!&lt;/p&gt;

&lt;p&gt;Consider following me on &lt;a href="https://twitter.com/wm_matuszewski"&gt;Twitter – @wm.matuszewski&lt;/a&gt; if you wish to see more serverless content on your timeline.&lt;/p&gt;

&lt;p&gt;Thank you for your precious time.&lt;/p&gt;

</description>
      <category>appsync</category>
      <category>aws</category>
      <category>serverless</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Two ways to directly integrate AWS Lambda function with Amazon API Gateway</title>
      <dc:creator>Wojciech Matuszewski</dc:creator>
      <pubDate>Sun, 17 Jul 2022 04:04:46 +0000</pubDate>
      <link>https://forem.com/aws-builders/two-ways-to-directly-integrate-aws-lambda-function-with-amazon-api-gateway-3can</link>
      <guid>https://forem.com/aws-builders/two-ways-to-directly-integrate-aws-lambda-function-with-amazon-api-gateway-3can</guid>
      <description>&lt;p&gt;Since the inception of the concept of "serverless", the combination of &lt;a href="https://aws.amazon.com/api-gateway/" rel="noopener noreferrer"&gt;Amazon API Gateway&lt;/a&gt; and &lt;a href="https://aws.amazon.com/lambda/" rel="noopener noreferrer"&gt;AWS Lambda&lt;/a&gt; function has become one of the most popular serverless patterns on AWS.&lt;/p&gt;

&lt;p&gt;With the advent of deployment frameworks like &lt;a href="https://aws.amazon.com/cdk/" rel="noopener noreferrer"&gt;AWS CDK&lt;/a&gt; or &lt;a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html" rel="noopener noreferrer"&gt;AWS SAM&lt;/a&gt;, it has never been easier, using IaC, to deploy this pattern for the world to see.&lt;/p&gt;

&lt;p&gt;As with many things in programming and computer science, there are often multiple ways to do something. The integration of API Gateway with the Amazon Lambda function is no different – one could use the &lt;em&gt;proxy&lt;/em&gt; and &lt;em&gt;non-proxy&lt;/em&gt; integration type.&lt;/p&gt;

&lt;p&gt;This blog post will discuss the difference between the &lt;em&gt;proxy&lt;/em&gt; and &lt;em&gt;non-proxy&lt;/em&gt; integration and in which situations you might want to use a given integration method.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The &lt;em&gt;proxy&lt;/em&gt; and &lt;em&gt;non-proxy&lt;/em&gt; are used here &lt;strong&gt;only in the context of the integration type&lt;/strong&gt; and have nothing to do with &lt;a href="https://www.alexdebrie.com/posts/api-gateway-elements/#vocabulary-time-service-proxies-vs-proxy-integrations-vs-proxy-resources" rel="noopener noreferrer"&gt;service proxies or proxy resources&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The &lt;em&gt;proxy&lt;/em&gt; integration
&lt;/h2&gt;

&lt;p&gt;I would argue that the &lt;em&gt;proxy&lt;/em&gt; integration type is the most commonly deployed one. Partly because, to my knowledge, it is the default one that most of the deployment frameworks utilize, and partly because it is relatively light on the configuration side of things.&lt;/p&gt;

&lt;p&gt;The following is a simplified diagram of the API Gateway integration with the AWS Lambda function via the &lt;em&gt;proxy&lt;/em&gt; type.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note that all the diagrams in this article omit concepts related to authorization and authentication as they are not relevant to the ideas presented in the text.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Form0kp513lp2suqk70up.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Form0kp513lp2suqk70up.jpeg" alt="_proxy_ integration type"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;With the &lt;em&gt;proxy&lt;/em&gt; integration, you lose the ability to perform data manipulation directly via the API Gateway mapping templates&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Since you cannot use the mapping templates and the VTL, your function code might be a bit more complex than the &lt;em&gt;non-proxy&lt;/em&gt; counterpart.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Very light on the configuration side of things. The IaC declaration is relatively easy to maintain.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;You &lt;strong&gt;still able to take advantage of the API Gateway validation models&lt;/strong&gt;. A feature that, in my humble opinion, is underutilized.&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Before using the API Gateway validation models, consider reading &lt;a href="https://rboyd.dev/089999bf-b973-42ed-9796-6167539269b8" rel="noopener noreferrer"&gt;this article&lt;/a&gt; as it surfaces some of the issues you might encounter.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For completeness' sake, here is the minimal configuration required to integrate the API Gateway with an AWS Lambda function using AWS CDK and &lt;a href="https://www.typescriptlang.org/" rel="noopener noreferrer"&gt;TypeScript&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws_apigateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RestApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;API&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws_lambda_nodejs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NodejsFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Handler&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./handler.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addMethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws_apigateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LambdaIntegration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pretty minimal, right? Now, onto the &lt;em&gt;non-proxy&lt;/em&gt; integration type.&lt;/p&gt;

&lt;h2&gt;
  
  
  The &lt;em&gt;non-proxy&lt;/em&gt; integration type
&lt;/h2&gt;

&lt;p&gt;It took me a while to familiarize myself with some of the options the API Gateway REST API exposes. Looking back, I've allocated a sizeable portion of that time to understand how one could utilize the mapping templates and the &lt;em&gt;non-proxy&lt;/em&gt; integration in the real world. There are a LOT of knobs one could tweak in this type of integration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmv5lc50xs7xercez05um.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmv5lc50xs7xercez05um.jpeg" alt="_non-proxy_ integration"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;When using the &lt;em&gt;non-proxy&lt;/em&gt; integration type, &lt;strong&gt;you have to specify the response mapping template&lt;/strong&gt;. If you do not, the API Gateway will fail with &lt;em&gt;"Internal Server Error"&lt;/em&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When writing API Gateway &lt;strong&gt;mapping templates&lt;/strong&gt; you &lt;strong&gt;do NOT have the ability to use features that you might have used in &lt;a href="https://aws.amazon.com/appsync/" rel="noopener noreferrer"&gt;AWS AppSync&lt;/a&gt; resolver templates&lt;/strong&gt;. You should consider the VTL in API Gateway and VTL in AppSync resolvers as two separate entities.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is the most minimal AWS CDK code snippet to declare an API Gateway route backed by &lt;em&gt;non-proxy&lt;/em&gt; integration I could come up with.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws_apigateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RestApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;API2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws_lambda_nodejs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NodejsFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Handler&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./handler.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nonProxyIntegrationResource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;non-proxy&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;nonProxyIntegrationResource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addMethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws_apigateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LambdaIntegration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="cm"&gt;/** Optional */&lt;/span&gt;
    &lt;span class="na"&gt;requestTemplates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="cm"&gt;/**
       * This is what the Lambda function is invoked with.
       */&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;{"foo": "bar"}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="cm"&gt;/**
     * Required
     */&lt;/span&gt;
    &lt;span class="na"&gt;integrationResponses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;200&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;responseTemplates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$input.json('$.body')&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cm"&gt;/**
     * Required
     */&lt;/span&gt;
    &lt;span class="na"&gt;methodResponses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;200&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  My recommendation
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;When integrating API Gateway with AWS Lambda function, &lt;strong&gt;consider using the &lt;em&gt;proxy&lt;/em&gt; integration as the default integration type&lt;/strong&gt;, especially if you are new to the service or are working with a team yet to familiarize themselves with API Gateway and AWS Lambda.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Consider using the &lt;strong&gt;&lt;em&gt;non-proxy&lt;/em&gt; integration for massaging the input data and precomputing given properties&lt;/strong&gt;. Consult with your team on this one. It all depends on the use case.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Take some time to learn the &lt;em&gt;non-proxy&lt;/em&gt; integration&lt;/strong&gt; as the notion of mapping templates and other closely related settings are what drives the API Gateway direct service integrations.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Closing worlds
&lt;/h2&gt;

&lt;p&gt;I find it very valuable to dive deep into a given AWS service, especially when I'm unfamiliar with a particular way of configuring it – I've mainly used the &lt;em&gt;proxy&lt;/em&gt; integration throughout, albeit not that long, my career. I encourage you to do a similar and write about your findings. This way, the whole community can benefit and learn together!&lt;/p&gt;

&lt;p&gt;For more serverless content, consider following me on Twitter - &lt;a href="https://twitter.com/wm_matuszewski" rel="noopener noreferrer"&gt;@wm_matuszewski&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And as always, thank you for your precious time.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>apigw</category>
      <category>lambda</category>
    </item>
    <item>
      <title>Better error messages for non-existing API resources with Amazon API Gateway</title>
      <dc:creator>Wojciech Matuszewski</dc:creator>
      <pubDate>Sun, 26 Jun 2022 13:19:53 +0000</pubDate>
      <link>https://forem.com/aws-builders/better-error-messages-for-non-existing-api-resources-with-amazon-api-gateway-50pb</link>
      <guid>https://forem.com/aws-builders/better-error-messages-for-non-existing-api-resources-with-amazon-api-gateway-50pb</guid>
      <description>&lt;p&gt;One of the most popular serverless services in the AWS offering is the &lt;a href="https://aws.amazon.com/api-gateway/"&gt;Amazon API Gateway&lt;/a&gt;. With three flavors of APIs to choose from – REST, HTTP, and WebSocket, the service is an essential piece of the AWS serverless ecosystem.&lt;/p&gt;

&lt;p&gt;This blog post will look at the REST and HTTP versions of the Amazon API Gateway APIs and how to ensure the &lt;em&gt;path or resource not found&lt;/em&gt; responses contain messages which are helpful to the end-user.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can find the code from this article in &lt;a href="https://github.com/WojciechMatuszewski/apigw-non-existing-resource-error-msg"&gt;this GitHub repository&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;We all make mistakes – after all, we are only human. When interacting with APIs, one of the mistakes one can make is to try to request a resource that does not exist (either the path or the method is incorrect).&lt;/p&gt;

&lt;p&gt;In an ideal world, the response generated from the API would help the user quickly resolve their issue – in our case, amend the request to use the correct resource/method combination.&lt;/p&gt;

&lt;p&gt;In the case of the Amazon API Gateway, the default behavior for handling such situations is not ideal, &lt;strong&gt;especially when it comes to the REST APIs&lt;/strong&gt;. Let us look at that flavor of the Amazon API Gateway next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unknown resource response in the context of Amazon API Gateway REST API
&lt;/h2&gt;

&lt;p&gt;By default, if the request resource/method combination is incorrect, the API Gateway REST API will respond with the following payload with a &lt;strong&gt;&lt;code&gt;statusCode&lt;/code&gt; of 403&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Missing Authentication Token"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ouch, there is room for improvement here. In fact, &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-gatewayResponse-definition.html#customize-gateway-responses"&gt;even the official documentation&lt;/a&gt; states that this behavior is confusing.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If the official documentation states that the default response is confusing, that begs the question, why have such a response be the default in the first place?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To my best knowledge, we can significantly improve the API ergonomics in this area by either &lt;strong&gt;modifying Amazon API Gateway &lt;em&gt;gateway responses&lt;/em&gt;&lt;/strong&gt; or by &lt;strong&gt;having a "greedy" route with &lt;code&gt;ANY&lt;/code&gt; method that acts as a fallback&lt;/strong&gt; for such requests. Since I'm biased toward solutions that involve configuration, let us first start with &lt;em&gt;gateway responses&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Modifying gateway responses
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-gatewayResponse-definition.html"&gt;gateway responses&lt;/a&gt; define what happens if the API Gateway fails to process an incoming request.&lt;/p&gt;

&lt;p&gt;In our particular scenario, we are interested in amending the &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/supported-gateway-response-types.html"&gt;&lt;code&gt;MISSING_AUTHENTICATION_TOKEN&lt;/code&gt; response type&lt;/a&gt; – that is the response type returned to the end-user when the method/resource combination of the request to the REST API is incorrect.&lt;/p&gt;

&lt;p&gt;The following is an &lt;a href="https://aws.amazon.com/cdk/"&gt;AWS CDK&lt;/a&gt; &lt;a href="https://www.typescriptlang.org/"&gt;TypeScript&lt;/a&gt; snippet that re-defines the gateway response in question.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;restAPI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws_apigateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RestApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;RestAPI&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="cm"&gt;/**
   * Configuration...
   */&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;restAPI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addGatewayResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PathOrMethodMaybeFound&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;aws_apigateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ResponseType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MISSING_AUTHENTICATION_TOKEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;templates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`{"message": "$context.error.message", "hint": "Is the method/path combination correct?"}`&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Since the &lt;code&gt;MISSING_AUTHENTICATION_TOKEN&lt;/code&gt; response type could also be triggered when the authentication token is missing (the method/resource combination is correct), I've opted to leave the original error message intact.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I did not employ any "smartness" to the template since &lt;strong&gt;one cannot use any &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/rest-api-data-transformations.html"&gt;VTL programming constructs&lt;/a&gt; in the gateway responses templates&lt;/strong&gt; – API Gateway will ignore them. I could have tailored the message even more, using conditional statements, but in the gateway responses templates, I'm constrained to only using &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html#context-variables-template-example"&gt;variables&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now, If I were to make the request to the API and mistype the path or use a wrong method (for example, POST instead of GET), I would get the following response.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Missing Authentication Token"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Is the method/path combination correct?"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An improvement, but we can do better. Let us look at the second way I mentioned earlier – using a "greedy" path with the &lt;code&gt;ANY&lt;/code&gt; method.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploying a "greedy" route with &lt;code&gt;ANY&lt;/code&gt; method
&lt;/h3&gt;

&lt;p&gt;As an alternative to modifying the gateway responses, one can deploy a "greedy" route with the &lt;code&gt;ANY&lt;/code&gt; method. Suppose the API Gateway invokes the integration backing that route. In that case, we can be confident that none of our other routes match the request instead of lacking the authentication token (if our API requires authorization).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;br&gt;
In some cases, developers might use the &lt;code&gt;ANY&lt;/code&gt; method with a "greedy" route for monolithic AWS Lambda deployments (think running &lt;a href="https://www.npmjs.com/package/express"&gt;express&lt;/a&gt; on AWS Lambda. While I'm not a fan of such practice, I also understand that it might work well for some. If that is the case for you, you should consider tackling the issue I'm describing using your framework of choice.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Greedy route backed by AWS Lambda function
&lt;/h3&gt;

&lt;p&gt;This is, arguably, a more accessible, from the IaC configuration standpoint, variant to pull off. The "greedy" &lt;code&gt;ANY&lt;/code&gt; route is backed by an &lt;a href="https://aws.amazon.com/lambda/"&gt;AWS Lambda&lt;/a&gt; function. Since we have the power of a programing language at our disposal, one has the opportunity to tailor the "method/path does not exist on this API" message to a given API user.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;br&gt;
AWS Lambda function is not the only service the API Gateway can integrate. In this scenario, &lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/concepts-standard-vs-express.html"&gt;AWS Step Function Express workflows&lt;/a&gt; would also do the trick.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The following is an AWS CDK definition of the "greedy" &lt;code&gt;ANY&lt;/code&gt; route backed by the AWS Lambda function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;restAPI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws_apigateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RestApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;RestAPI&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="cm"&gt;/**
   * Configuration...
   */&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;anyAPIHandler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws_lambda_nodejs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NodejsFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AnyAPIHandler&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./handler.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;restAPI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;{proxy+}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addMethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ANY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws_apigateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LambdaIntegration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;anyAPIHandler&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here is the handler.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;APIGatewayProxyHandler&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aws-lambda&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;APIGatewayProxyHandler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Method/Path combination not found. Please check the documentation.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, If I were to call the API and use either an incorrect method or path (or both) instead of the generic &lt;code&gt;Missing Authentication Token&lt;/code&gt; message, the response would be the one I've encoded in the AWS Lambda.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;Method/Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;combination&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;found.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Please&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;check&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;documentation.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One important thing to remember when going this route is to ensure that &lt;strong&gt;we treat the "greedy" route AWS Lambda function just like any other AWS Lambda functions in our infrastructure&lt;/strong&gt;. This means adding throttling limits and so on (if applicable). The last thing you want is a &lt;a href="https://www.sciencedirect.com/science/article/pii/S221421262100079X"&gt;&lt;em&gt;denial of wallet attack&lt;/em&gt;&lt;/a&gt; just because you forgot to add authorization to the "greedy" endpoint.&lt;/p&gt;

&lt;h3&gt;
  
  
  Greedy route backend by Mock Integration
&lt;/h3&gt;

&lt;p&gt;The second, and the option I'm more in favor of, is to ditch the AWS Lambda function and use the Amazon API Gateway Mock Integration as the "brains" that creates the response for the "greedy" &lt;code&gt;ANY&lt;/code&gt; route.&lt;/p&gt;

&lt;p&gt;The following is an AWS CDK definition of the "greedy" &lt;code&gt;ANY&lt;/code&gt; route backed by Amazon API Gateway Mock Integration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;restAPI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws_apigateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RestApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;RestAPI&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="cm"&gt;/**
   * Configuration...
   */&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;restAPI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;{proxy+}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;addMethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ANY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws_apigateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MockIntegration&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;passthroughBehavior&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;aws_apigateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PassthroughBehavior&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEVER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;requestTemplates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`{"statusCode": 404}`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;integrationResponses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;404&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;responseTemplates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`"Method/Path combination not found. Please check the documentation."`&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;methodResponses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;404&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;main benefit&lt;/strong&gt; of using the mock integration as opposed to the AWS Lambda function is that you &lt;strong&gt;do not have to pay for AWS Lambda function invocations&lt;/strong&gt;. The &lt;strong&gt;main drawback&lt;/strong&gt; is the fact that now you have to deal with mapping templates, VTL, and relatively complex IaC configuration.&lt;/p&gt;

&lt;p&gt;Remember that mock integration templates allow you to perform conditional logic – something the gateway responses templates lacked (worth noting that you are still unable to harness the full power of the VTL, like in the case of &lt;a href="https://docs.aws.amazon.com/appsync/latest/devguide/resolver-mapping-template-reference-programming-guide.html"&gt;AWS AppSync resolver templates&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Unknown resource response in the context of Amazon API Gateway HTTP API
&lt;/h2&gt;

&lt;p&gt;By default, if the request resource/method combination is incorrect, the API Gateway HTTP API will respond with the following payload with a &lt;strong&gt;&lt;code&gt;statusCode&lt;/code&gt; of 404&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Not Found"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not bad! An improvement over the REST API counterpart. You might wish to improve the message even further – in that case, let us look at how you could do that.&lt;/p&gt;

&lt;h3&gt;
  
  
  Greedy route backed by AWS Lambda function
&lt;/h3&gt;

&lt;p&gt;Since the HTTP flavor of the API Gateway API does not expose any way to manipulate request/response templates, to my best knowledge, the only way to change to configure this behavior is to have some compute service behind a "greedy" &lt;code&gt;ANY&lt;/code&gt; route. Like Amazon API Gateway REST API, one might use the AWS Step Function Express Workflows or the AWS Lambda function to achieve the desired result.&lt;/p&gt;

&lt;p&gt;For completeness, here is the AWS CDK definition of a "greedy" &lt;code&gt;ANY&lt;/code&gt; route for API Gateway HTTP API backed by AWS Lambda function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;httpAPI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws_apigatewayv2_alpha&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HttpApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HttpAPI&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="cm"&gt;/**
   * Configuration...
   */&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;greedyPathHandler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws_lambda_nodejs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NodejsFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GreedyPathHandler&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./greedy/handler.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;httpAPI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addRoutes&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;integration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws_apigatewayv2_integrations_alpha&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HttpLambdaIntegration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GreedyPathIntegration&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;greedyPathHandler&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/{proxy+}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_apigatewayv2_alpha&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HttpMethod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ANY&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here is the handler code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;APIGatewayProxyHandlerV2&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aws-lambda&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;APIGatewayProxyHandlerV2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Method/Path combination not found. Please check the documentation.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Like in the case of the REST counterpart, there is not that much to it. I have intentionally did not add any logic to my handlers as the logic might be highly dependent on your specific case.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing words
&lt;/h2&gt;

&lt;p&gt;We can, &lt;strong&gt;and I would argue we should&lt;/strong&gt; change the default response the API Gateway returns when the user makes a request to our API with an incorrect method/path combination, &lt;strong&gt;especially for the REST flavor&lt;/strong&gt; of the API Gateway APIs. The confusion one single error message could cause cannot be underestimated.&lt;/p&gt;

&lt;p&gt;Consider following me on &lt;a href="https://twitter.com/wm_matuszewski"&gt;Twitter – @wm_matuszewski&lt;/a&gt; if you are interested in similar content like this blog post.&lt;/p&gt;

&lt;p&gt;Thank you for your precious time.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>apigw</category>
      <category>lambda</category>
      <category>cdk</category>
    </item>
    <item>
      <title>Amazon EventBridge archive and ordered replay of events</title>
      <dc:creator>Wojciech Matuszewski</dc:creator>
      <pubDate>Sat, 11 Jun 2022 11:14:49 +0000</pubDate>
      <link>https://forem.com/aws-builders/amazon-eventbridge-archive-and-ordered-replay-of-events-2lmg</link>
      <guid>https://forem.com/aws-builders/amazon-eventbridge-archive-and-ordered-replay-of-events-2lmg</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Edited on 17.06.2022&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With many serverless event-first services available, utilizing events in application backends has become very popular. Those asynchronous workflows are often more cost-efficient and resilient than their synchronous counterparts.&lt;/p&gt;

&lt;p&gt;In AWS, one service that usually works great for event-driven workflows is the &lt;a href="https://aws.amazon.com/eventbridge/" rel="noopener noreferrer"&gt;Amazon EventBridge&lt;/a&gt;. With a rich integration with other AWS services, extensive event filtering capabilities, an option to archive events, and the ability to send HTTP requests via &lt;a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-api-destinations.html" rel="noopener noreferrer"&gt;API destinations&lt;/a&gt;, it seemingly has everything you would need to place it in the core of your application.&lt;/p&gt;

&lt;p&gt;In this blog post, I would like to zoom in on the &lt;a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-archive-event.html" rel="noopener noreferrer"&gt;event archival capabilities&lt;/a&gt; of the Amazon EventBridge, mainly how to ensure that the replayed events flow through your application in order they have been sent to the event bus.&lt;/p&gt;

&lt;h2&gt;
  
  
  About the event archive
&lt;/h2&gt;

&lt;p&gt;If enabled, Amazon EventBridge will save all the events that flow through the event bus into the so-called &lt;em&gt;event archive&lt;/em&gt;. You can configure the archive to have a given retention period or only to save events that match a specific shape. You can define multiple event archives per event bus.&lt;/p&gt;

&lt;p&gt;The following is a high-level diagram of an EventBridge event bus that utilizes the event archive for event archival.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fszqf04q0jpnqdewdoeua.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fszqf04q0jpnqdewdoeua.jpeg" alt="High-level diagram of an EventBridge bus"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One of the more exciting capabilities of the event archive is &lt;strong&gt;the ability to replay events from the archive&lt;/strong&gt;. As you can imagine, if something goes wrong in your system or you have to backfill historical data, this capability comes in handy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The archive event replay
&lt;/h2&gt;

&lt;p&gt;In the context of event replay, there are a few essential things to consider – the period from which the events should be replayed and &lt;strong&gt;the fact that EventBridge does not guarantee the order of events when replaying them to a given target&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4nv0jlwgvjbnz32r7jze.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4nv0jlwgvjbnz32r7jze.jpeg" alt="Event order is not guaranteed when using archive replay"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In a perfect world, the systems we create should withstand the out-of-order delivery of events and proceed with the workflows as if the order of events is a non-factor, but sometimes it is impossible to do so. Replaying events in the order, they arrived on the event bus could be helpful while developing new features or trying to debug a particular issue.&lt;/p&gt;

&lt;p&gt;With the help of &lt;a href="https://aws.amazon.com/step-functions/?step-functions.sort-by=item.additionalFields.postDateTime&amp;amp;step-functions.sort-order=desc" rel="noopener noreferrer"&gt;AWS Step Functions&lt;/a&gt;, it is possible to force the "correct" order of the events back to our system when the event replay is active. Let us take a look at how next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ordered replay with the help of AWS Step Functions
&lt;/h2&gt;

&lt;p&gt;The following is an example structure of an EventBridge event replayed by the archive.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"6a7e8feb-b491-4cf7-a9f1-bf3703467718"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"detail-type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EC2 Instance State-change Notification"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"aws.ec2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"account"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"111122223333"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2017-12-22T18:43:48Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"us-west-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"resources"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:ec2:us-west-1:123456789012:instance/i-1234567890abcdef0"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"replay-name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"myEventReplay"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"detail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"instance-id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"i-1234567890abcdef0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"terminated"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To distinguish between a &lt;em&gt;replayed event&lt;/em&gt; and the &lt;em&gt;regular event&lt;/em&gt;, the EventBridge archive adds the &lt;code&gt;replay-name&lt;/code&gt; property to the event. This property is handy for writing filtering rules that only allow the &lt;em&gt;replayed event&lt;/em&gt; to pass onto the underlying integration. In our particular situation, &lt;strong&gt;the &lt;code&gt;time&lt;/code&gt; property&lt;/strong&gt; is the one that will allow us artificially enforce order on the replayed events.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The EventBridge service automatically adds the &lt;code&gt;time&lt;/code&gt; property to the event. You do not have (but you definitely can) to supply the timestamp in the event body.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;By &lt;strong&gt;computing the difference between the current timestamp&lt;/strong&gt; and &lt;strong&gt;the timestamp of the event&lt;/strong&gt;, we can deduce the amount of time the event has to wait before being forwarded to the target. If we apply this heuristic to all replayed events, we effectively enforce order based on the event &lt;code&gt;time&lt;/code&gt; property. The work of computing the time difference and the waiting will be done by a middleman service – in our case, an AWS Step Function.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcqe66sj4n9ja9c3d00et.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcqe66sj4n9ja9c3d00et.jpeg" alt="Ordering events based on the event time property"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Instead of forwarding the events to the original destination, we deploy a middle-man, the AWS Step Function, to enforce the order and deliver the events to the original destination. Inside the Step Function workflow, we first calculate the time difference between the &lt;strong&gt;replay start timestamp and the event &lt;code&gt;time&lt;/code&gt; property&lt;/strong&gt;, then, using the &lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-wait-state.html" rel="noopener noreferrer"&gt;&lt;code&gt;Wait&lt;/code&gt; state&lt;/a&gt;, we idle for that period and finally forward the event to its original destination.&lt;/p&gt;

&lt;p&gt;The following is a &lt;strong&gt;simplified&lt;/strong&gt; design of the AWS Step Function step machine that takes care of ordering logic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuohlqk8fqcet7dw1tryo.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuohlqk8fqcet7dw1tryo.jpeg" alt="Simplified diagram of the AWS Step Function state machine that enforces event ordering"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can find an example GitHub repository where I've implemented the ordered replay &lt;a href="https://github.com/WojciechMatuszewski/eventbridge-archive-ordered-replay" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Note that we &lt;strong&gt;cannot&lt;/strong&gt; publish the event back to the event bus. If we were to do that &lt;strong&gt;, we would lose the ordering guarantee enforced by our Step Function&lt;/strong&gt; – EventBridge does not guarantee strict ordering of events when invoking targets.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You could also forward the event to &lt;a href="https://aws.amazon.com/dynamodb/" rel="noopener noreferrer"&gt;Amazon DynamoDB&lt;/a&gt; (and then use DynamoDB Streams) or &lt;a href="https://aws.amazon.com/kinesis/data-streams/" rel="noopener noreferrer"&gt;Amazon Kinesis Data Streams&lt;/a&gt; and read the events from those services. Both of these services have ordering guarantees.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Cost considerations
&lt;/h2&gt;

&lt;p&gt;Enforcing ordering for the replayed events is not free. In fact, &lt;strong&gt;it can get quite expensive for large amounts of events&lt;/strong&gt;. Since we have to invoke an AWS Step Functions state machine for each replayed event, we incur additional charges subject to the AWS Step Functions pricing dimensions. Personally, I would only use the feature of ordered replay in development, where I control the volume of the incoming events.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing words
&lt;/h2&gt;

&lt;p&gt;I hope you found this little nugget of AWS knowledge as helpful as I did. I encourage you to explore ways to enforce event order in other parts of your system, if only for training purposes – I guarantee you will learn something new and exciting.&lt;/p&gt;

&lt;p&gt;For more AWS serverless content, consider following me on Twitter – &lt;a href="https://twitter.com/wm_matuszewski" rel="noopener noreferrer"&gt;@wm_matuszewski&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And as always, thank you for your precious time!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>stepfunctions</category>
      <category>eventbridge</category>
    </item>
    <item>
      <title>Processing large payloads with Amazon API Gateway asynchronously</title>
      <dc:creator>Wojciech Matuszewski</dc:creator>
      <pubDate>Wed, 27 Apr 2022 06:09:09 +0000</pubDate>
      <link>https://forem.com/aws-builders/processing-large-payloads-with-amazon-api-gateway-asynchronously-1m4f</link>
      <guid>https://forem.com/aws-builders/processing-large-payloads-with-amazon-api-gateway-asynchronously-1m4f</guid>
      <description>&lt;p&gt;In my previous article, I've talked about &lt;a href="https://dev.to/aws-builders/synchronous-aws-lambda-amazon-api-gateway-limits-and-what-to-do-about-them-2oec"&gt;&lt;em&gt;Synchronous AWS Lambda &amp;amp; Amazon API Gateway limits and what to do about them&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As stated in the blog post, the ultimate solution for a "big payload" problem is making the architecture asynchronous. Let us then zoom in on the aspect of asynchronous communication in the &lt;a href="https://aws.amazon.com/api-gateway/" rel="noopener noreferrer"&gt;Amazon API Gateway&lt;/a&gt; service context and build a serverless architecture based on the notion of "pending work".&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem refresher
&lt;/h2&gt;

&lt;p&gt;The following illustrates the Amazon API Gateway &amp;amp; &lt;a href="https://aws.amazon.com/lambda/" rel="noopener noreferrer"&gt;AWS Lambda&lt;/a&gt; payload size problem I've touched about in the previous article.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffz6tq6h3zchsg27s2v95.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffz6tq6h3zchsg27s2v95.jpeg" alt="Problem refresher"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No matter how hard you try, you &lt;strong&gt;unable&lt;/strong&gt; to &lt;strong&gt;synchronously send more than 6 MB of data&lt;/strong&gt; to your AWS Lambda function. The limit can seriously mess with your significant data-processing needs.&lt;/p&gt;

&lt;p&gt;Luckily there are ways to process much more data via AWS Lambda using asynchronous workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  The starting line - pushing the data to the storage layer
&lt;/h2&gt;

&lt;p&gt;Suppose you were to ask the community about the potential solution to this problem. In that case, I wager that the most common answer would be to use &lt;a href="https://aws.amazon.com/s3/" rel="noopener noreferrer"&gt;Amazon S3&lt;/a&gt; as the data storage layer and utilize &lt;em&gt;&lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/PresignedUrlUploadObject.html" rel="noopener noreferrer"&gt;presigned URLs&lt;/a&gt;&lt;/em&gt; to push the data to Amazon S3 storage.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0d6wvy9xm2d0tkd5t7qr.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0d6wvy9xm2d0tkd5t7qr.jpeg" alt="Presigned URL flow with S3"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While very universal, the flow of &lt;em&gt;presigned URL to Amazon S3&lt;/em&gt; can be nuanced, especially since one can create the &lt;em&gt;presigned URL&lt;/em&gt; in two ways.&lt;/p&gt;

&lt;p&gt;Describing these would make this article a bit too long for my liking, so I'm going to defer you to &lt;a href="https://zaccharles.medium.com/s3-uploads-proxies-vs-presigned-urls-vs-presigned-posts-9661e2b37932" rel="noopener noreferrer"&gt;this great article&lt;/a&gt; by my colleague &lt;a href="https://twitter.com/zaccharles" rel="noopener noreferrer"&gt;Zac Charles&lt;/a&gt; which did the topic much more justice than I could ever do.&lt;/p&gt;

&lt;h2&gt;
  
  
  I have the data in Amazon S3. Now what?
&lt;/h2&gt;

&lt;p&gt;Before processing the data, our system must know whether the client used the &lt;em&gt;presigned URL&lt;/em&gt; to push the data to the storage layer. To my best knowledge, there are two options we can pursue here (please let me know if there are other ways to go about it, I'm very keen to learn!)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I omitted the &lt;a href="https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-user-guide.html" rel="noopener noreferrer"&gt;AWS CloudTrail&lt;/a&gt; to &lt;a href="https://aws.amazon.com/eventbridge/" rel="noopener noreferrer"&gt;Amazon EventBridge&lt;/a&gt; flow on purpose as I think engineers should favor the direct integration with AWS EventBridge instead.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  S3 Event Notifications
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/NotificationHowTo.html" rel="noopener noreferrer"&gt;Amazon S3 event notifications&lt;/a&gt;, till recently, was a de facto way of knowing whether data landed into Amazon S3.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsr8taqny6r2jdaro6oxu.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsr8taqny6r2jdaro6oxu.jpeg" alt="S3 Event Notifications flow"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While sufficient for most use-cases, the feature is not without its problems, the biggest of which, I would argue, are the misunderstandings around event filtering and IaC implementation.&lt;/p&gt;

&lt;p&gt;The most essential thing to have in mind when it comes to &lt;strong&gt;event filtering&lt;/strong&gt; is that you &lt;strong&gt;cannot use wildcards for prefix or suffix&lt;/strong&gt; matching in your filtering rules. There are more things to consider, though. If you plan to use the filtering feature of S3 event notifications, I strongly encourage you to read &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/notification-how-to-filtering.html" rel="noopener noreferrer"&gt;this documentation page&lt;/a&gt; thoroughly.&lt;/p&gt;

&lt;p&gt;On the &lt;strong&gt;IaC side of things&lt;/strong&gt;, know that, in creating the &lt;a href="https://aws.amazon.com/cloudformation/" rel="noopener noreferrer"&gt;AWS CloudFormation&lt;/a&gt; template, you might &lt;a href="https://aws.amazon.com/blogs/mt/resolving-circular-dependency-in-provisioning-of-amazon-s3-buckets-with-aws-lambda-event-notifications/" rel="noopener noreferrer"&gt;end up with circular dependency problems&lt;/a&gt;. Deployment frameworks like &lt;a href="https://aws.amazon.com/cdk/" rel="noopener noreferrer"&gt;AWS CDK&lt;/a&gt; will make sure that should never happen. However, I still think you should be aware of this potential problem, even if you use deployment frameworks.&lt;/p&gt;

&lt;h3&gt;
  
  
  EventBridge events
&lt;/h3&gt;

&lt;p&gt;In late 2021, AWS announced &lt;a href="https://aws.amazon.com/blogs/aws/new-use-amazon-s3-event-notifications-with-amazon-eventbridge/" rel="noopener noreferrer"&gt;Amazon EventBridge support for S3 Event Notifications&lt;/a&gt;. The announcement had a warm welcome in the serverless community as EventBridge integration solves most of the "native" S3 event notifications problems.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F34gautwqzttxb92tktvd.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F34gautwqzttxb92tktvd.jpeg" alt="S3 event to EventBridge flow"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Utilizing &lt;a href="https://aws.amazon.com/eventbridge/" rel="noopener noreferrer"&gt;Amazon EventBridge&lt;/a&gt; for S3 events gives you &lt;strong&gt;more extensive integration surface area&lt;/strong&gt; (you can forward the event to more targets) and &lt;strong&gt;better filtering capabilities&lt;/strong&gt; (one of the strong points of Amazon EventBridge). The integration is not without its problems, though.&lt;/p&gt;

&lt;p&gt;For starters, the events are sent to the "default" bus, and using EventBridge might be more costly for high event volumes. To learn more about different caveats, consider giving &lt;a href="https://bitesizedserverless.com/bite/monitor-events-from-multiple-s3-buckets-with-eventbridge/" rel="noopener noreferrer"&gt;this great article&lt;/a&gt; a read.&lt;/p&gt;

&lt;h2&gt;
  
  
  Munching on the data
&lt;/h2&gt;

&lt;p&gt;You have the data in the Amazon S3 storage, and you have a way to notify your system about that fact. Now what? How could you process the data and yield the result back to the user?&lt;/p&gt;

&lt;p&gt;Given the nature of AWS, for better or worse, there are multiple scenarios one can move forward. We will start from the "simplest" architecture and move our way up to deploying an orchestrator with shared, high-speed data access.&lt;/p&gt;

&lt;h3&gt;
  
  
  Processing with a dedicated AWS Lambda
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://aws.amazon.com/lambda/" rel="noopener noreferrer"&gt;AWS Lambda&lt;/a&gt; service is often called a "swiss knife" of serverless. In most cases, processing the data in-memory within the AWS Lambda is sufficient.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5dgovmp4f1jix4t7c505.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5dgovmp4f1jix4t7c505.jpeg" alt="Processing the S3 upload event via AWS Lambda"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The only limitations are your imagination and the AWS Lambda service timeout – the 15-minute maximum function runtime. Please note that &lt;strong&gt;in this setup, your AWS Lambda function must spend some of that time downloading the object&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I'm vague about saving the results of the performed work on purpose as it is very use-case dependant. You might want to keep the outcome of your work back on Amazon S3 or add an entry to a database. Up to you.&lt;/p&gt;

&lt;p&gt;But what if that 15-minute timeout limitation is a thorn at your side? What if the process within the compute layer of the solution is complex and would benefit from splitting it into multiple chunks? If that is the case, keep on reading, we are going to be talking about &lt;a href="https://aws.amazon.com/step-functions/" rel="noopener noreferrer"&gt;AWS Step Functions&lt;/a&gt; next.&lt;/p&gt;

&lt;h3&gt;
  
  
  Processing with Amazon StepFunctions
&lt;/h3&gt;

&lt;p&gt;If the compute process within the AWS Lambda we looked at previously is complex or takes more time than the hard timeout limit, the AWS Step Functions might be just the service you need.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F88dvip4lft2ovfx1zyyh.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F88dvip4lft2ovfx1zyyh.jpeg" alt="Processing the S3 upload event via AWS Step Functions"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Take a note of the number of times the code within your Step Function definition needs to download the object from Amazon S3 storage (the illustration being an example, of course). Depending on the workload, that number may vary, but no matter the workflow, after a certain number threshold, it does feel "wasteful" for me to have to download the object again and again.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Keep in mind that AWS Step Functions maximum &lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/avoid-exec-failures.html" rel="noopener noreferrer"&gt;payload size is 256KB&lt;/a&gt;. That is why you have to download the object repeatedly whenever you need access to the object.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You could implement &lt;a href="https://www.jambit.com/en/latest-info/toilet-papers/easy-in-memory-caching-for-aws-lambda/" rel="noopener noreferrer"&gt;in-memory caching in your AWS Lambda function&lt;/a&gt;, but that technique only applies to a single AWS Lambda and is tied to its container lifecycle.&lt;/p&gt;

&lt;p&gt;Depending on the requirements and constraints, I like to use &lt;a href="https://aws.amazon.com/efs/" rel="noopener noreferrer"&gt;Amazon EFS&lt;/a&gt; integration with AWS Lambda in such situations. Amazon EFS allows me to have a storage layer shared by all AWS Lambda functions that partake in the workflow. Let us look into that next.&lt;/p&gt;

&lt;h4&gt;
  
  
  Shared AWS Lambda storage
&lt;/h4&gt;

&lt;p&gt;Before starting, understand that &lt;strong&gt;using Amazon EFS with AWS Lambda functions requires &lt;a href="https://aws.amazon.com/vpc/" rel="noopener noreferrer"&gt;Amazon VPC&lt;/a&gt;&lt;/strong&gt;. For some workflows, this fact does not change anything. For others, it does. I strongly advocate keeping an open mind (I know some people from serverless community despise VPCs) and evaluating the architecture according to your business needs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6cqtab15g92x17kofsqn.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6cqtab15g92x17kofsqn.jpeg" alt="Processing the S3 upload event via AWS Step Functions and Amazon EFS"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This architecture is, arguably, quite complex. You have to consider some networking concerns. If your object is quite large and downloading it takes a lot of time, I would advise you to look into this architecture – VPCs do not bite!&lt;/p&gt;

&lt;p&gt;Suppose you yearn for a practical example, &lt;a href="https://github.com/WojciechMatuszewski/serverless-video-transcribe-fun" rel="noopener noreferrer"&gt;here is a sample architecture I've built&lt;/a&gt; for parsing media files. It utilizes Amazon EFS for fast access to that video file across all AWS Lambda functions involved in the AWS Step Function workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Yielding back to the user
&lt;/h2&gt;

&lt;p&gt;With the compute part behind us, it is time to see how we might notify the user that our system processed the object and that the results are available.&lt;/p&gt;

&lt;p&gt;No matter what kind of solution you choose to notify the user about the result, the state of the work has to be saved somewhere. Is the work pending? Is it finished? Maybe an error occurred?&lt;/p&gt;

&lt;h3&gt;
  
  
  Keeping track of the work status
&lt;/h3&gt;

&lt;p&gt;The following is a diagram of an example architecture that keeps track of the status of the performed work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmxapk7yv5ue3cuw41djl.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmxapk7yv5ue3cuw41djl.jpeg" alt="Generic diagram depicting how to keep track of the work state via CDC"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The database sends CDC events to the system. We could use these CDC events to notify the user about the work progress in real-time! (although that might not be necessary, in most scenarios, polling for the results by the client is sufficient).&lt;/p&gt;

&lt;p&gt;In my humble opinion, the &lt;code&gt;key:value&lt;/code&gt; nature of the "work progress" data makes the &lt;a href="https://aws.amazon.com/dynamodb/" rel="noopener noreferrer"&gt;Amazon DynamoDB&lt;/a&gt; a perfect choice for the database component (nothing is stopping you from using RDS, which also supports CDC events, which themselves are optional here).&lt;/p&gt;

&lt;h3&gt;
  
  
  Notifying the user about the results
&lt;/h3&gt;

&lt;p&gt;The last piece of the puzzle is making sure the user has a way to be notified (or retrieve) the status of the request.&lt;/p&gt;

&lt;p&gt;Usually, in such situations, we have two options to consider – either we implement an API in which the user is going to pool for the updates, or we push the status directly to the user.&lt;/p&gt;

&lt;p&gt;The first option, pooling, would look similar to the following diagram in terms of architecture.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo5imk29vemne93gm6om9.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo5imk29vemne93gm6om9.jpeg" alt="Polling for changes"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This architecture is sufficient up to a particular scale. The architecture must scale according to the pollers hitting the API. The more pollers actively engage with the API, the higher the chance of throttling or other issues (refer to the &lt;a href="https://en.wikipedia.org/wiki/Thundering_herd_problem" rel="noopener noreferrer"&gt;&lt;em&gt;Thundering herd problem&lt;/em&gt;&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;In higher throughput scenarios, one might want to replace the polling behavior with a &lt;strong&gt;push model based on &lt;a href="https://en.wikipedia.org/wiki/WebSocket" rel="noopener noreferrer"&gt;&lt;em&gt;WebSockets&lt;/em&gt;&lt;/a&gt;&lt;/strong&gt;. For AWS-specific implementations, I would recommend looking at &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api.html" rel="noopener noreferrer"&gt;Amazon API Gateway WebSocket support&lt;/a&gt; or &lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/mqtt.html" rel="noopener noreferrer"&gt;AWS IoT Core WebSockets&lt;/a&gt; for broadcast-like use-cases.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I've written an article solely focused on serverless WebSockets on AWS. You can find &lt;a href="https://dev.to/aws-builders/serverless-websockets-on-aws-3nm9"&gt;the blog post here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Closing words
&lt;/h2&gt;

&lt;p&gt;And that is the end of our journey. We have looked at how to get the large payload into our system, process it, and respond to the user. Implementing this architecture is not a small feat, and I hope you found the walkthrough helpful.&lt;/p&gt;

&lt;p&gt;For more AWS serverless content, consider following me on Twitter - &lt;a href="https://twitter.com/wm_matuszewski" rel="noopener noreferrer"&gt;@wm_matuszewski&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thank you for your precious time.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>apigw</category>
      <category>sfn</category>
    </item>
    <item>
      <title>Synchronous AWS Lambda &amp; Amazon API Gateway limits and what to do about them</title>
      <dc:creator>Wojciech Matuszewski</dc:creator>
      <pubDate>Sun, 10 Apr 2022 15:27:20 +0000</pubDate>
      <link>https://forem.com/aws-builders/synchronous-aws-lambda-amazon-api-gateway-limits-and-what-to-do-about-them-2oec</link>
      <guid>https://forem.com/aws-builders/synchronous-aws-lambda-amazon-api-gateway-limits-and-what-to-do-about-them-2oec</guid>
      <description>&lt;p&gt;In the era of AWS serverless computing, there are two services that, one would argue, are used together very frequently – the &lt;a href="https://aws.amazon.com/lambda/" rel="noopener noreferrer"&gt;AWS Lambda&lt;/a&gt; and &lt;a href="https://aws.amazon.com/api-gateway/" rel="noopener noreferrer"&gt;Amazon API Gateway&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Combining these two services allows developers to create APIs that serve millions of customers daily, scaling elastically depending on the workload. It is not all sunshine and rainbows, though.&lt;/p&gt;

&lt;p&gt;In your AWS serverless journey, you will, at some point, end up faced with inbound payload size limits, most likely when interacting with AWS Lambda and Amazon API Gateway.&lt;/p&gt;

&lt;p&gt;In this article, I will lay out different alternatives you might want to consider when you end up in a situation where Amazon API Gateway and AWS Lambda inbound payload size limits constrain your &lt;strong&gt;synchronous&lt;/strong&gt; workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  The typical AWS Lambda Amazon API Gateway synchronous architecture
&lt;/h2&gt;

&lt;p&gt;Let us examine what, I would argue, is the most typical synchronous architecture that combines the AWS Lambda and Amazon API Gateway services.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh29s6a650cm8hfgqbjq7.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh29s6a650cm8hfgqbjq7.jpeg" alt="Typical synchronous architecture"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;There is nothing wrong with it&lt;/strong&gt;. I would argue that the architecture is just fine for the vast majority of use-cases, especially for creating API routes in a serverless way.&lt;/p&gt;

&lt;h3&gt;
  
  
  The payload size difference problem
&lt;/h3&gt;

&lt;p&gt;It is crucial to notice one critical detail – the &lt;strong&gt;difference in maximum payload size of Amazon API Gateway and AWS Lambda&lt;/strong&gt;. This delta might be frustrating and confusing. If you send a payload bigger than 6 MB, Amazon API Gateway will happily accept it and fail when invoking the AWS Lambda.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnh0a7nlrusjy7g1t6bcy.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnh0a7nlrusjy7g1t6bcy.jpeg" alt="Payload size delta problem in Typical APIGW Lambda sync architecture"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm not aware of any other solution to this problem than to &lt;strong&gt;restrict the payload size in the user-land&lt;/strong&gt;. Amazon API Gateway can &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-method-request-validation.html" rel="noopener noreferrer"&gt;validate the request via JSONSchema&lt;/a&gt;, but, to my best knowledge, you cannot validate the size of the payload.&lt;/p&gt;

&lt;p&gt;One might also look into &lt;a href="https://aws.amazon.com/waf/" rel="noopener noreferrer"&gt;AWS WAF&lt;/a&gt;, where request body validation is possible, but only &lt;a href="https://docs.aws.amazon.com/waf/latest/APIReference/API_SizeConstraintStatement.html" rel="noopener noreferrer"&gt;for payloads of size up to 8192 bytes&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  I need to process bigger payloads synchronously
&lt;/h3&gt;

&lt;p&gt;If you cannot afford or do not want to re-architect your solution to be asynchronous (which would, in theory, enable you to process potentially unbound payload sizes), there is one quick-win type of change you can make that could fit your use case. I'm referring to &lt;strong&gt;compressing the payload before sending it to Amazon API Gateway&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgcpupk42j0tizmg7gmw5.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgcpupk42j0tizmg7gmw5.jpeg" alt="Compressing the payload before sending it to Amazon API Gateway"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice that the &lt;strong&gt;Amazon API Gateway is NOT decompressing the payload&lt;/strong&gt;. While the service in question &lt;a href="https://docs.amazonaws.cn/en_us/apigateway/latest/developerguide/api-gateway-gzip-compression-decompression.html" rel="noopener noreferrer"&gt;has this capability&lt;/a&gt;, it is the opposite of what we want to do. If we were to decompress at the Amazon API Gateway level, the service would try to send a 15 MB payload to AWS Lambda, resulting in an error.&lt;/p&gt;

&lt;h3&gt;
  
  
  I need more time to process my synchronous request
&lt;/h3&gt;

&lt;p&gt;By compressing the payload in the user-land, you can, to some extent, get around the 6 MB payload size limit. What about the 30-second response timeout?&lt;/p&gt;

&lt;p&gt;If your payload is large, you might need more time to process the request synchronously. Luckily, with some changes to our infrastructure, it is &lt;strong&gt;possible&lt;/strong&gt; to &lt;strong&gt;bump the response timeout to 15 minutes&lt;/strong&gt; (the AWS Lambda timeout).&lt;/p&gt;

&lt;h4&gt;
  
  
  AWS Lambda function URL
&lt;/h4&gt;

&lt;p&gt;A very recent addition to the AWS Lambda-related family of features, the &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-urls.html" rel="noopener noreferrer"&gt;AWS Lambda URL&lt;/a&gt; feature might be just what you need. We &lt;strong&gt;ditch Amazon API Gateway&lt;/strong&gt; in this architecture since it was the primary response timeout bottleneck.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj7w6dg89piw2w94pehhz.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj7w6dg89piw2w94pehhz.jpeg" alt="AWS Lambda URL instead of Amazon API Gateway"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep in mind&lt;/strong&gt; that ditching Amazon API Gateway could have enormous consequences depending on the use case. Amazon API Gateway is rich in features like validation, caching, usage keys/plans, etc. &lt;strong&gt;By utilizing the AWS Lambda URL feature, we gain the longer synchronous request timeout but lose all the niceties of Amazon API Gateway&lt;/strong&gt;. Keep this in mind while reaching out for this feature.&lt;/p&gt;

&lt;h4&gt;
  
  
  AWS Lambda fronted with Application Load Balancer
&lt;/h4&gt;

&lt;p&gt;Instead of using Amazon API Gateway for fronting the AWS Lambda function, we can use the &lt;a href="https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html" rel="noopener noreferrer"&gt;Application Load Balancer&lt;/a&gt;. Like in the case of the AWS Lambda function URL feature, you will lose many of the Amazon API Gateway features but gain a much longer response timeout.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In some architectures, with high-enough traffic and small sizes of the requests, fronting AWS Lambda with ALB is much more cost-effective than using Amazon API Gateway. You can read more about it &lt;a href="https://blog.tinystacks.com/battle-of-the-serverless-api-routers-alb-vs-api-gateway-pricing" rel="noopener noreferrer"&gt;in this article&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6v27uv1dg86iqi79jasz.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6v27uv1dg86iqi79jasz.jpeg" alt="AWS Lambda fronted with ALB"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note about "a lot" you see for &lt;em&gt;Maximum payload size&lt;/em&gt; and &lt;em&gt;Response timeout&lt;/em&gt; for ALB. I could not find quotas referring to these variables in the AWS documentation. &lt;strong&gt;If you know the limits for those, please let me know!&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The ultimate solution
&lt;/h2&gt;

&lt;p&gt;If the 6 MB payload size is not enough, even with compressed payloads, and the 15 minutes of response timeout is too short, consider &lt;strong&gt;re-architecting for asynchronous communication&lt;/strong&gt;. In the end &lt;strong&gt;waiting for responses is expensive&lt;/strong&gt; and reaching out for tools like AWS Lambda function URL or fronting AWS Lambda with ALB to go around the limits might be a sign that asynchronous processing would be the right fit for your use case.&lt;/p&gt;

&lt;p&gt;AWS exposes various services to facilitate such workloads, ranging from AWS Step Functions to AWS Batch or AWS Fargate and storing data on Amazon S3. This article would be too long if I were to describe all the possible asynchronous ways to get around the 6 MB AWS Lambda payload size limit – do not worry, that article is next on my list!&lt;/p&gt;

&lt;p&gt;Until that, I will leave you with &lt;a href="https://theburningmonk.com/2020/04/hit-the-6mb-lambda-payload-limit-heres-what-you-can-do/" rel="noopener noreferrer"&gt;this great article&lt;/a&gt; written by &lt;a href="https://twitter.com/theburningmonk" rel="noopener noreferrer"&gt;Yan Cui&lt;/a&gt; as a sneak-peek of what I will be covering.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing words
&lt;/h2&gt;

&lt;p&gt;I hope you find the approaches described in this blog post helpful. Remember that all limits have a reason, be it related to technical or hardware limitations. Some might seem off-putting, like the 6 MB AWS Lambda payload size limit. Still, I do wholeheartedly believe that some of them, most likely by accident, push your architecture more towards asynchronous communication, which in most cases is a good thing!&lt;/p&gt;

&lt;p&gt;Consider following me on &lt;a href="https://twitter.com/wm_matuszewski" rel="noopener noreferrer"&gt;Twitter – @wm_matuszewski&lt;/a&gt; so that you do not miss the follow-up article (and more AWS serverless-related content).&lt;/p&gt;

&lt;p&gt;Thank you for your valuable time.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>lambda</category>
      <category>apigw</category>
    </item>
    <item>
      <title>Minimal AWS SSO setup for personal AWS development</title>
      <dc:creator>Wojciech Matuszewski</dc:creator>
      <pubDate>Thu, 17 Mar 2022 13:48:29 +0000</pubDate>
      <link>https://forem.com/aws-builders/minimal-aws-sso-setup-for-personal-aws-development-220k</link>
      <guid>https://forem.com/aws-builders/minimal-aws-sso-setup-for-personal-aws-development-220k</guid>
      <description>&lt;p&gt;I would argue that the &lt;a href="https://aws.amazon.com/single-sign-on/" rel="noopener noreferrer"&gt;AWS SSO&lt;/a&gt; is a "hidden gem" of AWS service. With AWS SSO, you do not have to deal with &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users.html" rel="noopener noreferrer"&gt;AWS IAM Users&lt;/a&gt; and long-lived credentials.&lt;/p&gt;

&lt;p&gt;I have a suspicion that there is a lot of misinformation about AWS SSO out there. Some common misconceptions I've heard are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;"You need &lt;a href="https://www.okta.com/" rel="noopener noreferrer"&gt;OKTA&lt;/a&gt; to use AWS SSO"&lt;/em&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;"You have to use &lt;a href="https://aws.amazon.com/controltower/?control-blogs.sort-by=item.additionalFields.createdDate&amp;amp;control-blogs.sort-order=desc" rel="noopener noreferrer"&gt;AWS Control Tower&lt;/a&gt; to use AWS SSO"&lt;/em&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;"The AWS SSO setup is complex, and only experienced AWS developers can utilize it"&lt;/em&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of the above are correct. I'm using AWS SSO for my personal AWS stuff, and I've never had to interact with AWS Control Tower or OKTA in any shape or form. It is entirely possible to have AWS SSO working with a &lt;strong&gt;bare minimum &lt;a href="https://aws.amazon.com/organizations/" rel="noopener noreferrer"&gt;AWS Organizations&lt;/a&gt; and AWS IAM configuration&lt;/strong&gt;, and this blog post will show you how.&lt;/p&gt;

&lt;h2&gt;
  
  
  The why of AWS SSO for AWS development
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;"Why should I bother using/taking my time to learn AWS SSO?"&lt;/em&gt; is a valid question, especially when time is the scarcest resource we have. My arguments in favor of learning/using AWS SSO follow.&lt;/p&gt;

&lt;p&gt;The first point is that &lt;strong&gt;you do not have to worry about long-lived credentials&lt;/strong&gt; for your AWS accounts anymore (except the root one), which drastically improves the security posture of your environment.&lt;/p&gt;

&lt;p&gt;The second point is that &lt;strong&gt;if set up correctly&lt;/strong&gt;, AWS SSO is &lt;strong&gt;extremely convenient to use&lt;/strong&gt;. In my current personal dev workflow, all I do to obtain a time-bound AWS session is type one short command and pick the right account/IAM role combo.&lt;/p&gt;

&lt;p&gt;The third point is that &lt;strong&gt;knowing just a little about AWS SSO&lt;/strong&gt; could &lt;strong&gt;help you in your day job&lt;/strong&gt; where you &lt;strong&gt;most likely use OKTA (or any other identity provider) with AWS SSO&lt;/strong&gt; (If you are not, and the configuration you have works for you, more power to you). Someone had to know how to connect the services in the first place. That someone might be you :).&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting the stage
&lt;/h2&gt;

&lt;p&gt;Before we dive into the configuration itself, I think it is vital to set expectations regarding what I'm about to show you.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;We will only use the bare minimum of AWS services/features for simplicity.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I would not recommend using this setup for your company's AWS SSO story. Look into combining &lt;a href="https://github.com/org-formation/org-formation-cli" rel="noopener noreferrer"&gt;OrgFormation&lt;/a&gt; / AWS Control Tower with AWS SSO for that.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;I've tried to keep the complexity to a minimum&lt;/strong&gt; so that people who are not familiar with AWS (for example, me from 3 years ago) can go through the listed steps without significant issues.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You will be using the AWS console through configuration steps. Creating an IaC template to automate the setup is beyond this blog post's scope.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The end goal
&lt;/h2&gt;

&lt;p&gt;After you have completed the configuration steps, you will be able to do the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Login into a given AWS account console with a single click via the AWS SSO user portal. Here is a screenshot of my AWS SSO portal dashboard.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu2xz7v5p3aru31q9trl4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu2xz7v5p3aru31q9trl4.png" alt="AWS SSO portal dashboard sneak-peak" width="800" height="385"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Assume credentials for a given AWS IAM role in a given AWS account for CLI usage with a single command.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Interested? Let us get started with the configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  The configuration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/cli/" rel="noopener noreferrer"&gt;AWS CLI&lt;/a&gt; installed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The steps
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1&lt;/strong&gt; - If you do not have an AWS account already, create one. There are many great resources on how to do just that, for example, &lt;a href="https://acloudguru.com/videos/acg-fundamentals/how-to-create-an-aws-account?utm_campaign=11244863417&amp;amp;utm_source=google&amp;amp;utm_medium=cpc&amp;amp;utm_content=469352928666&amp;amp;utm_term=_&amp;amp;adgroupid=115625160932" rel="noopener noreferrer"&gt;this guide by A Cloud Guru&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I would &lt;strong&gt;strongly recommend this AWS account&lt;/strong&gt; be &lt;strong&gt;used only for AWS SSO / AWS Organizations set up&lt;/strong&gt;. Consider &lt;strong&gt;not&lt;/strong&gt; running any deployments on this account.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2&lt;/strong&gt;‏‏‎‏‏‎ - Pick the region you wish your AWS SSO configuration to be deployed into. To my best knowledge, the &lt;a href="https://docs.aws.amazon.com/singlesignon/latest/userguide/regions.html#region-data" rel="noopener noreferrer"&gt;AWS SSO is a region-bound service&lt;/a&gt;. &lt;strong&gt;For demonstration purposes&lt;/strong&gt; I will be using the &lt;em&gt;eu-west-1&lt;/em&gt; region throughout this blog post.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpwoctne9f5jh7y9o5jzh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpwoctne9f5jh7y9o5jzh.png" alt="2-AWS-Region" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3&lt;/strong&gt; - In the search bar, look for AWS SSO service. The &lt;em&gt;sso&lt;/em&gt; search phrase should do. Click on the service tile to go to its dashboard.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmjwcq49cy7v7mzyp4asu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmjwcq49cy7v7mzyp4asu.png" alt="3-search-for-sso" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4&lt;/strong&gt; - After landing on the AWS SSO dashboard, assuming you do not have it enabled in any other AWS regions, you should be presented with a landing page that contains a big &lt;em&gt;"Enable AWS SSO"&lt;/em&gt; button – click on it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frd7fdqgvciuao3eqr7ko.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frd7fdqgvciuao3eqr7ko.png" alt="4-landing-page-enable-sso" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5&lt;/strong&gt; - Assuming you do not have any &lt;a href="https://aws.amazon.com/organizations/" rel="noopener noreferrer"&gt;AWS Organizations&lt;/a&gt; created on this account, the AWS console will greet you with the popup to create one. Click on the &lt;em&gt;"Create AWS Organization"&lt;/em&gt; button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzlf3vk1l2hguzwjgiv8o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzlf3vk1l2hguzwjgiv8o.png" alt="5-create-organization-prompt" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;If you are new to AWS and have no idea what AWS Organizations are&lt;/strong&gt;, consider them a way of creating multiple AWS accounts with ease. &lt;strong&gt;You do NOT have to know a lot about them to configure AWS SSO&lt;/strong&gt;. Consult the &lt;a href="https://docs.aws.amazon.com/organizations/latest/userguide/orgs_introduction.html" rel="noopener noreferrer"&gt;AWS Organizations documentation&lt;/a&gt; for more information.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After waiting a bit for the AWS console to create the AWS Organization, you will be redirected to the AWS SSO service dashboard.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6&lt;/strong&gt; - Next up, we need to create an AWS account we will be SSOing into (and assuming given policy in that account, more on that later). As I eluded earlier, we will be using AWS Organizations to manage AWS accounts. AWS SSO service created an AWS Organization for us in the previous step. Go to the created AWS Organization by searching for the service name.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fatlo5dc1yneyf7apnyd5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fatlo5dc1yneyf7apnyd5.png" alt="6-Search-for-AWS-Organizations" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7&lt;/strong&gt; - Next up, click on the &lt;em&gt;"Add an AWS account"&lt;/em&gt; button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fom9qhk3uhtf2qo7ksjdi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fom9qhk3uhtf2qo7ksjdi.png" alt="7.1-Add an AWS account" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;We want to create a &lt;strong&gt;new AWS account&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pick whatever name you want. My recommendation would be to add the "Development" suffix to the name to signal that this is the account I'm using for playing around – i.e., it contains deployed AWS resources.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I would leave the &lt;em&gt;"IAM role name"&lt;/em&gt; default value. The permission model of AWS Organizations goes beyond the scope of this article.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I would not bother with any Tags for personal-only use.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0zheae95pb8izlk1zhlk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0zheae95pb8izlk1zhlk.png" alt="7.2-Create-AWS-Account" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8&lt;/strong&gt; - After you have successfully created the AWS account and set the password (the email you have received from AWS), it is time to go back to the AWS SSO dashboard to finish the configuration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgnt5r6kqdm1bqj2jsnin.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgnt5r6kqdm1bqj2jsnin.png" alt="8-Go-back-to-SSO" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;9&lt;/strong&gt; - Next, we have to create a &lt;a href="https://docs.aws.amazon.com/singlesignon/latest/userguide/permissionsetsconcept.html" rel="noopener noreferrer"&gt;permission set&lt;/a&gt; (a scary name for a collection of IAM Policies) you will be acquiring when SSOing into a given AWS account. &lt;strong&gt;This part is what allows us to ditch the long-lived credentials&lt;/strong&gt;. Switch to the &lt;em&gt;"Permission sets"&lt;/em&gt; tab and click on &lt;em&gt;"Create permission set"&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F06u7wwpnxogsaqvpdzvi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F06u7wwpnxogsaqvpdzvi.png" alt="9.1-SSO-permission-sets" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiy8et8k4msvn33mch6oh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiy8et8k4msvn33mch6oh.png" alt="9.2-SSO-create-permission-set" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;10&lt;/strong&gt; - Now comes the permission set creation wizard. The following are my recommendations for each step.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;For the &lt;em&gt;"Type"&lt;/em&gt; step, I recommend sticking with &lt;em&gt;"Use an existing job function policy"&lt;/em&gt; unless you are adept at writing AWS IAM Policies.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For the &lt;em&gt;"Detail"&lt;/em&gt; step, I recommend picking the &lt;em&gt;"PowerUserAccess"&lt;/em&gt; policy. &lt;strong&gt;Make sure you understand what a given policy grants&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When it comes to the &lt;em&gt;"Tags"&lt;/em&gt; step, I would not bother setting them for personal use, but I leave that decision up to you.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Tags are essential in a work environment where multiple teams operate on different resources.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flm9edogddajjuupkoida.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flm9edogddajjuupkoida.png" alt="10-Create-SSO-permission-set" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;11&lt;/strong&gt; - Next, we have to create a user that can sign in to an AWS SSO user portal. We will be associating the user with a permission set and AWS account later. To create an AWS SSO user, navigate to the &lt;em&gt;"Users"&lt;/em&gt; tab and click the &lt;em&gt;"Add user"&lt;/em&gt; button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr97sl1scuofoi5v1s9f6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr97sl1scuofoi5v1s9f6.png" alt="11-Add-SSO-User" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Make sure to save the username you specified in the &lt;em&gt;"Specify user details"&lt;/em&gt; step – you will need it later on.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I would suggest omitting creating groups for simplicity's sake, but that is up to you.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;After you have created the user, &lt;strong&gt;I would strongly consider adding an MFA for that user&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;12&lt;/strong&gt; - With the AWS SSO user created, it is time to tie everything we have been configuring together. Navigate to the &lt;em&gt;"AWS accounts"&lt;/em&gt; tab. We will be associating the AWS SSO user with a given permission set and an AWS account.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frrm7arirt8sse375f31r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frrm7arirt8sse375f31r.png" alt="12.1-navigate-to-accounts" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6fexjk8o3pyu6ckmeyls.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6fexjk8o3pyu6ckmeyls.png" alt="12.2-assign-users-or-groups" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As with AWS SSO users, the AWS console uses a wizard-style form for this configuration. The following are screenshots that should help you navigate the steps.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq6zp8z6hlmodgwts0tb5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq6zp8z6hlmodgwts0tb5.png" alt="12.3" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx7pvtl0vembpbaptckpk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx7pvtl0vembpbaptckpk.png" alt="12.4" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkrzhufd5c2hozf7zjca7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkrzhufd5c2hozf7zjca7.png" alt="12.5" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;p&gt;Okay, we have done everything we need to make the AWS SSO work in its simplest, from – having access to an AWS account without using long-lived credentials. Now it is time to test our setup to ensure everything is working correctly. Let us start with AWS console access using AWS SSO.&lt;/p&gt;

&lt;h3&gt;
  
  
  AWS console access through AWS SSO
&lt;/h3&gt;

&lt;p&gt;After finishing step 12 from the &lt;em&gt;The steps&lt;/em&gt; section of this article, navigate to the AWS SSO &lt;em&gt;"Dashboard"&lt;/em&gt; tab and copy the &lt;em&gt;"User portal URL"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi3n4yha2z75hgudy593g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi3n4yha2z75hgudy593g.png" alt="testing-aws-console-access" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After signing in successfully (&lt;strong&gt;use credentials for AWS SSO user and NOT the AWS account&lt;/strong&gt;), you should be able to pick the AWS account and the permission set combo, we have configured in the previous section.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6lbapxp3x1o0fce0i6vl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6lbapxp3x1o0fce0i6vl.png" alt="testing-aws-console-access-2" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;"Management console"&lt;/em&gt; link should redirect you to a given AWS account as a &lt;em&gt;federated user&lt;/em&gt; scoped to a given permission set.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fra74zap8vfdk7s90uesf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fra74zap8vfdk7s90uesf.png" alt="testing-aws-console-access-3" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consider bookmarking the &lt;em&gt;"User portal URL"&lt;/em&gt; for easy access&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  AWS CLI access
&lt;/h3&gt;

&lt;p&gt;With the AWS console access out of the way, let us focus on AWS CLI access and getting the AWS credentials from the AWS SSO session.&lt;/p&gt;

&lt;p&gt;Luckily for us, the &lt;a href="https://aws.amazon.com/cli/" rel="noopener noreferrer"&gt;AWS CLI&lt;/a&gt; already has excellent support for AWS SSO.&lt;/p&gt;

&lt;p&gt;I find the &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sso.html" rel="noopener noreferrer"&gt;official AWS CLI SSO configuration guide&lt;/a&gt; to be a very well-put resource on how to configure the AWS CLI with AWS SSO correctly.&lt;/p&gt;

&lt;p&gt;After configuring the CLI, I recommend you look into &lt;a href="https://github.com/benkehoe/aws-export-credentials" rel="noopener noreferrer"&gt;&lt;em&gt;aws-export-credentials&lt;/em&gt;&lt;/a&gt; for exporting the credentials as environment variables across different shells you use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Possible improvements
&lt;/h2&gt;

&lt;p&gt;One improvement that one can make to the configuration I presented is to encapsulate it within reproducible IaC deployment. This goes beyond my area of expertise as I'm more of an application developer that never had to deal with creating IaC code for AWS Organizations and AWS SSO.&lt;/p&gt;

&lt;p&gt;If you feel like this is what you want to do next – great! I would start by reading on either &lt;a href="https://aws.amazon.com/controltower/" rel="noopener noreferrer"&gt;AWS ControlTower&lt;/a&gt; or &lt;a href="https://bahr.dev/2022/02/07/org-formation/" rel="noopener noreferrer"&gt;OrgFormation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing words
&lt;/h2&gt;

&lt;p&gt;I hope you find this blog post helpful as I would a couple of months prior when I had no idea what I was doing going through the process I described in the &lt;em&gt;"The steps"&lt;/em&gt; section.&lt;/p&gt;

&lt;p&gt;We configured AWS SSO for our development account with twelve steps and ditched the long-lived credentials associated with IAM Users in the process.&lt;/p&gt;

&lt;p&gt;For more AWS / serverless content, consider following me on Twitter - &lt;a href="https://twitter.com/wm_matuszewski" rel="noopener noreferrer"&gt;@wm_matuszewski&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And, as always, thank you for your valuable time.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>sso</category>
      <category>iam</category>
    </item>
    <item>
      <title>Curious case of AWS mapping template built-in variables</title>
      <dc:creator>Wojciech Matuszewski</dc:creator>
      <pubDate>Mon, 07 Mar 2022 03:26:30 +0000</pubDate>
      <link>https://forem.com/aws-builders/curious-case-of-aws-mapping-template-built-in-variables-1og1</link>
      <guid>https://forem.com/aws-builders/curious-case-of-aws-mapping-template-built-in-variables-1og1</guid>
      <description>&lt;p&gt;&lt;a href="https://aws.amazon.com/api-gateway/" rel="noopener noreferrer"&gt;Amazon API Gateway&lt;/a&gt; is, along with &lt;a href="https://aws.amazon.com/lambda/" rel="noopener noreferrer"&gt;AWS Lambda&lt;/a&gt;, arguably, the most frequently used service in AWS serverless architectures. The service's feature spectrum, starting from rate-limiting and ending on request/response validation, is vast, making it an ideal choice for many API-driven applications.&lt;/p&gt;

&lt;p&gt;Amongst its features, as a &lt;a href="https://dev.to/aws-builders/lambda-less-outbound-http-requests-on-aws-serverless-41mf"&gt;"Lambda-less" enthusiast&lt;/a&gt;, one is particularly interesting to me – the ability to create &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/models-mappings.html#models-mappings-mappings" rel="noopener noreferrer"&gt;mapping templates&lt;/a&gt; that transform the payload or the response without the need for AWS Lambda.&lt;/p&gt;

&lt;p&gt;This article will touch on something I've spent many hours debugging – the behavior of the built-in mapping template variables concerning Amazon REST API Gateway &lt;a href="https://velocity.apache.org/engine/devel/vtl-reference.html" rel="noopener noreferrer"&gt;VTL&lt;/a&gt; and talk about my assumptions that turned out to be wrong.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The code in this article is sourced from this &lt;a href="https://github.com/WojciechMatuszewski/aws-mapping-template-variables" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;

&lt;p&gt;I often find that often the understanding lies in the concrete. This blog post is no exception. The following architecture will serve as a reference in our discussions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvr0636lxp9cjt64q0pez.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvr0636lxp9cjt64q0pez.jpeg" alt="The architecture"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The user makes a request to an API frontend with Amazon API Gateway backed by a &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-mock-integration.html" rel="noopener noreferrer"&gt;mock integration&lt;/a&gt;. The &lt;a href="https://aws.amazon.com/lambda/" rel="noopener noreferrer"&gt;AWS Lambda&lt;/a&gt; authorizer handles authorization. The role of the mapping template is to respond with some of the Amazon API Gateway mapping template variables, mainly the ones related to authorization.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;mapping template is crucial&lt;/strong&gt; since its structure dictates the response of the API. Let us look at it next.&lt;/p&gt;

&lt;h2&gt;
  
  
  The code
&lt;/h2&gt;

&lt;p&gt;Luckily, to showcase the behavior I'm teasing you about, we do not need to write a lot of code. A two-liner of a mapping template and a very bare-bones AWS Lambda authorizer will do.&lt;/p&gt;

&lt;p&gt;The following is the mapping template code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "authorizerContextUsernameProperty": "$context.authorizer.username",
    "wholeAuthorizerVariable": "$util.escapeJavaScript($context.authorizer)",
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The mapping template declares two keys, the &lt;code&gt;authorizerContextUsernameProperty&lt;/code&gt; which is read directly from the &lt;code&gt;$context.authorizer.username&lt;/code&gt; and the &lt;code&gt;wholeAuthorizerVariable&lt;/code&gt;. I'm escaping the value of &lt;code&gt;$context.authorizer&lt;/code&gt; to avoid parsing issues.&lt;/p&gt;

&lt;p&gt;And here is the AWS Lambda authorizer code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;APIGatewayTokenAuthorizerHandler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Assuming the user is authenticated and the token is valid&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;principalId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test-username&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;policyDocument&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;Statement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;execute-api:Invoke&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Allow&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;methodArn&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2012-10-17&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The authorizer is not doing much. It is only there to populate the &lt;code&gt;context&lt;/code&gt; property so that we have something to work with within the mapping template.&lt;/p&gt;

&lt;h2&gt;
  
  
  The surprising behavior
&lt;/h2&gt;

&lt;p&gt;If I were to invoke the API, what would be the output? As a reminder, the following is our response mapping template.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "authorizerContextUsernameProperty": "$context.authorizer.username",
    "wholeAuthorizerVariable": "$util.escapeJavaScript($context.authorizer)",
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When I first started looking into API Gateway mapping templates &lt;strong&gt;I thought that the result would look similar to the following object&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"authorizerContextUsernameProperty"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"test-username"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"wholeAuthorizerVariable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;username&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;test-username&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, ... OTHER_PROPERTIES }"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To my surprise, &lt;strong&gt;that was not the case&lt;/strong&gt;. Here is the response I have received.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"authorizerContextUsernameProperty"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"test-username"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"wholeAuthorizerVariable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Even though we clearly see that &lt;code&gt;$context.authorizer.username&lt;/code&gt; is defined&lt;/strong&gt;, the &lt;strong&gt;&lt;code&gt;$context.authorizer&lt;/code&gt; somehow is "empty"&lt;/strong&gt;. After staring at the screen trying to come up with an answer, I've decided to check the whole &lt;code&gt;$context&lt;/code&gt; object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;{
    "authorizerContextUsernameProperty": "$context.authorizer.username",
&lt;span class="gd"&gt;-   "wholeAuthorizerVariable": "$util.escapeJavaScript($context.authorizer)",
&lt;/span&gt;&lt;span class="gi"&gt;+   "wholeAuthorizerVariable": "$util.escapeJavaScript($context)",
&lt;/span&gt;}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the result was surprising to me as well! (I redacted some of the values).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"authorizerContextUsernameProperty"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"test-username"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"wholeAuthorizerVariable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{resourceId=w7badv0rv4, authorizer=, resourcePath=/, httpMethod=GET, extendedRequestId=OY6_EHr1DoEFSzg=, requestTime=03/Mar/2022:04:06:30 +0000, path=/prod/, accountId=XXX, protocol=HTTP/1.1, requestOverride=, stage=prod, domainPrefix=9sd680n5z5, requestTimeEpoch=1646280390676, requestId=f9393904-3014-43f6-8e24-f594db2d7604, identity=, domainName=9sd680n5z5.execute-api.eu-west-1.amazonaws.com, responseOverride=, apiId=9sd680n5z5}"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, the &lt;code&gt;authorizer&lt;/code&gt;, &lt;code&gt;identity&lt;/code&gt;, &lt;code&gt;responseOverride&lt;/code&gt; properties are empty. Since I did not &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-override-request-response-parameters.html" rel="noopener noreferrer"&gt;override any request and response parameters&lt;/a&gt;, the emptiness of &lt;code&gt;responseOverride&lt;/code&gt; property is understandable. But what about the &lt;code&gt;authorizer&lt;/code&gt; and &lt;code&gt;identity&lt;/code&gt;? I can see that &lt;code&gt;$context.authorizer.username&lt;/code&gt; contains a value, otherwise the &lt;code&gt;authorizerContextUsernameProperty&lt;/code&gt; property would be empty.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about AWS AppSync?
&lt;/h2&gt;

&lt;p&gt;Like Amazon API Gateway, the &lt;a href="https://aws.amazon.com/appsync/" rel="noopener noreferrer"&gt;AWS AppSync&lt;/a&gt; service enables developers to write VTL mapping templates to transform data. Will AWS AppSync VTL parsing logic behave differently from the one we observed in Amazon API Gateway? Let us find out.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fer5yssqbb6q8g9e793eg.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fer5yssqbb6q8g9e793eg.jpeg" alt="AWS AppSync setup"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The architecture is very similar to the one we have looked at previously. The main difference is that instead of making a GET request, we will be making a POST request since AWS AppSync exposes a GraphQL API.&lt;/p&gt;

&lt;h3&gt;
  
  
  The mapping template and the response
&lt;/h3&gt;

&lt;p&gt;The following is the AppSync resolver &lt;em&gt;request&lt;/em&gt; mapping template we will be working with. Check out &lt;a href="https://docs.aws.amazon.com/appsync/latest/devguide/resolver-mapping-template-reference-none.html" rel="noopener noreferrer"&gt;&lt;em&gt;resolver mapping template reference for Node data source documentation&lt;/em&gt;&lt;/a&gt; for more information.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This article is not about AWS AppSync itself. If you are not familiar with the service, this &lt;a href="https://github.com/dabit3/awesome-aws-appsync" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt; contains a lot of AWS AppSync specific resources that you can consume.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#set($payload = {
  "authorizerContextUsernameProperty": $context.identity.resolverContext.username,
  "wholeAuthorizerVariable": $util.escapeJavaScript($context.identity.resolverContext)
})

{
  "version": "2018-05-29",
  "payload": $util.toJson($payload),
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the mapping templates would work the same way as those declared in the context of Amazon API Gateway, the &lt;code&gt;wholeAuthorizerVariable&lt;/code&gt; should return an empty string whenever I perform the GraphQL Query that uses this mapping template.&lt;/p&gt;

&lt;p&gt;That is not the case. The following is the response I've got.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"getData"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"authorizerContextUsernameProperty"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"test-username"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"wholeAuthorizerVariable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{username=test-username}"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;wholeAuthorizerVariable&lt;/code&gt; contains the key with a variable set by AWS Lambda authorizer – much different from how our sample Amazon API Gateway mapping template behaved.&lt;/p&gt;

&lt;p&gt;If I were to amend the mapping template the following way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;#set($payload = {
  "authorizerContextUsernameProperty": $context.identity.resolverContext.username,
&lt;span class="gd"&gt;- "wholeAuthorizerVariable": $util.escapeJavaScript($context.identity.resolverContext)
&lt;/span&gt;&lt;span class="gi"&gt;+ "wholeAuthorizerVariable": $util.escapeJavaScript($context.identity)
&lt;/span&gt;})
&lt;span class="err"&gt;
&lt;/span&gt;{
  "version": "2018-05-29",
  "payload": $util.toJson($payload),
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The GraphQL Query result is even more interesting as it leaks some service internals.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"getData"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"authorizerContextUsernameProperty"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"test-username"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"wholeAuthorizerVariable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"com.amazonaws.deepdish.common.identity.LambdaAuthIdentity@610a9a15"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I have no idea what &lt;code&gt;com.amazonaws.deepdish.common.identity.LambdaAuthIdentity@610a9a15&lt;/code&gt; is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bottom line
&lt;/h2&gt;

&lt;p&gt;It appears that Amazon API Gateway evaluates the mapping template variables in a &lt;em&gt;lazy&lt;/em&gt; fashion. Only the "deepest" properties of a given built-in variable are evaluated at all.&lt;/p&gt;

&lt;p&gt;The AWS AppSync acts a bit differently. As we saw earlier with the &lt;code&gt;$util.escapeJavaScript($context.identity.resolverContext)&lt;/code&gt; returning the &lt;em&gt;stringified&lt;/em&gt; object.&lt;/p&gt;

&lt;p&gt;After playing a bit more with the code, I have created the following rule of thumb I will be following from now on: &lt;em&gt;Whenever working with built-in mapping template variables, &lt;strong&gt;always&lt;/strong&gt; work with the "deepest" properties of a given variable&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing words
&lt;/h2&gt;

&lt;p&gt;I wrote this article because I've spent a lot of time debugging a "weird" behavior of Amazon API Gateway mapping templates. While the knowledge of what I wrote about is &lt;strong&gt;not necessary&lt;/strong&gt; for you to develop incredible applications, I hope it could save you the time I've wasted.&lt;/p&gt;

&lt;p&gt;For more AWS serverless content, consider following me on Twitter – &lt;a href="https://twitter.com/wm_matuszewski" rel="noopener noreferrer"&gt;@wm_matuszewski&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thank you for your precious time.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>appsync</category>
      <category>apigw</category>
      <category>cdk</category>
    </item>
    <item>
      <title>Saving on AWS Lambda Amazon CloudWatch Logs costs</title>
      <dc:creator>Wojciech Matuszewski</dc:creator>
      <pubDate>Mon, 21 Feb 2022 16:04:15 +0000</pubDate>
      <link>https://forem.com/aws-builders/saving-on-aws-lambda-amazon-cloudwatch-logs-costs-51od</link>
      <guid>https://forem.com/aws-builders/saving-on-aws-lambda-amazon-cloudwatch-logs-costs-51od</guid>
      <description>&lt;p&gt;&lt;a href="https://aws.amazon.com/lambda/" rel="noopener noreferrer"&gt;AWS Lambda&lt;/a&gt; is a go-to compute service for many serverless developers. Powerful and flexible, it allows for arbitrary code execution in various programming languages. The service is not all sunshine and rainbows – things tend to fail. And when they do, it is crucial to have visibility into what happened.&lt;/p&gt;

&lt;p&gt;A popular choice of gaining visibility into the AWS Lambda execution cycle is logging. When implemented with care, it can save you hours of debugging time, but it is a double-edged sword. The more you log, the more you will pay for &lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/WhatIsCloudWatchLogs.html" rel="noopener noreferrer"&gt;Amazon CloudWatch Logs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So how to eat the cake and have it too? How can we ensure that our logs are sufficient to help us debug potential AWS Lambda issues while still keeping our costs down?&lt;/p&gt;

&lt;p&gt;This blog post will showcase one technique I'm aware of that, &lt;strong&gt;in my personal opinion&lt;/strong&gt;, works excellent for this particular use case.&lt;/p&gt;

&lt;p&gt;Let us begin.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;All the code in this blog post is written in TypeScript. Treat the code snippets as pseudo-code rather than actual implementation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The problem with the extremes
&lt;/h2&gt;

&lt;p&gt;One mistake that, &lt;strong&gt;in my experience&lt;/strong&gt;, developers tend to make is to reach for extremes – either logging &lt;em&gt;everything&lt;/em&gt; or not logging at all. Both scenarios are problematic from an operational perspective.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frlshlasfye5kthww5bx1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frlshlasfye5kthww5bx1.png" alt="Two extremes" width="800" height="204"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Logging every action&lt;/strong&gt; undoubtedly gives you this warm fuzzy feeling of comfort that if something were to happen, you have every piece of context right in front of you. Hopefully, this gives you the ability to pinpoint any potential issues fast and resolve them in no time.&lt;/p&gt;

&lt;p&gt;The problem with this approach is that &lt;strong&gt;your Amazon CloudWatch bill&lt;/strong&gt; will &lt;strong&gt;most likely be huge&lt;/strong&gt; (relative to other services you use). I've seen serverless applications where Amazon CloudWatch contributed to 80% of the AWS bill. Not ideal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not having any logs in place or logging very little&lt;/strong&gt; can also have devastating effects. This time, it is not about the AWS CloudWatch costs but the &lt;strong&gt;potential time it takes to debug an issue within the AWS Lambda&lt;/strong&gt; function. Every error turns into a murder mystery with hours of resolution time. Not ideal.&lt;/p&gt;

&lt;p&gt;In the ideal world, we could &lt;strong&gt;log the bare minimum when everything is fine&lt;/strong&gt; but &lt;strong&gt;have very verbose logs when an error occurred&lt;/strong&gt;. It turns out the "ideal world" is within reach and, in my personal experience, works great for most AWS Lambda functions, no matter the programming language.&lt;/p&gt;

&lt;h2&gt;
  
  
  The technique
&lt;/h2&gt;

&lt;p&gt;The way we could achieve the logging nirvana I eluded to earlier is surprisingly not that complex (maybe I overlooked something? If so, please let me know!).&lt;/p&gt;

&lt;p&gt;The basic idea is as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use your logger of choice as you were before.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;log.debug&lt;/code&gt; calls, &lt;strong&gt;by default&lt;/strong&gt;, will not be pushed to STDOUT. Instead, we will keep them in memory.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;log.info&lt;/code&gt; calls &lt;strong&gt;will be&lt;/strong&gt; pushed to STDOUT.&lt;/li&gt;
&lt;li&gt;Whenever an error happens, in our case followed by a &lt;code&gt;log.error&lt;/code&gt; call, the logger releases all &lt;code&gt;log.debug&lt;/code&gt; messages, along with the &lt;code&gt;log.error&lt;/code&gt; message.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fryljzqdyzcvy3s73xpo2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fryljzqdyzcvy3s73xpo2.png" alt="Debug buffer" width="800" height="680"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With this setup in place, If the execution of an AWS Lambda function was successful, we only pay for the &lt;code&gt;log.info&lt;/code&gt; (and other, all depends on your setup) data. Conversely, if something blows up, we get all the context we need to debug the execution.&lt;/p&gt;

&lt;p&gt;Now, onto the implementation.&lt;/p&gt;

&lt;h3&gt;
  
  
  The implementation
&lt;/h3&gt;

&lt;p&gt;Here is a &lt;strong&gt;very contrived&lt;/strong&gt; example of an AWS Lambda function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;APIGatewayProxyEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;APIGatewayProxyHandler&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aws-lambda&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;APIGatewayProxyHandler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;performWork&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;performOtherWork&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Something blew up!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;performWork&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;APIGatewayProxyEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// implementation...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;performOtherWork&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// implementation...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code is not instrumented in any shape or form. Let us change that by adding logging.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;import { APIGatewayProxyEvent, APIGatewayProxyHandler } from "aws-lambda";
&lt;/span&gt;&lt;span class="gi"&gt;+ import { log } from './logger'
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="p"&gt;import { APIGatewayProxyEvent, APIGatewayProxyHandler } from "aws-lambda";
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="p"&gt;export const handler: APIGatewayProxyHandler = async event =&amp;gt; {
&lt;/span&gt;&lt;span class="gi"&gt;+ log.info("event", event);
&lt;/span&gt;  try {
    const data = await performWork(event);
    const result = await performOtherWork(data);
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gi"&gt;+ log.info("response", {...})
&lt;/span&gt;    return {
      statusCode: 200,
      body: JSON.stringify(result)
    };
  } catch (e) {
&lt;span class="gi"&gt;+ log.error("error!", e)
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;    return {
      statusCode: 500,
      message: "Something blew up!"
    };
  }
};
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="p"&gt;async function performWork(event: APIGatewayProxyEvent) {
&lt;/span&gt;&lt;span class="gi"&gt;+ log.debug("entering performWork", event)
&lt;/span&gt;  // implementation...
&lt;span class="gi"&gt;+ log.debug("exiting performWork", {...})
&lt;/span&gt;}
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="p"&gt;async function performOtherWork(data: unknown) {
&lt;/span&gt;&lt;span class="gi"&gt;+ log.debug("entering performOtherWork", data)
&lt;/span&gt;  // implementation...
&lt;span class="gi"&gt;+ log.debug("exiting performOtherWork", {...})
&lt;/span&gt;}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I've added quite a generous amount of logging to our code.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Keep in mind that this example is only for demonstrational purposes. You would most likely have a logging middleware that logs parts of the request and response in the real world.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If we were to &lt;strong&gt;allow every &lt;code&gt;log.X&lt;/code&gt; call to push to STDOUT&lt;/strong&gt;, &lt;strong&gt;given high-enough traffic&lt;/strong&gt;, our &lt;strong&gt;Amazon CloudWatch bill might be pretty high&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There are many dimensions when it comes to Amazon CloudWatch Logs pricing. One of them is the size of the log messages, which I'm not concerned about in the example. Check out the &lt;a href="https://aws.amazon.com/cloudwatch/pricing/" rel="noopener noreferrer"&gt;Amazon CloudWatch pricing page&lt;/a&gt; for more details.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But our logger has a twist I've talked about earlier. &lt;strong&gt;The &lt;code&gt;log.debug&lt;/code&gt; calls will NOT be pushed to STDOUT&lt;/strong&gt; by default. Here is a &lt;strong&gt;contrived&lt;/strong&gt; implementation of the logger.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// logger.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;debugCallsBuffer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="k"&gt;as &lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)[],&lt;/span&gt;
  &lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Debug buffer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debugCallsBuffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;debugLogCall&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;debugLogCall&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debugCallsBuffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;log&lt;/code&gt; object methods are facades over various &lt;code&gt;console&lt;/code&gt; methods. Instead of calling &lt;code&gt;console.debug&lt;/code&gt; eagerly, the &lt;code&gt;debug&lt;/code&gt; function pushes a function to the &lt;code&gt;debugCallsBuffer&lt;/code&gt; array. When we call the &lt;code&gt;error&lt;/code&gt; function, the &lt;code&gt;debugCallsBuffer&lt;/code&gt; is "flushed".&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You most likely use a third-party package for logging in your day-to-day work to ensure that the log messages you produce are &lt;a href="https://docs.aws.amazon.com/lambda/latest/operatorguide/parse-logs.html" rel="noopener noreferrer"&gt;&lt;em&gt;structured&lt;/em&gt;&lt;/a&gt;. Nevertheless, the idea still stands.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Neat! Now you can write &lt;code&gt;log.debug&lt;/code&gt; statements to your heart's content without the fear of logging a massive blob of data for every AWS Lambda function execution.&lt;/p&gt;

&lt;p&gt;We need to think about two more things before we start using the &lt;code&gt;log&lt;/code&gt; object for our logging.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;How do we "flush" the &lt;code&gt;debugCallsBuffer&lt;/code&gt; when AWS Lambda executes successfully – otherwise, we run a risk of logging debug messages from previous AWS Lambda executions!&lt;/li&gt;
&lt;li&gt;How do we "flush" the &lt;code&gt;debugCallsBuffer&lt;/code&gt; whenever your AWS Lambda is about to timeout.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Flushing after successful AWS Lambda execution
&lt;/h3&gt;

&lt;p&gt;If you read the documentation for &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/runtimes-context.html" rel="noopener noreferrer"&gt;AWS Lambda execution environment&lt;/a&gt;, you will learn about the concepts of &lt;em&gt;execution environment re-use&lt;/em&gt;, &lt;em&gt;freezing&lt;/em&gt; and &lt;em&gt;unfreezing&lt;/em&gt; the execution environment. These concepts are necessary to understand the WHY behind this section, so consider giving it a read if you have not already.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If AWS Lambda service did not re-use the &lt;em&gt;execution environments&lt;/em&gt;, we would not have been to cache at the AWS Lambda runtime level. For more information regarding caching at the AWS Lambda runtime level, consider reading &lt;a href="https://levelup.gitconnected.com/serverless-caching-strategies-part-3-lambda-runtime-b3d21250927b" rel="noopener noreferrer"&gt;this article&lt;/a&gt; or &lt;a href="https://theburningmonk.com/2019/10/all-you-need-to-know-about-caching-for-serverless-applications/" rel="noopener noreferrer"&gt;this article&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The following is a visual representation of the problem we are trying to solve in this section.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqzfjbb7932b8kpdfbzfx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqzfjbb7932b8kpdfbzfx.png" alt="Without flushing" width="800" height="609"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If we do not clear the &lt;code&gt;debugCallsBuffer&lt;/code&gt; when AWS Lambda succeeds&lt;/strong&gt;, we might &lt;strong&gt;log debug messages from the previous execution if it errors!&lt;/strong&gt;. Not ideal.&lt;/p&gt;

&lt;p&gt;The solution to the problem is very implementation-specific. Adding a &lt;strong&gt;clear&lt;/strong&gt; function for our contrived logger example will do.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;// logger.ts
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="p"&gt;export const log = {
&lt;/span&gt;  debugCallsBuffer: [] as (() =&amp;gt; void)[],
  info(message: string, ...args: any[]) {
    console.log(message, ...args);
  },
  error(message: string, ...args: any[]) {
    console.error(message, ...args);
&lt;span class="err"&gt;
&lt;/span&gt;    console.log("Debug buffer");
&lt;span class="err"&gt;
&lt;/span&gt;    this.debugCallsBuffer.forEach(debugLogCall =&amp;gt; debugLogCall());
  },
  debug(message: string, ...args: any[]) {
    this.debugCallsBuffer.push(() =&amp;gt; console.debug(message, ...args));
  },
&lt;span class="gi"&gt;+ clear() {
+   this.debugCallsBuffer = []
+ }
&lt;/span&gt;};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And within our AWS Lambda handler code...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;import { APIGatewayProxyEvent, APIGatewayProxyHandler } from "aws-lambda";
import { log } from './logger'
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="p"&gt;import { APIGatewayProxyEvent, APIGatewayProxyHandler } from "aws-lambda";
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="p"&gt;export const handler: APIGatewayProxyHandler = async event =&amp;gt; {
&lt;/span&gt; log.info("event", event);
  try {
    const data = await performWork(event);
    const result = await performOtherWork(data);
&lt;span class="err"&gt;
&lt;/span&gt; log.info("response", {...})
    return {
      statusCode: 200,
      body: JSON.stringify(result)
    };
  } catch (e) {
 log.error("error!", e)
&lt;span class="err"&gt;
&lt;/span&gt;    return {
      statusCode: 500,
      message: "Something blew up!"
    };
&lt;span class="gi"&gt;+ } finally {
+   log.clear();
+ }
&lt;/span&gt;};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, the code is very contrived, but the point still stands. If you do not "clear" the buffer after every execution, you might see weird logs in the AWS CloudWatch logs console.&lt;/p&gt;

&lt;h3&gt;
  
  
  Flushing before AWS Lambda timeout
&lt;/h3&gt;

&lt;p&gt;AWS Lambda is a time-bounded compute service. The maximum duration the AWS Lambda function can run for, at the time of writing this article, is &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html#function-configuration-deployment-and-execution" rel="noopener noreferrer"&gt;15 minutes&lt;/a&gt;. This setting is, of course, configurable.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you are using a framework to deploy and develop your serverless applications (which, in my humble opinion, you should), the framework you use might set a default function timeout for you. Be aware of that.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I would argue that if my AWS Lambda function is about to timeout, it would be wise to release all the logs from the &lt;code&gt;debugCallsBuffer&lt;/code&gt;. The function timeout will result in an error. If the function is frontend with &lt;a href="https://aws.amazon.com/api-gateway/" rel="noopener noreferrer"&gt;Amazon API Gateway&lt;/a&gt;, the Amazon API Gateway will return a 500 status code to the caller (unless you have configured it to do otherwise). With all the logs available to me, I would likely have a higher chance of fixing the timeout issues.&lt;/p&gt;

&lt;p&gt;To know whether our AWS Lambda function is about to timeout, we could use the &lt;code&gt;getRemainingTimeInMillis&lt;/code&gt; function retrieved from the &lt;em&gt;context&lt;/em&gt; that the service passes to our AWS Lambda function. Here is a sample implementation of logging an error (in turn releasing the &lt;code&gt;debug&lt;/code&gt; logs) just before AWS Lambda is about to timeout.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;APIGatewayProxyEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;APIGatewayProxyHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Context&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aws-lambda&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;log&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./logger&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;logBeforeTimeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;deadline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRemainingTimeInMillis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timeoutId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;About to timeout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timeoutId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;APIGatewayProxyHandler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cleanup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;logBeforeTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;performWork&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Something blew up!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;cleanup&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;performWork&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;APIGatewayProxyEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// implementation...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;logBeforeTimeout&lt;/code&gt; function starts a timer and logs an error before the deadline. It is &lt;strong&gt;essential&lt;/strong&gt; to &lt;strong&gt;clear the timer no matter if the execution was successful or not&lt;/strong&gt;. If you do not, you are asking for a memory leak to happen!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Small note for the Node.js users out there. It is &lt;strong&gt;entirely possible&lt;/strong&gt; for the &lt;code&gt;setTimeout&lt;/code&gt; never to run at all. If the &lt;a href="https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/" rel="noopener noreferrer"&gt;&lt;em&gt;Node.js event loop&lt;/em&gt;&lt;/a&gt; is blocked (think an infinite loop somewhere in your code), your timer will never run. One of the solutions I'm aware of is to run the timer in a separate thread.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Closing words
&lt;/h2&gt;

&lt;p&gt;There you have it. This is how to have low AWS CloudWatch Logs costs and have every log line available to you when an issue occurs. This blog was heavy on the theory side since I wanted to relay an idea (which I believe is language-agnostic) rather than a specific implementation.&lt;/p&gt;

&lt;p&gt;What do you think of this approach? Is it a bad idea? Maybe you have other solutions in mind? I'm always happy to hear your thoughts and opinions.&lt;/p&gt;

&lt;p&gt;For more serverless content, consider following me on Twitter - &lt;a href="https://twitter.com/wm_matuszewski" rel="noopener noreferrer"&gt;@wm_matuszewski&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thank you for your valuable time. Have a great day 👋.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>typescript</category>
      <category>observability</category>
    </item>
    <item>
      <title>Testing AWS Step Functions flows</title>
      <dc:creator>Wojciech Matuszewski</dc:creator>
      <pubDate>Mon, 14 Feb 2022 17:00:47 +0000</pubDate>
      <link>https://forem.com/aws-builders/testing-aws-step-functions-flows-2kpn</link>
      <guid>https://forem.com/aws-builders/testing-aws-step-functions-flows-2kpn</guid>
      <description>&lt;p&gt;&lt;a href="https://aws.amazon.com/step-functions/" rel="noopener noreferrer"&gt;AWS Step Functions&lt;/a&gt; is a serverless orchestrator service. Since its inception, it has become a go-to for all my low-code and orchestration needs on AWS.&lt;/p&gt;

&lt;p&gt;Recently, AWS announced that the &lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/sfn-local.html" rel="noopener noreferrer"&gt;Step Functions Local&lt;/a&gt; can now &lt;a href="https://aws.amazon.com/blogs/compute/mocking-service-integrations-with-aws-step-functions-local/" rel="noopener noreferrer"&gt;mock service integrations&lt;/a&gt;. I found the announcement a great opportunity and excuse to revisit the topic of testing in the context of AWS Step Functions, which historically was a bit hard.&lt;/p&gt;

&lt;p&gt;This article will cover the techniques I use for testing flows that utilize the AWS Step Functions service as the logic orchestrator. Let us begin.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The code in this blog post is written in &lt;a href="https://www.typescriptlang.org/" rel="noopener noreferrer"&gt;TypeScript&lt;/a&gt; and uses &lt;a href="https://aws.amazon.com/cdk/" rel="noopener noreferrer"&gt;AWS CDK&lt;/a&gt; for infrastructure piece. &lt;a href="https://github.com/WojciechMatuszewski/testing-step-functions" rel="noopener noreferrer"&gt;This GitHub repository&lt;/a&gt; contains all the code used in this article.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/WojciechMatuszewski" rel="noopener noreferrer"&gt;
        WojciechMatuszewski
      &lt;/a&gt; / &lt;a href="https://github.com/WojciechMatuszewski/testing-step-functions" rel="noopener noreferrer"&gt;
        testing-step-functions
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Testing AWS Step Functions flows&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;This repository contains examples of how one might test various AWS Step Functions flows.&lt;/p&gt;
&lt;p&gt;The test files are located in the &lt;code&gt;lib/__tests__&lt;/code&gt; directory.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Deployment&lt;/h2&gt;

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;npm run boostrap&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;npm run deploy&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Running the tests&lt;/h2&gt;

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Ensure that Docker is running.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Pull the &lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/sfn-local-docker.html" rel="nofollow noopener noreferrer"&gt;&lt;em&gt;aws-stepfunctions-local&lt;/em&gt;&lt;/a&gt; image.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;npm run test&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/WojciechMatuszewski/testing-step-functions" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;h2&gt;
  
  
  Layers of testing and confidence
&lt;/h2&gt;

&lt;p&gt;Before we dive into specific techniques, we shall take a swift detour and talk about testing in general.&lt;/p&gt;

&lt;p&gt;There are many heuristics when it comes to testing. There is the classical &lt;a href="https://martinfowler.com/articles/practical-test-pyramid.html" rel="noopener noreferrer"&gt;testing pyramid&lt;/a&gt; or the &lt;a href="https://engineering.atspotify.com/2018/01/11/testing-of-microservices/" rel="noopener noreferrer"&gt;testing honeycomb&lt;/a&gt; to name a few. In my personal opinion, &lt;strong&gt;in the context of serverless applications&lt;/strong&gt;, tests written according to the testing honeycomb will give you much more confidence and ROI per test written than the "traditional" testing pyramid approach.&lt;/p&gt;

&lt;p&gt;Before you base most of your tests on the local implementation of an AWS service, I urge you to think about the confidence the test gives you and less about how easy it is to write. Give the testing honeycomb a try. I'm positive you will not regret it!&lt;/p&gt;

&lt;h2&gt;
  
  
  By writing integration / end-to-end tests
&lt;/h2&gt;

&lt;p&gt;By utilizing e2e / integration tests, we gain the most confidence from our tests. That said, the tests are usually slow and can be hard to maintain to some degree.&lt;/p&gt;

&lt;p&gt;Here, you will be interacting with AWS services directly, executing the AWS Step Function and asserting the side-effects of the flow. The side-effect can be, for example, a new item in &lt;a href="https://aws.amazon.com/dynamodb/" rel="noopener noreferrer"&gt;Amazon DynamoDB&lt;/a&gt; or a message pushed to &lt;a href="https://aws.amazon.com/sqs/" rel="noopener noreferrer"&gt;Amazon SQS&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This style of testing is not free of challenging problems to tackle. Testing AWS Step Functions flow end-to-end becomes tricky when the Step Function definition is complex and contains multiple branches and error fallbacks.&lt;/p&gt;

&lt;p&gt;Let us start with a basic example of saving a user into the Amazon DynamoDB table, then work our way up with Step Function definition complexity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Simplistic Step Functions workflows
&lt;/h3&gt;

&lt;p&gt;The following image represents the Step Function definition we would like to test.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg27w90enjgffvll3lluv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg27w90enjgffvll3lluv.png" alt="Simplistic AWS Step Function definition"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Lacking branching logic and error handling, all we have to do is test a single execution path.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You should most likely always have error handling in place in your Step Function definition. This particular Step Function is only here for demonstration purposes. We will tackle testing error states later in the article.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is how I would write the test I'm referring to.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="c1"&gt;// simplistic-e2e.test.ts&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SFNClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;StartExecutionCommand&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-sdk/client-sfn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sfnClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SFNClient&lt;/span&gt;&lt;span class="p"&gt;({});&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Saves the user in the DynamoDB table&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;startExecutionResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sfnClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StartExecutionCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;stateMachineArn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SIMPLISTIC_E2E_STEP_FUNCTION_ARN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;John&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Doe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_REGION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SIMPLISTIC_E2E_DATA_TABLE_NAME&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;toHaveItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;PK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`USER#&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;startExecutionResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;executionArn&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;PK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`USER#&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;startExecutionResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;executionArn&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;John&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Doe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;First, I start the Step Function, and then I assert, using the &lt;a href="https://github.com/erezrokah/aws-testing-library" rel="noopener noreferrer"&gt;aws-testing-library&lt;/a&gt;, whether the item was correctly saved into the Amazon DynamoDB.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Check out the &lt;a href="https://github.com/aleios-cloud/sls-test-tools" rel="noopener noreferrer"&gt;sls-test-tools&lt;/a&gt; as well. It is a great library. I'm using &lt;em&gt;aws-testing-library&lt;/em&gt; because I'm used to it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And that is it! Since this Step Function did not contain multiple branches and error handling, writing an end-to-end test takes little to no effort. With the most basic example behind us, let us tackle error states and multiple branches next.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step Functions with branching logic
&lt;/h3&gt;

&lt;p&gt;Step Functions can be complex, especially when taking error handling and &lt;code&gt;Choice&lt;/code&gt; steps into account. With the increased complexity comes increased difficulty in writing end-to-end tests.&lt;/p&gt;

&lt;p&gt;Let us evolve our Step Function to include "background-check" &lt;a href="https://aws.amazon.com/lambda/" rel="noopener noreferrer"&gt;AWS Lambda&lt;/a&gt; function. Depending on the result, the user in the DynamoDB table will have its &lt;code&gt;backgroundCheck&lt;/code&gt; attribute populated (either "PASS", "FAIL" or "ERROR").&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdyhnbbvpzneruot4jnec.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdyhnbbvpzneruot4jnec.png" alt="Branching logic Step Function"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can find the AWS Step Function AWS CDK definition &lt;a href="https://github.com/WojciechMatuszewski/testing-step-functions/blob/main/lib/branching-logic-e2e.ts" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To test all possible execution paths of the Step Function, &lt;strong&gt;we have to have a way to force an error or particular response&lt;/strong&gt; for the &lt;code&gt;BackgroundCheckStep&lt;/code&gt; AWS Lambda function – not an easy task!&lt;/p&gt;

&lt;p&gt;So what can we do in this situation? Enter &lt;strong&gt;aws-stepfunctions-local&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;aws-stepfunctions-local&lt;/em&gt; is a local implementation of the AWS Step Functions service provided to us by great folks at AWS. With this tool, we &lt;strong&gt;will force the lambda to error without mocking other steps&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You could write a test that executes asserts on the status returned by the "background-check" AWS Lambda function. For the interest of time, I've chosen only to write a test for the case where the "background-check" fails.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Since I will be using the Docker version of the &lt;em&gt;aws-stepfunctions-local&lt;/em&gt;, the &lt;strong&gt;first step&lt;/strong&gt; is to integrate the act of spinning up and spinning down the container into the testing flow. My personal go-to in such situations is the &lt;a href="https://www.npmjs.com/package/testcontainers" rel="noopener noreferrer"&gt;testcontainers&lt;/a&gt; package.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="c1"&gt;// branching-logic-e2e.test.ts&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;StartedTestContainer&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;beforeAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mockConfigPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./branching-logic-e2e.mocks.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GenericContainer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;amazon/aws-stepfunctions-local&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withExposedPorts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8083&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withBindMount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mockConfigPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/home/branching-logic-e2e.mocks.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ro&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withEnv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SFN_MOCK_CONFIG&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/home/branching-logic-e2e.mocks.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withEnv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withEnv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;// For federated credentials (for example, SSO), this environment variable is required.&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withEnv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AWS_SESSION_TOKEN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_SESSION_TOKEN&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withEnv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AWS_DEFAULT_REGION&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_REGION&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;afterAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Let us unpack what is going on here.&lt;/p&gt;

&lt;p&gt;First, the mysterious &lt;code&gt;mockConfigPath&lt;/code&gt; and subsequent usages of this variable. The path points to a mock configuration file described on &lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/sfn-local-test-sm-exec.html" rel="noopener noreferrer"&gt;&lt;em&gt;aws-stepfunctions-local&lt;/em&gt; documentation page&lt;/a&gt; and contains a mock definition for the &lt;code&gt;BackgroundCheckStep&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"StateMachines"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"BranchingLogic"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"TestCases"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"ErrorPath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"BackgroundCheckStep"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"BackgroundCheckError"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"MockedResponses"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"BackgroundCheckError"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Throw"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"Error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Lambda.TimeoutException"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"Cause"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Lambda timed out."&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Notice that I'm only concerned with the &lt;code&gt;BackgroundCheckStep&lt;/code&gt; here. &lt;strong&gt;By only mocking &lt;code&gt;BackgroundCheckStep&lt;/code&gt;, I can force this particular step to fail&lt;/strong&gt;. Other steps are not mocked. Thus &lt;strong&gt;the &lt;em&gt;aws-stepfunctions-local&lt;/em&gt; will reach out to native AWS services&lt;/strong&gt; – in our case Amazon DynamoDB.&lt;/p&gt;

&lt;p&gt;The Docker image needs to have AWS-related environment variables populated to allow &lt;em&gt;aws-stepfunctions-local&lt;/em&gt; to talk to other AWS services. Refer to &lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/sfn-local-config-options.html#docker-credentials" rel="noopener noreferrer"&gt;this documentation page&lt;/a&gt; for more information.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withEnv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withEnv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withEnv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AWS_SESSION_TOKEN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_SESSION_TOKEN&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withEnv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AWS_DEFAULT_REGION&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_REGION&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;As for the test itself, the first thing to do is gather necessary information about the Step Function under test.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="c1"&gt;// branching-logic-e2e.test.ts&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Handles the failure of the BackgroundCheck step&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sfnClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SFNClient&lt;/span&gt;&lt;span class="p"&gt;({});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;describeStepFunctionResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sfnClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DescribeStateMachineCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;stateMachineArn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BRANCHING_LOGIC_E2E_STEP_FUNCTION_ARN&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stepFunctionDefinition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nx"&gt;describeStepFunctionResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;definition&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stepFunctionRoleARN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;describeStepFunctionResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roleArn&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Rest of the test...&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We will need all of this information since we will be re-creating this Step Function locally. Remember, the &lt;em&gt;aws-stepfunctions-local&lt;/em&gt; is the engine that runs our Step Function.&lt;/p&gt;

&lt;p&gt;Next, let us re-create and run the Step Function that lives in the cloud using the &lt;em&gt;aws-stepfunctions-local&lt;/em&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="c1"&gt;// branching-logic-e2e.test.ts&lt;/span&gt;

&lt;span class="c1"&gt;// Test setup ...&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Handles the failure of the BackgroundCheck step&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Previous code snippet ...&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sfnLocalClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SFNClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`http://localhost:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;getMappedPort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8083&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createLocalSFNResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sfnLocalClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CreateStateMachineCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;definition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stepFunctionDefinition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;BranchingLogic&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;roleArn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stepFunctionRoleARN&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;startLocalSFNExecutionResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sfnLocalClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StartExecutionCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;stateMachineArn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;
        &lt;span class="nx"&gt;createLocalSFNResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stateMachineArn&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;#ErrorPath`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;John&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Doe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Rest of the test...&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;There are two &lt;strong&gt;essential&lt;/strong&gt; pieces of detail I would like to bring your attention to.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;name&lt;/code&gt; of the Step Function I'm creating. The &lt;strong&gt;&lt;code&gt;name&lt;/code&gt; parameter must be the same as declared in the mock configuration file&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;stateMachineArn&lt;/code&gt; format in the &lt;code&gt;StartExecutionCommand&lt;/code&gt; call. The convention of &lt;code&gt;ARN#TEST_CASE&lt;/code&gt; is required by &lt;em&gt;aws-stepfunctions-local&lt;/em&gt;. Refer to &lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/sfn-local-test-sm-exec.html#run-mocked-serv-integ-tests" rel="noopener noreferrer"&gt;this documentation page&lt;/a&gt; to learn more.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All that is left is to assert on the data of a given Amazon DynamoDB item. The assertion is almost identical as in the &lt;em&gt;Simplistic Step Functions&lt;/em&gt; section.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="c1"&gt;// branching-logic-e2e.test.ts&lt;/span&gt;

&lt;span class="c1"&gt;// Test setup ...&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Handles the failure of the BackgroundCheck step&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Previous code snippet ...&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_REGION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BRANCHING_LOGIC_E2E_DATA_TABLE_NAME&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;toHaveItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;PK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`USER#&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;startLocalSFNExecutionResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;executionArn&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;PK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`USER#&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;startLocalSFNExecutionResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;executionArn&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;John&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Doe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;// The `BackgroundCheckStep` must have failed for this attribute to have the `ERROR` value.&lt;/span&gt;
      &lt;span class="na"&gt;backgroundCheck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ERROR&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We assert on an Amazon DynamoDB table that lives in the AWS. Since we &lt;strong&gt;did not&lt;/strong&gt; provide any mocks for the step that saves the user data to Amazon DynamoDB, the &lt;em&gt;aws-stepfunctions-local&lt;/em&gt; made the request to the actual service, utilizing the credentials from Docker environment variables.&lt;/p&gt;

&lt;p&gt;
  Click to expand (the whole test definition)
  &lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;CreateStateMachineCommand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;DescribeStateMachineCommand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;SFNClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;StartExecutionCommand&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-sdk/client-sfn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;join&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GenericContainer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;StartedTestContainer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;testcontainers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;StartedTestContainer&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;beforeAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mockConfigPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./branching-logic-e2e.mocks.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GenericContainer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;amazon/aws-stepfunctions-local&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withExposedPorts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8083&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withBindMount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mockConfigPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/home/branching-logic-e2e.mocks.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ro&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withEnv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SFN_MOCK_CONFIG&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/home/branching-logic-e2e.mocks.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withEnv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withEnv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="cm"&gt;/**
     * For federated credentials (for example, SSO), this environment variable is required.
     */&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withEnv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AWS_SESSION_TOKEN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_SESSION_TOKEN&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withEnv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AWS_DEFAULT_REGION&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_REGION&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;afterAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Handles the failure of the BackgroundCheck step&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sfnClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SFNClient&lt;/span&gt;&lt;span class="p"&gt;({});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;describeStepFunctionResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sfnClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DescribeStateMachineCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;stateMachineArn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BRANCHING_LOGIC_E2E_STEP_FUNCTION_ARN&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stepFunctionDefinition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nx"&gt;describeStepFunctionResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;definition&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stepFunctionRoleARN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;describeStepFunctionResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roleArn&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sfnLocalClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SFNClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`http://localhost:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;getMappedPort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8083&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createLocalSFNResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sfnLocalClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CreateStateMachineCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;definition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stepFunctionDefinition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;BranchingLogic&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;roleArn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stepFunctionRoleARN&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;startLocalSFNExecutionResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sfnLocalClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StartExecutionCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;stateMachineArn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;
        &lt;span class="nx"&gt;createLocalSFNResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stateMachineArn&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;#ErrorPath`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;John&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Doe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_REGION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BRANCHING_LOGIC_E2E_DATA_TABLE_NAME&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;toHaveItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;PK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`USER#&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;startLocalSFNExecutionResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;executionArn&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;PK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`USER#&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;startLocalSFNExecutionResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;executionArn&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;John&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Doe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;backgroundCheck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ERROR&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;/p&gt;

&lt;h2&gt;
  
  
  By decomposing AWS Step Function Tasks
&lt;/h2&gt;

&lt;p&gt;Switching gears from a somewhat complex end-to-end tests world, there exists another technique I would like to highlight.&lt;/p&gt;

&lt;p&gt;I first saw this method of testing various AWS Step Function tasks while browsing code written by my colleagues at &lt;a href="http://stedi.com/" rel="noopener noreferrer"&gt;Stedi&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;My friend &lt;a href="https://twitter.com/Grundlefleck" rel="noopener noreferrer"&gt;Graham Allan&lt;/a&gt; wrote about it &lt;a href="https://grundlefleck.github.io/2022/01/12/how-using-the-same-programming-language-for-iac-made-a-step-function-testable.html" rel="noopener noreferrer"&gt;on his blog here&lt;/a&gt;. Check it out!&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing Pass state transformations
&lt;/h2&gt;

&lt;p&gt;Have you ever spent way too much time trying to transform data via the &lt;code&gt;Pass&lt;/code&gt; state to the shape you need it to be? I know I have.&lt;/p&gt;

&lt;p&gt;As great as the AWS console and the &lt;a href="https://aws.amazon.com/about-aws/whats-new/2021/04/aws-step-functions-adds-new-data-flow-simulator-for-modelling-input-and-output-processing/" rel="noopener noreferrer"&gt;AWS Step Functions data flow simulator&lt;/a&gt; is, I find the feedback loop of a test case unbeatable.&lt;/p&gt;

&lt;p&gt;So, how can we reliably extract those &lt;code&gt;Pass&lt;/code&gt; states from our AWS CDK code and test them? Here is my solution.&lt;/p&gt;

&lt;p&gt;Here is our sample &lt;a href="https://docs.aws.amazon.com/cdk/v2/guide/constructs.html" rel="noopener noreferrer"&gt;&lt;em&gt;Construct&lt;/em&gt;&lt;/a&gt; definition. It contains the &lt;code&gt;transformDataStep&lt;/code&gt; that we would like to test.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="c1"&gt;// pass-states-integration.ts&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PassStatesIntegration&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Construct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Construct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transformDataStep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws_stepfunctions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Pass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TransformDataStep&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;aws_stepfunctions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;JsonPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;States.Format('{} {}', $.firstName, $.lastName)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
          &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// You can imagine the definition being a bit more complex.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stepFunctionDefinition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;transformDataStep&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stepFunction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws_stepfunctions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;StateMachine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;StepFunction&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;definition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stepFunctionDefinition&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We will be using the &lt;em&gt;aws-stepfunctions-local&lt;/em&gt;, so the test setup looks very similar to the previous code snippets in this article.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="c1"&gt;// pass-states-integration.test.ts&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;StartedTestContainer&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;beforeAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GenericContainer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;amazon/aws-stepfunctions-local&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withExposedPorts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8083&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withEnv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AWS_DEFAULT_REGION&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_REGION&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;afterAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;To test the &lt;code&gt;transformDataStep&lt;/code&gt;, we must retrieve the &lt;code&gt;transformDataStep&lt;/code&gt; &lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/concepts-amazon-states-language.html" rel="noopener noreferrer"&gt;ASL&lt;/a&gt; definition. We can do this in two ways.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Use the &lt;code&gt;DescribeStateMachine&lt;/code&gt; API call, like we did in the &lt;em&gt;Step Functions with branching logic&lt;/em&gt; section.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Make the &lt;code&gt;transformDataStep&lt;/code&gt; a publicly accessible property on the &lt;code&gt;PassStatesIntegration&lt;/code&gt; construct.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I'm going to go with option number two since we have already seen option number one in action.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="err"&gt;

&lt;/span&gt;// pass-states-integration.ts
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="p"&gt;export class PassStatesIntegration extends Construct {
&lt;/span&gt;&lt;span class="gi"&gt;+ public transformDataStep: aws_stepfunctions.Pass;
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;  constructor(scope: Construct, id: string) {
    super(scope, id);
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-   const transformDataStep = new aws_stepfunctions.Pass(
&lt;/span&gt;&lt;span class="gi"&gt;+   this.transformDataStep = new aws_stepfunctions.Pass(
&lt;/span&gt;      this,
      "TransformIncomingDataStep",
      {
        parameters: {
          payload: aws_stepfunctions.JsonPath.stringAt(
            "States.Format('{} {}', $.firstName, $.lastName)"
          )
        }
      }
    );
&lt;span class="err"&gt;
&lt;/span&gt;    // You can imagine the definition being a bit more complex.
&lt;span class="gd"&gt;-   const stepFunctionDefinition = transformDataStep;
&lt;/span&gt;&lt;span class="gi"&gt;+   const stepFunctionDefinition = this.transformDataStep;
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;    const stepFunction = new aws_stepfunctions.StateMachine(
      this,
      "StepFunction",
      {
        definition: stepFunctionDefinition
      }
    );
  }
}
&lt;span class="err"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;With the &lt;code&gt;transformDataStep&lt;/code&gt; made public, the test body would look as follows.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="c1"&gt;// pass-states-integration.test.ts&lt;/span&gt;

&lt;span class="c1"&gt;// Test setup ...&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Handles the failure of the BackgroundCheck step&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Stack&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;construct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PassStatesIntegration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PassStatesIntegration&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transformDataStepDefinition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;construct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transformDataStep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toStateJson&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stepFunctionDefinition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;StartAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TransformDataStep&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;States&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;TransformDataStep&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;transformDataStepDefinition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;End&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sfnLocalClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SFNClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`http://localhost:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;getMappedPort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8083&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createLocalSFNResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sfnLocalClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CreateStateMachineCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;definition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stepFunctionDefinition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PassStates&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;roleArn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;arn:aws:iam::012345678901:role/DummyRole&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;startLocalSFNExecutionResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sfnLocalClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StartExecutionCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;stateMachineArn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;createLocalSFNResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stateMachineArn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;John&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Doe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;waitFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getExecutionHistoryResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sfnLocalClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GetExecutionHistoryCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;executionArn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;startLocalSFNExecutionResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;executionArn&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;successState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;getExecutionHistoryResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ExecutionSucceeded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;successState&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;executionSucceededEventDetails&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;John Doe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The ASL for the &lt;code&gt;TransformDataStep&lt;/code&gt; is extracted via the &lt;code&gt;toStateJson&lt;/code&gt; method. The rest of the test is similar to how we did it previously. The only difference is how we make the assertion.&lt;/p&gt;

&lt;p&gt;This testing method is analogous to the one described by the &lt;em&gt;By decomposing AWS Step Function Tasks&lt;/em&gt; section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing words
&lt;/h2&gt;

&lt;p&gt;I hope you find this blog post helpful regarding AWS Step Functions testing.&lt;/p&gt;

&lt;p&gt;Consider following me on Twitter for more serverless content - &lt;a href="https://twitter.com/wm_matuszewski" rel="noopener noreferrer"&gt;@wm_matuszewski&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thank you for your valuable time.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>typescript</category>
      <category>cdk</category>
    </item>
  </channel>
</rss>
