<?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>DEV Community: Matt Morgan</title>
    <description>The latest articles on DEV Community by Matt Morgan (@elthrasher).</description>
    <link>https://dev.to/elthrasher</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%2F248623%2F3bc01c08-2153-41ed-8d52-c8845f3a917a.jpeg</url>
      <title>DEV Community: Matt Morgan</title>
      <link>https://dev.to/elthrasher</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/elthrasher"/>
    <language>en</language>
    <item>
      <title>Create Stateful Serverless Workflows with AWS Step Functions and JSONata</title>
      <dc:creator>Matt Morgan</dc:creator>
      <pubDate>Mon, 31 Mar 2025 13:44:34 +0000</pubDate>
      <link>https://dev.to/aws-builders/create-stateful-serverless-workflows-with-aws-step-functions-and-jsonata-2oe3</link>
      <guid>https://dev.to/aws-builders/create-stateful-serverless-workflows-with-aws-step-functions-and-jsonata-2oe3</guid>
      <description>&lt;p&gt;As an avid user of &lt;a href="https://aws.amazon.com/step-functions/" rel="noopener noreferrer"&gt;AWS Step Functions&lt;/a&gt;, I've been pleased by several excellent releases over the past few years, including &lt;a href="https://aws.amazon.com/blogs/aws/step-functions-distributed-map-a-serverless-solution-for-large-scale-parallel-data-processing/" rel="noopener noreferrer"&gt;Distributed Map&lt;/a&gt;, &lt;a href="https://docs.aws.amazon.com/wellarchitected/latest/serverless-applications-lens/step-functions-workflows.html" rel="noopener noreferrer"&gt;Express Workflows&lt;/a&gt;, &lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/intrinsic-functions.html" rel="noopener noreferrer"&gt;Intrinsic functions&lt;/a&gt;, &lt;a href="https://aws.amazon.com/blogs/compute/accelerating-workflow-development-with-the-teststate-api-in-aws-step-functions/" rel="noopener noreferrer"&gt;TestState&lt;/a&gt;, &lt;a href="https://aws.amazon.com/blogs/compute/introducing-aws-step-functions-redrive-a-new-way-to-restart-workflows/" rel="noopener noreferrer"&gt;redrive&lt;/a&gt;, &lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/integrate-services.html" rel="noopener noreferrer"&gt;service integrations&lt;/a&gt;, and so many others. Those are all fantastic releases, but in my humble opinion, none of them are as big of a deal as the &lt;a href="https://aws.amazon.com/blogs/compute/simplifying-developer-experience-with-variables-and-jsonata-in-aws-step-functions/" rel="noopener noreferrer"&gt;introduction of JSONata expressions&lt;/a&gt;. AWS announced this game-changing feature a week before re:Invent 2024.&lt;/p&gt;

&lt;h2&gt;
  
  
  JSONata
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://jsonata.org/" rel="noopener noreferrer"&gt;JSONata&lt;/a&gt; is a language for querying and transforming JSON data developed by &lt;a href="https://www.linkedin.com/in/andrew-coleman-6166245a/" rel="noopener noreferrer"&gt;Andrew Coleman&lt;/a&gt; of IBM. I had not heard of or worked with JSONata before its inclusion in Step Functions, but it seems like a perfect fit.&lt;/p&gt;

&lt;p&gt;I haven't used it yet outside of Step Functions, but JSONata seems quite remarkable in its own right, and I can imagine using it in other contexts. Check out the docs at &lt;a href="https://docs.jsonata.org/overview" rel="noopener noreferrer"&gt;https://docs.jsonata.org/overview&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  JSONata in Step Functions
&lt;/h2&gt;

&lt;p&gt;JSONata simplifies how data is passed from step to step over the existing JSONPath approach. This is a very good thing because the developer experience of Step Functions hasn't always been the greatest. Even frequent users of this service get lost in the flow of &lt;code&gt;InputPath&lt;/code&gt;, &lt;code&gt;Parameters&lt;/code&gt;, &lt;code&gt;Task Result&lt;/code&gt;, &lt;code&gt;ResultSelector&lt;/code&gt;, &lt;code&gt;ResultPath&lt;/code&gt;, and &lt;code&gt;OutputPath&lt;/code&gt;. Wait, which one do I use to add a value to the state input? AWS released the &lt;a href="https://us-east-1.console.aws.amazon.com/states/home?region=us-east-1#/simulator" rel="noopener noreferrer"&gt;Data flow simulator&lt;/a&gt; to help navigate this flow, but in some ways, all that did was drive home the point that this can be complex and hard to use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Passing Data
&lt;/h2&gt;

&lt;p&gt;When we use JSONata with Step Functions, we do away with the above flow and instead work with &lt;code&gt;Parameters&lt;/code&gt;, &lt;code&gt;Output&lt;/code&gt;, and one new directive, &lt;code&gt;Assign&lt;/code&gt;. Because JSONata lets us create complex expressions, we can use &lt;code&gt;Output&lt;/code&gt; to achieve what used to require &lt;code&gt;ResultSelector&lt;/code&gt;, &lt;code&gt;ResultPath&lt;/code&gt;, and &lt;code&gt;OutputPath&lt;/code&gt;, a significant simplification.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Assign Directive
&lt;/h2&gt;

&lt;p&gt;As good as that is, &lt;code&gt;Assign&lt;/code&gt; pushes JSONata support to the top of an impressive feature set. &lt;code&gt;Assign&lt;/code&gt; lets us create &lt;strong&gt;scoped variables&lt;/strong&gt; that can be read in later steps! If you've ever had to pass a value from step 2 to step 7 to be read, you're probably out of your seat and cheering. Note that these variables will be scoped when you use &lt;code&gt;Assign&lt;/code&gt; in steps within a Map or Parallel state. The Map or Parallel state can use &lt;code&gt;Assign&lt;/code&gt; to make values available later in the state machine execution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparing JSONPath with JSONata
&lt;/h2&gt;

&lt;p&gt;Let's look at a simple example. I refactored the state machine I used in this &lt;a href="https://dev.to/elthrasher/exploring-aws-cdk-step-functions-1d1e"&gt;2019 blogpost&lt;/a&gt;. It demonstrates a simple workflow that moves through different states, each implemented by a Lambda Function.&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%2Frtuteajy82d8h1vys35b.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%2Frtuteajy82d8h1vys35b.png" alt="State Machine Diagram"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Five years ago, we had no choice but to pass data along from state to state. We could refer back to the original input using &lt;code&gt;$$.Execution.Input&lt;/code&gt;, but anything derived by a step must be passed from step to step (or stored in a database). So while my simple state machine example works fine, it introduces an opportunity for weird bugs. I'm passing the CaseId from step to step. It should be understood I'm working on the same case all the way through. What if I pass "001" to the "Assign Case" step and then return a case number of "ABC123"? My step definitions may create a "garbage-in, garbage-out" machine.&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;"Next"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Assign Case"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"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;"Task"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"OutputPath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$.Payload"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&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:states:::lambda:invoke"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Parameters"&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;"FunctionName"&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:lambda:us-east-1:123456890:function:assign-case"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Payload.$"&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;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;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;"Next"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Work On Case"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"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;"Task"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"OutputPath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$.Payload"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&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:states:::lambda:invoke"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Parameters"&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;"FunctionName"&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:lambda:us-east-1:123456890:function:assign-case"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Payload.$"&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;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;It's easy enough to convert these to JSONata steps and start seeing the value of &lt;code&gt;Assign&lt;/code&gt;. We set &lt;code&gt;QueryLanguage&lt;/code&gt; to &lt;code&gt;JSONata&lt;/code&gt; and now we can use the &lt;code&gt;Assign&lt;/code&gt; directive.&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;"QueryLanguage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JSONata"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Next"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Assign Case"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"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;"Task"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Arguments"&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;"FunctionName"&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:lambda:us-east-1:1234567890:function:open-case"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Payload"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{% $states.input %}"&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;"Assign"&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;"caseId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{% $states.input.inputCaseID %}"&lt;/span&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;"{% $states.result.Payload.message %}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{% $states.result.Payload.status %}"&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;"Resource"&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:states:::lambda:invoke"&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;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;"QueryLanguage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JSONata"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Next"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Work On Case"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"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;"Task"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Arguments"&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;"FunctionName"&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:lambda:us-east-1:1234567890:function:assign-case"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Payload"&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;"caseId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{% $caseId %}"&lt;/span&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;"{% $message %}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{% $status %}"&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;"Assign"&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;"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;"{% $states.result.Payload.message %}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{% $states.result.Payload.status %}"&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;"Resource"&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:states:::lambda:invoke"&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;Assign Case&lt;/code&gt; step now sets the &lt;code&gt;message&lt;/code&gt; and &lt;code&gt;status&lt;/code&gt; properties, but the &lt;code&gt;caseId&lt;/code&gt; remains constant throughout the state machine execution.&lt;/p&gt;

&lt;h2&gt;
  
  
  JSONata in the AWS Console
&lt;/h2&gt;

&lt;p&gt;Still not convinced? The console experience should close the deal for you. We get a new tab for every JSONata step: "Variables" that shows us any variables the step used and any it assigned.&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%2F0gbaysy0679ze6d9mgc8.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%2F0gbaysy0679ze6d9mgc8.png" alt="Variables tab"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But that's not all. We also get to view the history of each variable and the assignments made during the state machine execution.&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%2F3w5khtacp2ko1oefvovn.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%2F3w5khtacp2ko1oefvovn.png" alt="Variable history"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is impressively useful for debugging complex workflows. I can't think of another tool that records the state of each variable and lets me view it after execution. Just think of how many &lt;code&gt;console.log&lt;/code&gt; statements this will save you!&lt;/p&gt;

&lt;h2&gt;
  
  
  Migrating to JSONata
&lt;/h2&gt;

&lt;p&gt;Making the switch is relatively easy, although more complex workflows require more effort. I have had success doing this by hand. I spent a few hours converting a very complex and hard-to-debug workflow to use JSONata and immediately reaped the benefits of greater observability and maintainability. Although that isn't code I can share, readers might be interested in the &lt;a href="https://github.com/elthrasher/cdk-step-functions-example/commit/62898ed5a497fc3218732ff75ceaa07d97d517ab#diff-9d6811f2ef86e016746d45f7dd5c4276315f114f10df30fcc5bee70495e632c3L73" rel="noopener noreferrer"&gt;diff&lt;/a&gt; that upgraded my &lt;a href="https://github.com/elthrasher/cdk-step-functions-example" rel="noopener noreferrer"&gt;sample project&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But we don't need to do it by hand! The console now includes a tool we can use to help with the migration.&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%2Fu0n4s1kfysm42durbgtc.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%2Fu0n4s1kfysm42durbgtc.png" alt="Convert to JSONata"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You get completion right in the tool.&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%2F4v4byigdajqw9o56vqm0.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%2F4v4byigdajqw9o56vqm0.png" alt="Completion"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also build out powerful &lt;code&gt;Assign&lt;/code&gt; expressions here.&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%2Fzq6t3x71th0yuaj712rm.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%2Fzq6t3x71th0yuaj712rm.png" alt="Assign"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's possible to mix JSONPath and JSONata expressions in the same state machine execution. I wouldn't intentionally do this, but it's useful to be able to refactor one state at a time and test to ensure the workflow is still functional.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Else Does JSONata Offer?
&lt;/h2&gt;

&lt;p&gt;I feel like we've barely scratched the surface. &lt;a href="https://docs.jsonata.org/predicate" rel="noopener noreferrer"&gt;Predicate queries&lt;/a&gt; look very powerful, and now we can do this in an &lt;code&gt;Assign&lt;/code&gt; step instead of writing an ES6 &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce" rel="noopener noreferrer"&gt;reduce&lt;/a&gt; callback. JSONata also features an impressive list of built-in &lt;a href="https://docs.jsonata.org/string-functions" rel="noopener noreferrer"&gt;functions&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  One Last Thing
&lt;/h2&gt;

&lt;p&gt;Think this is cool, but not fully sold on Step Functions? Let me &lt;a href="https://dev.to/aws-builders/avoiding-the-serverless-workflow-antipattern-2ba1"&gt;convince&lt;/a&gt; you.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>jsonata</category>
      <category>stepfunctions</category>
    </item>
    <item>
      <title>Presenting AWS Speakers Directory, an AI Hackathon Project</title>
      <dc:creator>Matt Morgan</dc:creator>
      <pubDate>Thu, 29 Jun 2023 11:37:19 +0000</pubDate>
      <link>https://dev.to/aws-builders/presenting-aws-speakers-directory-an-ai-hackathon-project-19je</link>
      <guid>https://dev.to/aws-builders/presenting-aws-speakers-directory-an-ai-hackathon-project-19je</guid>
      <description>&lt;h2&gt;
  
  
  Related Posts
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/aws-builders/amplify-sdk-for-flutter-developer-experience-and-challenges-in-a-hackathon-2e15"&gt;Amplify SDK for Flutter - Developer experience and challenges in a hackathon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/aws-builders/a-real-project-with-codecatalyst-our-hackathon-gave-us-a-good-insight-into-what-works-and-what-doesnt-1e79"&gt;A real project with CodeCatalyst - Our hackathon gave us a good insight into what works and what doesn't&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/aws-builders/appsync-merged-api-our-real-project-experience-as-part-of-our-hackathon-2m96"&gt;AppSync Merged API – Our real project experience as part of our hackathon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/aws-builders/community-speakers-directory-adding-ai-functionality-3427"&gt;AWS Speakers Directory: Adding AI Functionality&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Community Speakers Directory
&lt;/h2&gt;

&lt;p&gt;Hackathons are an interesting phenomenon. We work hard for pay all day and then we continue working hard for free in the evenings and weekends instead of watching TV, hanging with friends and family, pursuing other hobbies and crafts, and sometimes in lieu of sleeping! Why? We each have our own motiviations, but for me, the temptation of collaborating with brilliant folks like Danielle, Johannes, and Julian is too powerful to ignore. A secondary, yet still compelling, motivation is to get hands-on experience with technologies heretofore missing from my toolbox. A distant third is whatever prizes may be offered.&lt;/p&gt;

&lt;p&gt;Johannes picked the venue and organized the team like a seasoned professional planning a heist. The job was to create an AI-powered tool using the &lt;a href="https://huggingface.co/docs/transformers/main/transformers_agents"&gt;Transformers Agent&lt;/a&gt; framework from &lt;a href="https://huggingface.co/"&gt;Hugging Face&lt;/a&gt;. This was a daunting task as none of the four of us have a great deal of experience with AI or ML models, but we did have a pretty good concept of what we could build, courtesy of Johannes. We would build a directory of speakers that can present at community events and use AI to enhance the content and provide a recommendation model to better match talks to events.&lt;/p&gt;

&lt;p&gt;We further challenged ourselves by agreeing to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Leverage &lt;a href="https://codecatalyst.aws/"&gt;CodeCatalyst&lt;/a&gt; as a code repository, collaboration tool, and CI/CD pipeline.&lt;/li&gt;
&lt;li&gt;Build our website using &lt;a href="https://flutter.dev/"&gt;Flutter&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Build out the backend using &lt;a href="https://docs.aws.amazon.com/appsync/index.html"&gt;AppSync's&lt;/a&gt; new &lt;a href="https://docs.aws.amazon.com/appsync/latest/devguide/merged-api.html"&gt;Merged API&lt;/a&gt; capability.&lt;/li&gt;
&lt;li&gt;Deploy our application to sandbox, test, and production environments in a &lt;a href="https://github.com/aws-community-projects/aws-organization-for-devs"&gt;multi-account AWS organization&lt;/a&gt; managed by &lt;a href="https://aws.amazon.com/controltower/"&gt;Control Tower&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Write our infrastructure-as-code using &lt;a href="https://aws.amazon.com/cdk/"&gt;AWS CDK&lt;/a&gt; in a multi-stack application.&lt;/li&gt;
&lt;li&gt;Secure our application using &lt;a href="https://aws.amazon.com/pm/cognito"&gt;Amazon Cognito&lt;/a&gt; and the &lt;a href="https://aws.amazon.com/amplify"&gt;Amplify&lt;/a&gt; SDK.&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://aws.amazon.com/blogs/compute/creating-a-single-table-design-with-amazon-dynamodb/"&gt;single table&lt;/a&gt; &lt;a href="https://aws.amazon.com/dynamodb"&gt;DynamoDB&lt;/a&gt; design.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We each brought expertise to the project that was indespensible to its success. Of course Johannes had the plan and brought the group together. He also organized meetings and make sure we were on task without pushing too hard (hey, it's just a hackathon). But he also wrote a lot of the frontend code and a large part of the AppSync resolvers. Amazing!&lt;/p&gt;

&lt;p&gt;Danielle focused on how we can leverage Transformers Agent in Python Lambda functions. She even recorded a video explaining some of the fundamentals so that Johannes and I were able to go from zero to contributing in no time flat. Safe to say we were at risk of not meeting the requirements of the challenge without Danielle's contribution.&lt;/p&gt;

&lt;p&gt;Julian designed our overall CDK architecture, implemented the Cognito flows, did a lot of additional Flutter work and also built out the Merge API with L1 constructs, a feat that deserves a blogpost of its own.&lt;/p&gt;

&lt;p&gt;My contribution was to provide the AWS Organization, roles that hopefully didn't get in the way &lt;em&gt;too&lt;/em&gt; much, and a lot of CDK optimization and fine-tuning. I designed our DynamoDB data model, wrote a bunch of the queries, and got to do my part on the AI bits.&lt;/p&gt;

&lt;p&gt;It wasn't long into our project that &lt;a href="https://aws.amazon.com/developer/community/heroes/johannes-koch/?did=dh_card&amp;amp;trk=dh_card"&gt;Johannes&lt;/a&gt; and &lt;a href="https://aws.amazon.com/developer/community/heroes/danielle-heberling/?did=dh_card&amp;amp;trk=dh_card"&gt;Danielle&lt;/a&gt; were honored as AWS Heroes. The hackathon organizers were kind enough to not kick them out of this Community Builders-organized event. Big congrats to Danielle and Johannes!&lt;/p&gt;

&lt;p&gt;What follows is a deeper dive into some of the challenges we faced along the way and how we overcame them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Built
&lt;/h2&gt;

&lt;p&gt;Our ambition is to build the &lt;a href="https://speakers.awscommunitybuilders.org/"&gt;AWS User Group Speaker Directory&lt;/a&gt;, making it easy to find speakers for remote or in-person events for AWS User Groups. This tool is powered by &lt;a href="https://huggingface.co/docs/transformers/main/transformers_agents"&gt;Transformers Agent&lt;/a&gt; to enhance talk profiles, normalize tagging, and make matching speakers and talks to events simple. The Speakers Directory will reduce the burden of organizing events and raise up more voices in the community.&lt;/p&gt;

&lt;p&gt;Our application consists of a web layer, a GraphQL API powered by &lt;a href="https://aws.amazon.com/pm/appsync"&gt;AppSync&lt;/a&gt; and a &lt;a href="https://aws.amazon.com/dynamodb"&gt;DynamoDB&lt;/a&gt; table. This part of the application handles user flows, authentication and authorization with &lt;a href="https://aws.amazon.com/cognito/"&gt;Cognito&lt;/a&gt;, and storage of event and talk data. Web assets (our Flutter application) are stored in S3 and served over &lt;a href="https://aws.amazon.com/cloudfront/"&gt;CloudFront&lt;/a&gt;. We have a custom domain managed by &lt;a href="https://aws.amazon.com/route53/"&gt;Route 53&lt;/a&gt;. All of this is composed with the &lt;a href="https://aws.amazon.com/cdk/"&gt;AWS Cloud Development Kit&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Zhe1KIOv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uuyf3wzy6gbpq8w2i9uw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Zhe1KIOv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uuyf3wzy6gbpq8w2i9uw.png" alt="high-level architecture" width="800" height="650"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  CodeCatalyst
&lt;/h2&gt;

&lt;p&gt;Johannes has been a big &lt;a href="https://lockhead.info/index.php/2023/04/20/amazon-codecatalyst-reaches-ga-status-and-becomes-available-for-general-use/"&gt;booster&lt;/a&gt; of CodeCatalyst, so it was no surprise he wanted to use it for this project. One cool thing I learned pretty early on is that it supports GitHub Actions out of the box. You can just use them. This is a pretty great feature. It was also nice how easy it was to set up with a deployment role and the tool automatically generated an appropriate role for using tools like CDK or SAM and asked if I wanted to use it. As far as I'm aware, this is the first time AWS has gone out of their way to generate a role that could be seen as suitable for such a CI/CD pipeline. That alone is worth the price of admission as I'm sure many long hours have been spent trying to design the ideal role and many pipelines wind up with permissions too broad!&lt;/p&gt;

&lt;p&gt;Other than that, the user experience of CodeCatalyst still leaves something to be desired. I can comment on a pull request, but the author of the PR cannot reply to my comment. Navigating between pipelines, issues and repositories feels a bit sluggish and it's just too many clicks to open a pull request. I do like the idea of an integrated tool that plays well with AWS. I can imagine getting a lot of value from some deeper integrations, such as EventBridge rules firing on a PR. I don't know if that's on the team's roadmap, but it probably should be!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4aj7YliR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0lvguxat1j7lyuy8uq25.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4aj7YliR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0lvguxat1j7lyuy8uq25.png" alt="CodeCatalyst Pipeline" width="800" height="855"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Flutter
&lt;/h2&gt;

&lt;p&gt;I think Johannes is also the reason we picked Flutter for our web tier and Julian also seemed to have prior experience. I've known of Dart for years, but never used it. We had some rough going in the early days as we tried to shoehorn the Flutter build into a CDK asset bundle using Docker. The dang thing went out of memory and drove me crazy for several days before I gave up and went for a build outside of Docker. In the end, it wasn't a Flutter problem, but a Docker problem and some of the libs not working with my M1 Mac. The next challenge was getting CDK to correctly cache builds so it wouldn't make us wait out a Flutter build every time we wanted to deploy the application. It took some doing, but ultimately cleaning up the prior build artifacts just before starting a new build proved to be the ticket and we got asset hashing working well.&lt;/p&gt;

&lt;p&gt;Beyond that, Flutter's component-based system let Julian and Johannes build quickly, the Amplify support seemed good and we have the possibility of targeting other platforms.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AXyaMKpq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y9efpcy2hw16k6rc5fbj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AXyaMKpq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y9efpcy2hw16k6rc5fbj.png" alt="UI architecture" width="800" height="650"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  AppSync / Merged API
&lt;/h2&gt;

&lt;p&gt;AppSync is a technology I haven't used much, but the rest of the team seemed familiar. We implemented JavaScript resolvers (transpiled from TypeScript). It's a bit funny to me how like and unlike Lambda this approach is, but overall it worked pretty well. Using TypeScript let us have more reusable code than we could've with plain js and no build process since (unlike Lambda) JS resolvers must be a single .js or .mjs file.&lt;/p&gt;

&lt;p&gt;As a bit of a GraphQL noob, I found JS resolvers to be very easy to use and are a good mental model for working with this kind of technology.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Ho9W4x82--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2jhunr5vp1aztliqmivz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ho9W4x82--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2jhunr5vp1aztliqmivz.png" alt="AppSync / Merged API" width="800" height="650"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS Organization
&lt;/h2&gt;

&lt;p&gt;I had set up a &lt;a href="https://github.com/aws-community-projects/aws-organization-for-devs"&gt;Community Builders AWS Organization&lt;/a&gt; recently using &lt;a href="https://superwerker.cloud/"&gt;superwerker&lt;/a&gt;. My intent is for this organization to show the best practices for setting up a multi-account organization with &lt;a href="https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_ous.html"&gt;Organizational Units&lt;/a&gt; representing sandbox, test and production environments, scoped developer roles and limited production access. I think the developer roles need a little work, but the group was able to be productive in this environment, so that's a win.&lt;/p&gt;

&lt;h2&gt;
  
  
  Image Generation
&lt;/h2&gt;

&lt;p&gt;We leverage Transformers Agent to generate an image to accompany a talk. This was a bit tricky to get going because the example I was working from uses &lt;a href="https://github.com/freddyaboulton/gradio-tools"&gt;gradio_tools&lt;/a&gt; which seems like a great library for LLM agents using Python, however it requires Python 3.8 or greater and the &lt;a href="https://hub.docker.com/r/huggingface/transformers-pytorch-cpu"&gt;Docker image&lt;/a&gt; we were using was last built a year ago on Ubuntu 18 which uses Python 3.6. This is a very frustrating class of problem to have and I spent about a day trying to find a substitute image to work with or (ugh) upgrade Python in our base image. Ultimately I rebuilt the same image using Ubuntu 22 and Python 3.9 and that worked out for us, but it was a bit of a journey to get there.&lt;/p&gt;

&lt;p&gt;Once we had an environment that allowed gradio_tools, the next challenge was that our image had ballooned to 5 GB with all the necessary dependencies, greatly slowly down development and deployment. And what was worse was our Lambda function would go out of memory before completing. We discussed moving the function to Fargate, a detour we weren't looking forward to. Johannes noted that Transformers Agent supports &lt;a href="https://huggingface.co/docs/transformers/main/main_classes/agent#transformers.RemoteTool"&gt;remote execution&lt;/a&gt; and I was able to get that working. This makes Lambda an ideal runtime, which definitely matches with our sensibilities!&lt;/p&gt;

&lt;p&gt;We used this capability to generate an image based on the talk abstract after a new talk is created on the site. Because the image generation can take about a minute, we leveraged &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Streams.html"&gt;DynamoDB Streams&lt;/a&gt; to trigger an asynchronous workflow to generate the image and add it to a talk.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2sod5xhc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/17r12g1qjbky4inmh6h7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2sod5xhc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/17r12g1qjbky4inmh6h7.png" alt="AI Architecture" width="800" height="652"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  AI-assisted Tagging and Recommendations
&lt;/h2&gt;

&lt;p&gt;We found another, perhaps a more practical, use of Transformers Agent, the ability to pipeline a model like &lt;a href="https://huggingface.co/docs/transformers/v4.30.0/en/model_doc/bart"&gt;BART&lt;/a&gt; which can then be used to score labels as to whether or not they may apply to a text sequence. We reasoned this could be used to help classify our talks by assigning tags that fit them. The tags are then fed into a recommendation engine that will help determine whether or not a talk is appropraite for a specific event.&lt;/p&gt;

&lt;p&gt;Our application allows both talks and events to receive tags when they are created. This establishes a baseline of tags. We might see values like "serverless", "devops", and "SAM". Given the baseline of common tags, we can feed them to the classifier and score them. Tags with high scores will be added to the talk or event. Then, if an event organizer wants to get a list of possible talks for the event, we have a real basis for comparison. Talks with like tags are fetched and ranked by number of matched tags.&lt;/p&gt;

&lt;p&gt;Using &lt;code&gt;pipeline&lt;/code&gt; made it fairly easy to get tag recommendations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;oracle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"facebook/bart-large-mnli"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;tags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_tags&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;candidate_labels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"tagName"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Records"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;"dynamodb"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;"NewImage"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# Use both the title and the description of the talk to identify tags
&lt;/span&gt;    &lt;span class="n"&gt;identifiedTags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;oracle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;candidate_labels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;candidate_labels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;labels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;identifiedTags&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"labels"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;scores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;identifiedTags&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"scores"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;selectedTags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;save_tags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;selectedTags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&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="s"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;identifiedTags&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code responds to a DynamoDB Stream, gets a list of tags active in the system, then takes the title and description of the newly-added talk and scores the tags against the text we fed it. Based on our (limited) testing, a score of 0.3 or higher seemed to work out pretty well and we'd then save any of those tags to the talk.&lt;/p&gt;

&lt;p&gt;The logic behind recommendations is fairly simple. Based on the tags assigned to an event, an organizer can get a list of recommended talks. We use the event tags to look for talks with similar tags and score them based on the number of matches.&lt;/p&gt;

&lt;h2&gt;
  
  
  DynamoDB Single Table Design
&lt;/h2&gt;

&lt;p&gt;We decided to use the &lt;a href="https://docs.aws.amazon.com/prescriptive-guidance/latest/dynamodb-data-modeling/welcome.html"&gt;recommended approach&lt;/a&gt; of a single-table design for our database. This is something I have prior experience with, but I feel I've yet to master. However there is a process to follow! To begin with, it's best to craft an ERD so that we can understand what our entities are and how they relate to one another.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FMCPkrja--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/utqfwxgy13ofe7kgw9po.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FMCPkrja--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/utqfwxgy13ofe7kgw9po.png" alt="Speakers Directory ERD" width="800" height="618"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From there, we should document expected access patterns and devise keys based on those.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Nr2jHG8H--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jlxw8ucpv2n81h9irsqz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Nr2jHG8H--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jlxw8ucpv2n81h9irsqz.png" alt="Talks Access Patterns" width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's inevitable that our expectations won't match reality 100% and when that happens, it's important to return to fundamentals and rework the design. I'd say that our keys changed pretty dramatically a handful of times over the course of the project as new access patterns emerged.&lt;/p&gt;

&lt;p&gt;One thing that's challenging is when we realize we need to get all of a kind of thing. How should we do that? A scan with a filter condition? Only if absolutely necessary!&lt;/p&gt;

&lt;p&gt;Looking at how we modeled Tags, they are part of an overloaded partition key so that Tags are separate entities assigned to our main domain objects of Talks and Events. Querying a Talk entity with its partition key only will also return its tag entities. Then there's a &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.html"&gt;Global Secondary Index&lt;/a&gt; that lets us query by tag and get relevant Talks and Events.&lt;/p&gt;

&lt;p&gt;This approach works well when we want to get a list of Talks that have the &lt;code&gt;serverless&lt;/code&gt; tag associated with them. But when it came to implementing our recommendation engine, we wanted to be able to provide the classifier with a unique list of existing tags. The access pattern doesn't support this! In fact, the only way we could've implemented this with the current design is to do a full table scan and then filter down to a unique list of tags. I know it's a hackathon, but we just couldn't be satisfied with an approach like that.&lt;/p&gt;

&lt;p&gt;So we introduced a new entity with a partition key of just &lt;code&gt;tag&lt;/code&gt;. A paginated query on that key will return all the tags. We implemented a Dynamo Stream such that whenever we store a new talk, any tags that were added to it will be evaluated and added to our tags entity. That code, written in TypeScript, uses several great features of the DynamoDB API.&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;command&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;UpdateCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;ExpressionAttributeNames&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="s1"&gt;#quantity&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;quantity&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;#tagName&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;tagName&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;#_et&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;_et&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;#_ct&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;_ct&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;#_md&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;_md&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;ExpressionAttributeValues&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="s1"&gt;:one&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:tagName&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tagName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:timestamp&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="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:zero&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:_et&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;TAG_COUNT&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;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;pk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tag&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;sk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`tag#&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tagName&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;TableName&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;UpdateExpression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`SET #quantity = :one + if_not_exists(#quantity, :zero),
                          #tagName = :tagName,
                          #_et = :_et,
                          #_ct = if_not_exists(#_ct, :timestamp),
                          #_md = :timestamp
                      `&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="nx"&gt;docClient&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="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Although it wasn't an explicit requirement, it seemed like a good idea to be able to track how many times each tag is used. Thanks to the &lt;code&gt;:one + if_not_exists(#quantity, :zero)&lt;/code&gt; expression, we can either increment the count of an existing tag or set the count of a new tag to 1. Although this is an Update command, DynamoDB APIs are idempotent so if the key doesn't exist, a new item will be created. &lt;code&gt;if_not_exists&lt;/code&gt; is also used here to ensire the creation date (&lt;code&gt;_ct&lt;/code&gt;) is only set if the item is new.&lt;/p&gt;

&lt;p&gt;These tag summaries (entity type &lt;code&gt;TAG_COUNT&lt;/code&gt;) are later queried for use in the summarization ML operation. That code is written in Python. It simply queries on the &lt;code&gt;tag&lt;/code&gt; partition key and loops to make sure we get all the items on that key as DynamoDB queries are paginated.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_tags&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;ExpressionAttributeNames&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"#pk"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"pk"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;ExpressionAttributeValues&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;":tag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"tag"&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
        &lt;span class="n"&gt;KeyConditionExpression&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"#pk = :tag"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;TableName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;tags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Items"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# Need the loop to get all tags.
&lt;/span&gt;    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="s"&gt;"LastEvaluatedKey"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;ExclusiveStartKey&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"LastEvaluatedKey"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;ExpressionAttributeNames&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"#pk"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"pk"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;ExpressionAttributeValues&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;":tag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"tag"&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
            &lt;span class="n"&gt;KeyConditionExpression&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"#pk = :tag"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;TableName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Items"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;tags&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Roadmap
&lt;/h2&gt;

&lt;p&gt;Hackathon projects always leave plenty of things undone! We think this project could be a real asset to the community and so we have brainstormed a number of capabilities that could be added going forward. We invite the community to contribute and develop skills with ML, AppSync, CDK, Flutter, and more.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UX Improvements&lt;/li&gt;
&lt;li&gt;Update entities&lt;/li&gt;
&lt;li&gt;Improve recommendation engine with a live model&lt;/li&gt;
&lt;li&gt;Text2Speech&lt;/li&gt;
&lt;li&gt;Notification system&lt;/li&gt;
&lt;li&gt;Recommendations include location/whether virtual&lt;/li&gt;
&lt;li&gt;Allow users to include their own images vs using AI-generated&lt;/li&gt;
&lt;li&gt;QoL devexp (builds are still slow)&lt;/li&gt;
&lt;li&gt;Integration tests&lt;/li&gt;
&lt;li&gt;Security tests and verification in pipeline&lt;/li&gt;
&lt;li&gt;Speaker, talk, and event reviews / 5-star system&lt;/li&gt;
&lt;li&gt;Import from sessionize/et al&lt;/li&gt;
&lt;li&gt;Social login&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This was an ambitious project and a lot of work during hours that we might otherwise be doing something else, but it's amazing working with Danielle, Julian, and Johannes and I know this won't be our last collaboration. Personally I learned a ton and got exposed to quite a few technologies I hadn't laid hands on before. As this project was focused on ML and Transformers Agent, we should give special attention there. I feel this API is fairly easy to use and it's an innovative way to expose ML models to developers. That said, it was clear to me over the course of the hackathon that data science is a real discipline and not one I've practiced enough to assert that I'm getting the most from these models and predictions.&lt;/p&gt;

&lt;p&gt;Lowering the bar to entry to a deep and complex technology can only be a good thing for the community. It's a lot to ask of someone to master all the technologies outlined in this blogpost and that's coming from a team that has more than half a century of collective experience! Despite being seasoned coders, we still spent valuable hackathon time asking "do we need a VPC?", "how does Sagemaker work?", and "why is this thing so slow?" It's our hope that our contribution will help others to solve these problems and more, and to build great things.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>machinelearning</category>
      <category>hackathon</category>
      <category>serverless</category>
    </item>
    <item>
      <title>AWS Users, Roles, and Identity Center Demystified</title>
      <dc:creator>Matt Morgan</dc:creator>
      <pubDate>Mon, 12 Jun 2023 12:46:50 +0000</pubDate>
      <link>https://dev.to/aws-builders/aws-users-roles-and-identity-center-demystified-55g9</link>
      <guid>https://dev.to/aws-builders/aws-users-roles-and-identity-center-demystified-55g9</guid>
      <description>&lt;p&gt;I had the privilege and pleasure of speaking in the developer lounge at &lt;a href="https://aws.amazon.com/events/summits/washington-dc/" rel="noopener noreferrer"&gt;DC Summit&lt;/a&gt; this year. I chose a topic that I've frequently perceived as a stumbling block in a developer's journey to serverless and cloud: Identity and Access Management or IAM.&lt;/p&gt;

&lt;p&gt;The gist of my talk is that I introduce some of the basic concepts which are critical to understanding IAM and then show a few practical ways to get hands-on experience. In particular I draw the distinction between IAM Users and Roles. I also had a section on IAM Identity Center, touched on OIDC and finished up talking about how frameworks can help us understand and find success using service roles.&lt;/p&gt;

&lt;p&gt;After my talk, I had some questions from a listener and it quickly dawned on me that I had failed to explain that Identity Center Users are not the same as IAM Users. By omitting this critical fact, I'd not cleared up things as much as I'd hoped I could.&lt;/p&gt;

&lt;p&gt;So let's try this again. Before I get to how overloaded "user" is, it's a good idea to define a few core concepts in IAM.&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;a&gt;&lt;/a&gt;IAM Policies
&lt;/h1&gt;

&lt;p&gt;Policies define the permissions that are being granted or denied. While they do nothing by themselves, they can be attached to all types of Principals (see below). Policies define the action or actions will either be allowed or denied and the resources which are governed by the Policy. If two Policies are in conflict (one says Allow and one Deny), then access is denied.&lt;/p&gt;

&lt;p&gt;Here's a sample Policy from my talk:&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;"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;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&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;"Action"&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;"sqs:SendMessage*"&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;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&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:sqs:us-east-1:123456789012:my-queue"&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;This Policy allows the Principal to which it is attached to send messages to the referenced queue.&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;a&gt;&lt;/a&gt;IAM Principals
&lt;/h1&gt;

&lt;p&gt;An IAM Principal can refer to a human user or a workload that requires credentialed access to AWS resources. There are two main types of Principals in AWS in addition to root users. The primary difference between types of Principals is whether the credentials they provide are permanent or temporary.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a&gt;&lt;/a&gt;Root Users
&lt;/h2&gt;

&lt;p&gt;Root users are a special type of IAM User that carry additional permissions such as the ability to close the account. Each account has exactly one root user. The best practice is to use that root user to set up other ways to access the account, then stop using it.&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;a&gt;&lt;/a&gt;IAM Users
&lt;/h1&gt;

&lt;p&gt;We've already established that we aren't &lt;em&gt;only&lt;/em&gt; talking about human users but it bears repeating that an IAM User is not necessarily a human user. IAM Users are frequently assigned to machine workflows. For example, a Jenkins server may have an IAM User which allows it to make aws cli requests in order to deploy applications. Or a web application may have an IAM User which allows it to store objects in S3. Humans may be assigned IAM Users which allow them to access the AWS console and/or execute cli or sdk requests to AWS services.&lt;/p&gt;

&lt;p&gt;The important thing to keep in mind is that an IAM User is a long-lived or permanent credential. It lasts until revoked. IAM Users can provide one or more access keys, often in the format of AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY. This is effectively a username and password combo for whatever resources can be managed by the IAM User's attached Policies.&lt;/p&gt;

&lt;p&gt;IAM Users are still in common use, but they are no longer a best practice. The problem with IAM Users is access keys get passed around, checked into GitHub and exposed as environment variables. Since they don't expire, the risk of exposure is ever-present. It's not possible to work with IAM Users without facing this risk.&lt;/p&gt;

&lt;p&gt;Not convinced? I was a bit of a hard sell, myself. I operated my personal account on the root user for more than a decade, then switched to an IAM User. Recently I locked down my root user, deleted my IAM User and switched entirely to roles. Why? In addition to wanting to learn how to secure my account properly, I also came to understand that using Roles is simply a better developer experience. I'll go into detail on my account setup in a future post.&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;a&gt;&lt;/a&gt;Roles
&lt;/h1&gt;

&lt;p&gt;Roles are used to grant temporary credentials to both human and machine users. This sounds like it might be complicated or annoying, but actually Roles are a lot easier to work with than Users.&lt;/p&gt;

&lt;p&gt;Human users using Roles can leverage &lt;a href="https://aws.amazon.com/iam/identity-center/" rel="noopener noreferrer"&gt;IAM Identity Center&lt;/a&gt; (formerly AWS SSO) which offers a pretty good experience, whether we're federating from &lt;a href="https://azure.microsoft.com/en-us/products/active-directory" rel="noopener noreferrer"&gt;Active Directory&lt;/a&gt; (a popular choice for enterprises) or managing users within Identity Center (fine for individuals or small team). We get an easy console sign-in experience and similarly frictionless command line access.&lt;/p&gt;

&lt;p&gt;Roles are the simplest way to integrate services in IAM. We can grant a Lambda Function the ability to send messages to SQS. We won't need to provide an access key at runtime. The AWS SDK will automatically handle fetching credentials and refreshing them when they expire. The credentials will be scoped to the Policy that is attached to the Role we have given our Lambda Function. This may be complex to understand, but it's easy enough to operate and can largely be handled by a framework.&lt;/p&gt;

&lt;p&gt;When it comes to outside access, whether an on-prem server or a third party CI/CD or monitoring service, we still have the option of using Roles by means of &lt;a href="https://docs.aws.amazon.com/rolesanywhere/latest/userguide/introduction.html" rel="noopener noreferrer"&gt;IAM Roles Anywhere&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc.html" rel="noopener noreferrer"&gt;OpenID Connect (OIDC)&lt;/a&gt;. These very similar capabilities allow us to establish a trust relationship between the 3rd party and AWS such that the 3rd party can assume a Role.&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;a&gt;&lt;/a&gt;IAM User vs Identity Center User
&lt;/h1&gt;

&lt;p&gt;IAM Identity Center is &lt;em&gt;not&lt;/em&gt; a Principal like IAM Users and Roles. Instead it's a way to connect a human user to a Role, which can then be used to access the AWS console or services via the cli or sdk. The users that are managed in IAM Identity Center are &lt;em&gt;not&lt;/em&gt; IAM Users. This is the point that I'd failed to convey to my listener.&lt;/p&gt;

&lt;p&gt;Identity Center provides support for inviting users (again not IAM Users) or federating from an Active Directory or SAML provider. Users can be given permission sets or added to groups that have permission sets. Best of all, Identity Center works at the AWS Organization level, so everything is in one place, even if your Organization spans many AWS accounts and regions.&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;a&gt;&lt;/a&gt;Comparing Users and Roles
&lt;/h1&gt;

&lt;p&gt;I'm making the claim that we should use Roles, not only because they are more secure, but also because they provide a better developer experience. Let's walk through a few scenarios to see if that holds up.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a&gt;&lt;/a&gt;Console Access
&lt;/h2&gt;

&lt;p&gt;Human access to the AWS console is a basic use case that must be supported.&lt;/p&gt;

&lt;h3&gt;
  
  
  With an IAM User
&lt;/h3&gt;

&lt;p&gt;Logging into the AWS console with an IAM User requires you to enter your Account ID, IAM user name, and Password, then (hopefully) enter a two-factor authentication code.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Account ID required to sign in&lt;/li&gt;
&lt;li&gt;Multi-account not supported&lt;/li&gt;
&lt;li&gt;Federation not supported&lt;/li&gt;
&lt;/ul&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%2Fc5titv0k9zt6y7b5nn8m.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%2Fc5titv0k9zt6y7b5nn8m.png" alt="Logging into AWS as an IAM User"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;IAM Users don't support federation so you can't use Active Directory or a SAML provider to sign in. If a human with access to an IAM User leaves an organization, the organization must take care to delete or disable the IAM User since removal from corporate directories will not automatically disable console access. This may lead organizations to limit console access when it's something that can benefit any developer who is developing in the cloud.&lt;/p&gt;

&lt;p&gt;IAM Users don't scale well to multi-account scenarios. You have the option of creating separate users in each account or you can allow IAM Users to assume Roles in other accounts, at which point you're much better off using IAM Identity Center.&lt;/p&gt;

&lt;h3&gt;
  
  
  With IAM Identity Center (and Roles)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Landing page with custom URL&lt;/li&gt;
&lt;li&gt;Multi-account, multi-role support&lt;/li&gt;
&lt;li&gt;Federation supported&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;IAM Identity Center allows for a much better sign-in experience and supports multi-account out of the box. It also supports &lt;a href="https://docs.aws.amazon.com/singlesignon/latest/userguide/samlfederationconcept.html" rel="noopener noreferrer"&gt;federation&lt;/a&gt; so you can give an organization-wide single sign-on experience.&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%2Fc6vhzt7tno3gjzo777ug.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%2Fc6vhzt7tno3gjzo777ug.png" alt="Identity Center Landing Page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When signing in with IAM Identity Center, users can start at a custom, company-branded URL, choose the account they wish to access and choose the appropriate role for the task at hand.&lt;/p&gt;

&lt;p&gt;Role and user management are handled in one place, even in multi-account scenarios. Administrators can create groups and permission sets to manage access. There are a couple of rough edges to Identity Center. For example, when viewing a user in the console, you can tell what groups they are a part of, but you can't easily tell if any additional permission sets have been assigned. That's only visible at the account level. This limitation in mind, it's still a far better experience than trying to manage IAM Users across multiple accounts.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a&gt;&lt;/a&gt;Command-Line Access
&lt;/h2&gt;

&lt;p&gt;Developers frequently require command-line access to AWS resources using tools like the AWS CLI, AWS CDK, or AWS SAM. This can be done with IAM Users or Roles.&lt;/p&gt;

&lt;h3&gt;
  
  
  With an IAM User
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Long-lived access key&lt;/li&gt;
&lt;li&gt;Separate access key needed for each account/role&lt;/li&gt;
&lt;li&gt;Credentials stored on developer workstation&lt;/li&gt;
&lt;li&gt;Credentials not tied to an identity source (no federation)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To use an IAM User at the command line, we must generate an access key and then place it in a credentials file which is normally inside a hidden directory on the user's filesystem. Although developers are recommended to store these credentials well outside a project path, they are occasionally committed to GitHub with &lt;a href="https://www.comparitech.com/blog/information-security/github-honeypot" rel="noopener noreferrer"&gt;disastrous results&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Developers also have the option of setting an access key and secret as environment variables, but this has the drawback of it being more burdensome to switch accounts and roles.&lt;/p&gt;

&lt;h3&gt;
  
  
  With IAM Identity Center (and Roles)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Short-lived credentials&lt;/li&gt;
&lt;li&gt;Easy to set up multi-account, multi-role&lt;/li&gt;
&lt;li&gt;Developer need not handle credentials&lt;/li&gt;
&lt;li&gt;Credentials can be tied to a federated identity source&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;IAM Identity Center provides &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sso.html" rel="noopener noreferrer"&gt;command line&lt;/a&gt; capabilities. While the short-lived credentials may be cached on a developer machine for speed, they don't need to be handled by any human so they are less likely to be mishandled, shared, or committed to a repository.&lt;/p&gt;

&lt;p&gt;The experience of using IAM Identity Center is seamless and not disruptive while being one of the most secure ways to access AWS. I'll detail how I do this in a future post.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a&gt;&lt;/a&gt;Third Party Machine Access
&lt;/h2&gt;

&lt;p&gt;This could be an on-prem application, a SaaS partner, or even an application running in AWS that isn't using Roles.&lt;/p&gt;

&lt;h3&gt;
  
  
  With an IAM User
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Long-lived access key which must be handled by humans and stored somewhere&lt;/li&gt;
&lt;li&gt;Credential can potentially be exfiltrated from a compromised partner or runtime&lt;/li&gt;
&lt;li&gt;Traceability is poor - it can be unclear which applications or partners are using which access keys&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This scenario is very similar to the developer cli access. We will have an access key and it must be stored somewhere. Usually the key is stored either in a credentials file on disk or as an environment variable. Either way, there's a risk of exposure of this long-lived credential.&lt;/p&gt;

&lt;p&gt;The access key will need to be handled by humans at some point which is how they wind up on sticky notes, laptops, and other places they shouldn't be. Rotating access keys regularly helps with this problem, but that often involves manual work.&lt;/p&gt;

&lt;h3&gt;
  
  
  With Roles
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;No copy-pasting of access keys&lt;/li&gt;
&lt;li&gt;Short-term credentials&lt;/li&gt;
&lt;li&gt;No credential need to be stored on the third party&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To use Roles with a third party, we'll need to either use &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc.html" rel="noopener noreferrer"&gt;OpenID Connect&lt;/a&gt; or &lt;a href="https://docs.aws.amazon.com/rolesanywhere/latest/userguide/introduction.html" rel="noopener noreferrer"&gt;IAM Roles Anywhere&lt;/a&gt;. These options will again allow us to use short-term credentials. Most significantly, we can use 3rd party or on-prem applications without storing any access keys or other secrets. Instead we'll establish a trust relationship using certificates or ssh keys, similar to how developers around the world push code to their version control.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a&gt;&lt;/a&gt;Service Access
&lt;/h2&gt;

&lt;p&gt;Developers working in serverless and managed services will frequently need to create roles to allow intra-service communication.&lt;/p&gt;

&lt;h3&gt;
  
  
  With an IAM User
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Long-lived access key which must be handled by humans and stored somewhere&lt;/li&gt;
&lt;li&gt;Credential can potentially be exfiltrated from a compromised partner or runtime&lt;/li&gt;
&lt;li&gt;Traceability is poor - it can be unclear which applications or partners are using which access keys&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's not that common to see developers trying to put access keys into Lambda function. Many kinds of integrations, such as an SQS queue subscribing to an SNS topic, have no IAM User option at all. Any compute layer such as Lambda, Fargate, or EC2 can use IAM Users but all of these can support roles.&lt;/p&gt;

&lt;p&gt;In addition to security concerns, access keys must be stored somewhere to be provided to an application. Often they are injected during a CI/CD pipeline. This wouldn't be necessary using Roles.&lt;/p&gt;

&lt;h3&gt;
  
  
  With a Role
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Short-lived credentials with refreshes handled by the SDK&lt;/li&gt;
&lt;li&gt;No copy-pasting access keys&lt;/li&gt;
&lt;li&gt;Framework support&lt;/li&gt;
&lt;li&gt;It's your only option in many scenarios!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As previously stated, Roles are the most common way to manage these interactions and are the only option in many cases. Developers who are using frameworks like the &lt;a href="https://aws.amazon.com/cdk/" rel="noopener noreferrer"&gt;AWS Cloud Development Kit&lt;/a&gt; or the &lt;a href="https://aws.amazon.com/serverless/sam/" rel="noopener noreferrer"&gt;AWS Serverless Application Model&lt;/a&gt; can take advantage of framework features that make Role management relatively easy. The Roles and Policies can be created and assigned using these frameworks. Here are a couple of examples of how that works:&lt;/p&gt;

&lt;p&gt;AWS CDK&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;queue&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;Queue&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="s1"&gt;MyQueue&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="kd"&gt;function&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;Function&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="s1"&gt;MyFunction&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;// more args &lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;grantSendMessages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;This code will create a function role and assign it a policy that allows it to send messages to the queue that was created.&lt;/p&gt;

&lt;p&gt;AWS SAM&lt;/p&gt;

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

&lt;span class="na"&gt;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;MyQueue&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::SQS::Queue&lt;/span&gt;
  &lt;span class="na"&gt;MyFunction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless::Function&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# additional properties&lt;/span&gt;
      &lt;span class="na"&gt;Policies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;AWSLambdaBasicExecutionRole&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;SQSSendMessagePolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;QueueName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;MyQueue.QueueName&lt;/span&gt;



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

&lt;/div&gt;

&lt;p&gt;This is functionally equivalent to the CDK code.&lt;/p&gt;

&lt;p&gt;Not using Lambda? Both &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html" rel="noopener noreferrer"&gt;EC2&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html" rel="noopener noreferrer"&gt;Fargate&lt;/a&gt; also support Roles.&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;a&gt;&lt;/a&gt;Conclusion
&lt;/h1&gt;

&lt;p&gt;In this post, I've defined IAM Policies, Principals, Users, and Roles. I've described IAM Identity Center and tried to explain how the users in Identity Center are not IAM Users.&lt;/p&gt;

&lt;p&gt;Beyond that, I made the case that switching from IAM Users to Roles delivers a superior developer experience in addition to being more secure.&lt;/p&gt;

&lt;p&gt;The best security tools are the ones that make the right thing the easiest thing. IAM Roles and Identity Center succeed in delivering not just a safer experience, but also a better developer experience.&lt;/p&gt;

&lt;p&gt;I hope this post has helped to clear up some of the core concepts in IAM. I invite readers to add questions in the comments if any of these concepts remain unclear.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>iam</category>
      <category>security</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Avoiding the Serverless Workflow Antipattern</title>
      <dc:creator>Matt Morgan</dc:creator>
      <pubDate>Mon, 27 Feb 2023 12:16:45 +0000</pubDate>
      <link>https://dev.to/aws-builders/avoiding-the-serverless-workflow-antipattern-2ba1</link>
      <guid>https://dev.to/aws-builders/avoiding-the-serverless-workflow-antipattern-2ba1</guid>
      <description>&lt;p&gt;A common pattern for teams moving from cron-based jobs to event-driven architectures is to start using AWS Lambda as the compute engine for asynchronous event handlers. This can work out very well for simple event handlers, but I've observed a common antipattern that can appear when workflows grow more complex. Sooner or later workflows with multiple steps enter the picture and we'll need to be careful in our approach lest the complexity get the best of us.&lt;/p&gt;

&lt;h2&gt;
  
  
  Asynchronous Event Handler
&lt;/h2&gt;

&lt;p&gt;Using Lambda as an event handler can be a pretty good plan. A &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-services.html#listing-of-services-and-links-to-more-information" rel="noopener noreferrer"&gt;large number of AWS services&lt;/a&gt; can target Lambda, making integrations easy. For third parties, you can use a &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-urls.html" rel="noopener noreferrer"&gt;FURL&lt;/a&gt; to create a low-friction webhook. Lambda of course has a pricing model that is ideal for irregularly-scheduled events. It's relatively easy to compose and deploy a Lambda function.&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%2Fegtw7w95x7laomfu6u79.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%2Fegtw7w95x7laomfu6u79.png" alt="Event triggering a Lambda Function" width="270" height="120"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Fan-Out with SQS
&lt;/h2&gt;

&lt;p&gt;This simple architecture can cover a lot of use cases, but not all of them. What happens when complexity grows? The main driver I've seen for adopting more complex architectures is when the job won't complete within 15 minutes, but there can be other reasons for doing this as well, such as asynchronously communicating with some other system in the middle of the job.&lt;/p&gt;

&lt;p&gt;Let's say our job is running long and we decide to adopt a fan-out pattern. We can achieve this by putting messages in a queue using SQS and subscribing another Lambda function to those messages. This will give us the opportunity for batching and throttling to make sure our workflow can run in parallel, but not overwhelm other resources.&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%2F6j7fp7uxfmtubjjsequa.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%2F6j7fp7uxfmtubjjsequa.png" alt="Event triggering a Lambda Function which uses SQS to fan out work to more Lambda Functions" width="570" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Fan-Out with Job State Tracking
&lt;/h2&gt;

&lt;p&gt;Okay, now our workload is broken down some, but we have a new problem. How do we know when the job has finished? How do we know the success/failure rate of each step in the job? What if we only want one instance of our job to run at a time? We can't prevent a new job from kicking off if we don't know whether or not the old one has finished.&lt;/p&gt;

&lt;p&gt;SQS is a great service, but it's not a workflow engine! It won't maintain job state for us, so if that's important, it will be up to us to implement it. We could store the current job state in a DynamoDB table, writing the number of expected downstream jobs and tallying them as we go, maybe including another step at the end to give us a summary report. Okay, now our architecture looks like this:&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%2Fjxpto7t8nbo0wmjev8gy.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%2Fjxpto7t8nbo0wmjev8gy.png" alt="Event triggering a Lambda Function using SQS for fan-out, storing job state in DynamoDB and finally using another Lambda function to provide a summary" width="800" height="639"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To achieve this architecture, each of our Lambda Functions is now responsible for job tracking. We've more or less had to write our own workflow engine. We'll own that code and need to maintain it. This is an &lt;strong&gt;antipattern&lt;/strong&gt; that violates the promise of managed services, misuses SQS, and forces us to add boilerplate, workflow-aware code to every Lambda Function.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter Step Functions
&lt;/h2&gt;

&lt;p&gt;AWS Step Functions is a great tool to solve this problem. Step Functions allow us to focus on our business logic while workflow and job state are handled by a managed service. We can query the service or check out the console visualization to find out whether or not a job is in progress or finished.&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%2Fj2ytlwb1ketgn74k7g0o.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%2Fj2ytlwb1ketgn74k7g0o.png" alt="Step Function with a Map state" width="671" height="571"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All the problems outlined above are solved by building the workflow with Step Functions. In the provided example, we still have the Job Start function, which perhaps performs an initial database query to drive the rest of the job, or it could query the Step Functions service to see if another execution is underway (for use cases where we want to limit concurrency). If none of that applies to our workflow, we can skip this step! Likewise we could skip the summary step since Step Functions will be able to provide the output of the work that was done by each of our workers and as such a summary step would only be needed if we wanted to format a human-readable report.&lt;/p&gt;

&lt;h2&gt;
  
  
  Learning Step Functions
&lt;/h2&gt;

&lt;p&gt;Step Functions can be complex and challenging to learn. They are described using &lt;a href="https://states-language.net/" rel="noopener noreferrer"&gt;Amazon States Language&lt;/a&gt;. This complexity can drive developers to avoid this service, leading to subpar architectures. I acknowledge there's a lot to learn here, but it's well worth it! And it doesn't need to be an uphill battle.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/workflow-studio.html" rel="noopener noreferrer"&gt;Workflow Studio&lt;/a&gt; is a great way to get get productive fast. You may also be interested in an &lt;a href="https://dev.to/elthrasher/exploring-aws-cdk-step-functions-1d1e"&gt;article I wrote&lt;/a&gt; about learning to write Step Functions using &lt;a href="https://aws.amazon.com/cdk/" rel="noopener noreferrer"&gt;AWS CDK&lt;/a&gt;. If that doesn't do it for you, there are a number of other free resources on the web that can put you on the right path.&lt;/p&gt;

&lt;h2&gt;
  
  
  You Also Get...
&lt;/h2&gt;

&lt;p&gt;I left off a lot of the great features of Step Functions by focusing on the comparison to an SQS-driven workflow. Step Functions also support:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/concepts-express-synchronous.html" rel="noopener noreferrer"&gt;synchronous express workflows&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/supported-services-awssdk.html" rel="noopener noreferrer"&gt;native integrations&lt;/a&gt; (Lambda-less)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/connect-to-resource.html#connect-wait-token" rel="noopener noreferrer"&gt;asynchronous invocation and callback with Task Tokens&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;time-based &lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-wait-state.html" rel="noopener noreferrer"&gt;Wait steps&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html" rel="noopener noreferrer"&gt;Intrinsic Functions&lt;/a&gt; (again Lambda-less)&lt;/li&gt;
&lt;li&gt;automatic &lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/concepts-error-handling.html#error-handling-retrying-after-an-error" rel="noopener noreferrer"&gt;Retry&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Know and Plan Your Architecture
&lt;/h2&gt;

&lt;p&gt;The best path to success in cloud is to understand our objectives and know which services will help us to achieve them. Sometimes all we need is a single Lambda handler. If "fire and forget" is good enough, then an SQS-based fan-out is probably fine. Still, the use-cases for more complex workflows is indelible and more often than not, we'll need the ability to audit them. Step Functions is a great tool for achieving that aim.&lt;/p&gt;

&lt;h2&gt;
  
  
  More on Step Functions
&lt;/h2&gt;

&lt;p&gt;As mentioned above, there's a lot of great articles out there on Step Functions to help in your journey. Here are a few of my favorites - and please feel free to add to the list in comments below!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://matt.martz.codes/orchestrating-choreography-with-event-driven-step-functions" rel="noopener noreferrer"&gt;https://matt.martz.codes/orchestrating-choreography-with-event-driven-step-functions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://theburningmonk.com/2020/08/choreography-vs-orchestration-in-the-land-of-serverless/" rel="noopener noreferrer"&gt;https://theburningmonk.com/2020/08/choreography-vs-orchestration-in-the-land-of-serverless/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.readysetcloud.io/blog/allen.helton/when-not-to-use-step-functions/" rel="noopener noreferrer"&gt;https://www.readysetcloud.io/blog/allen.helton/when-not-to-use-step-functions/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/lukvonstrom/best-practices-for-using-aws-stepfunctions-2io"&gt;https://dev.to/lukvonstrom/best-practices-for-using-aws-stepfunctions-2io&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/aws-builders/testing-aws-step-functions-flows-2kpn"&gt;https://dev.to/aws-builders/testing-aws-step-functions-flows-2kpn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/aws-builders/aws-step-function-vs-aws-lambda-benchmark-54hj"&gt;https://dev.to/aws-builders/aws-step-function-vs-aws-lambda-benchmark-54hj&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/mathem/an-approach-to-loosely-coupled-cloudwatch-alarms-and-contextual-alerts-2fh2"&gt;https://dev.to/mathem/an-approach-to-loosely-coupled-cloudwatch-alarms-and-contextual-alerts-2fh2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/aws-builders/using-step-functions-to-eliminate-your-nat-gateway-12ge"&gt;https://dev.to/aws-builders/using-step-functions-to-eliminate-your-nat-gateway-12ge&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/aws-builders/step-functions-for-making-your-text-based-images-searchable-334"&gt;https://dev.to/aws-builders/step-functions-for-making-your-text-based-images-searchable-334&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://serverlessfirst.com/serverless-email-scheduler/" rel="noopener noreferrer"&gt;https://serverlessfirst.com/serverless-email-scheduler/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>fullstack</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Appreciation for Community Managers</title>
      <dc:creator>Matt Morgan</dc:creator>
      <pubDate>Tue, 20 Dec 2022 12:42:48 +0000</pubDate>
      <link>https://dev.to/aws-builders/appreciation-for-community-managers-26ol</link>
      <guid>https://dev.to/aws-builders/appreciation-for-community-managers-26ol</guid>
      <description>&lt;p&gt;I had the privilege of joining the inaugural class of AWS Community Builders back in 2020. I've put a lot into the program in these two years and change, but it's fair to say I've gotten more out. So when &lt;a class="mentioned-user" href="https://dev.to/lockhead"&gt;@lockhead&lt;/a&gt; asked me to help out with an appreciation post for our community management team, I of course said yes! The format that follows is the same as the &lt;a href="https://dev.to/aws-builders/aws-community-builders-holidays-posts-2022-397h"&gt;related posts&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What surprises you most about the community builders program?
&lt;/h2&gt;

&lt;p&gt;I'm impressed with the way the program keeps growing in numbers but somehow maintains the intimacy of a conversation. Just one example of this is the Community Builders slack. There's always something interesting happening there, but it never feels overwhelming or intimidating. Of course Community Builders connect all over social media, in the workplace, at conferences and anywhere else we dare!&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s your background and your experience with AWS?
&lt;/h2&gt;

&lt;p&gt;It's long and varied :D&lt;/p&gt;

&lt;p&gt;I started writing web apps in the late 1990s. I registered my personal AWS account back in 2012 or 2013. I started using AWS heavily in my day job in 2017 and I became a serious hardcore &lt;a href="https://awscommunity.social/@serverlesscultist"&gt;serverless cultist&lt;/a&gt; in 2019. Still going strong!&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s the biggest benefit you see from the program?
&lt;/h2&gt;

&lt;p&gt;Connecting with other builders is a huge benefit. Having worked in a few different companies and having had a peek inside a few more, it's surprising how many of the same problems we're all solving. Taking on challenges is easier with a team and Community Builders are generous of their time to help others in their journeys.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s the next swag item that you would like to get?
&lt;/h2&gt;

&lt;p&gt;I know one of the Community Builders had a custom phone case made. This seems like a great idea!&lt;/p&gt;

&lt;h2&gt;
  
  
  What are you eating for dinner today? Share the recipe!
&lt;/h2&gt;

&lt;p&gt;Heat olive oil in a pan. Get it very hot! Add some green beans and sprinkle with salt and pepper. Toss them vigorously and watch the flames leap. Serve them crisp and hot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is there anything else you would like to say about the community builders program in 2022?
&lt;/h2&gt;

&lt;p&gt;This is the best tech community I've been a part of. I'm making lifelong friends, helping others to grow, growing myself, participating in great events, and learning a lot. The program provides me with experiences and activity when I want it and is patient when I get busy with my day job.&lt;/p&gt;

&lt;p&gt;I highly recommend applying to be an AWS Community Builder, and if you are not accepted, try again in six months. I know many Community Builders who were accepted on subsequent application. I invite readers to connect with me if you want to know more.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>awscommunity</category>
      <category>cbchristmas2022</category>
    </item>
    <item>
      <title>Lambda Powertools TypeScript is Generally Available</title>
      <dc:creator>Matt Morgan</dc:creator>
      <pubDate>Tue, 19 Jul 2022 16:03:19 +0000</pubDate>
      <link>https://dev.to/aws-builders/lambda-powertools-typescript-is-generally-available-1dm8</link>
      <guid>https://dev.to/aws-builders/lambda-powertools-typescript-is-generally-available-1dm8</guid>
      <description>&lt;p&gt;I gave a &lt;a href="https://dev.to/aws-builders/first-look-at-lambda-powertools-typescript-2k3p"&gt;first look&lt;/a&gt; at &lt;a href="https://awslabs.github.io/aws-lambda-powertools-typescript/latest/" rel="noopener noreferrer"&gt;Lambda Powertools TypeScript&lt;/a&gt; back in January of 2022. I was pretty excited for the library at the time, but it came with an admonishment that it wasn't ready for production use. Well, the &lt;a href="https://twitter.com/Sarutule/status/1547900106120187909" rel="noopener noreferrer"&gt;general availability announcement&lt;/a&gt; dropped July 15 so it's time for another look.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;What's Changed?&lt;/li&gt;
&lt;li&gt;ES Modules Support&lt;/li&gt;
&lt;li&gt;Comparisons&lt;/li&gt;
&lt;li&gt;Logger&lt;/li&gt;
&lt;li&gt;Metrics&lt;/li&gt;
&lt;li&gt;Tracer&lt;/li&gt;
&lt;li&gt;Roadmap&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's Changed&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;So what's changed in the beta? A glance at the &lt;a href="https://awslabs.github.io/aws-lambda-powertools-typescript/latest/changelog/" rel="noopener noreferrer"&gt;CHANGELOG&lt;/a&gt; suggests the answer is that not very much has changed in six months. Lambda Powertools TypeScript still supports &lt;a href="https://dev.to/aws-builders/first-look-at-lambda-powertools-typescript-2k3p#decorators"&gt;class decorators&lt;/a&gt;, &lt;a href="https://middy.js.org/" rel="noopener noreferrer"&gt;middy&lt;/a&gt;, and a manual API. It still covers the core capabilities of logging, metrics and tracing, and no new capabilities have been added. Outside of some bug fixes and optimization, this is still very much the library I previewed back in January.&lt;/p&gt;

&lt;h2&gt;
  
  
  ES Modules Support&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;One change I might've liked to see is support for &lt;a href="https://nodejs.org/docs/latest-v16.x/api/esm.html#modules-ecmascript-modules" rel="noopener noreferrer"&gt;ES Modules&lt;/a&gt;. Interest in ES Modules has been growing quickly in the serverless community, driven largely by the desire to use by &lt;a href="https://aws.amazon.com/about-aws/whats-new/2022/01/aws-lambda-es-modules-top-level-await-node-js-14/" rel="noopener noreferrer"&gt;Top-Level Await&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Lambda Powertools TypeScript can't be used directly as an ES Modules dependency, but there is an &lt;a href="https://github.com/awslabs/aws-lambda-powertools-typescript/issues/521" rel="noopener noreferrer"&gt;issue&lt;/a&gt; open, so please consider adding your +1. For now, it's possible to get by with a &lt;a href="https://github.com/elthrasher/powertools-benchmarks/blob/main/cdk/powertools-benchmarks-stack.ts#L12" rel="noopener noreferrer"&gt;require shim&lt;/a&gt; or via &lt;a href="https://twitter.com/brianleroux/status/1494046747227615235" rel="noopener noreferrer"&gt;cjs tricks&lt;/a&gt; but it would be great to see native support for ES Modules in Lambda Powertools TypeScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparisons&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Given that I've already gone over these modules in the prior post, I thought I'd instead compare the Lambda Powertools TypeScript modules to similar solutions. I'm looking at the API, the bundled script size, cold start and execution time. To gather metrics, I've written a little app using Step Functions that can run many instances of a function in parallel and capture the metrics.&lt;/p&gt;

&lt;p&gt;My benchmarking tool will run each function 50 times aiming to achieve a 20% cold start rate. Code samples are available on &lt;a href="https://github.com/elthrasher/powertools-benchmarks" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Logger&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The simplest way to log to CloudWatch from AWS Lambda is with &lt;code&gt;console.log&lt;/code&gt;. Doing so adds no bloat to your function, there's no dependency management and writes to CloudWatch are asynchronous, so the operation is non-blocking. Many developers use libraries to guarantee logs are in a structured format and to control the verbosity of logging.&lt;/p&gt;

&lt;p&gt;That said, we can use &lt;code&gt;console&lt;/code&gt; as a baseline due to its simplicity. If we want a function that simply writes some unstructured logs, we can do something like this.&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="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;APIGatewayProxyEventV2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;APIGatewayProxyResultV2&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="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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="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;APIGatewayProxyEventV2&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;APIGatewayProxyResultV2&lt;/span&gt;&lt;span class="o"&gt;&amp;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;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="s1"&gt;event: &lt;/span&gt;&lt;span class="dl"&gt;'&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;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="s1"&gt;context: &lt;/span&gt;&lt;span class="dl"&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="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="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Logging out the event yields the stringified context object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2022-07-19T12:10:45.503Z    2abe532e-2b26-46b5-9a65-884363160556    INFO    context:  {
  callbackWaitsForEmptyEventLoop: [Getter/Setter],
  succeed: [Function (anonymous)],
  fail: [Function (anonymous)],
  done: [Function (anonymous)],
  functionVersion: '$LATEST',
  functionName: 'LoggerConsole',
  memoryLimitInMB: '128',
  logGroupName: '/aws/lambda/LoggerConsole',
  logStreamName: '2022/07/19/[$LATEST]384dbd25ffeb4af49bc22c2ac4f333df',
  clientContext: undefined,
  identity: undefined,
  invokedFunctionArn: 'arn:aws:lambda:us-east-1:123456790:function:LoggerConsole',
  awsRequestId: '2abe532e-2b26-46b5-9a65-884363160556',
  getRemainingTimeInMillis: [Function: getRemainingTimeInMillis]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is pretty noisy and having those succeed, fail and done methods doesn't offer a lot of value.&lt;/p&gt;

&lt;p&gt;With Powertools we can inject a more useful context into log messages.&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;Logger&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="s1"&gt;@aws-lambda-powertools/logger&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="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;LambdaInterface&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="s1"&gt;@aws-lambda-powertools/commons&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="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;APIGatewayProxyEventV2&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="s1"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;logger&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;Logger&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Lambda&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;LambdaInterface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;injectLambdaContext&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;logEvent&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="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;handler&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;APIGatewayProxyEventV2&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&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;logger&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Here is some info!&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;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myFunction&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;Lambda&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="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;myFunction&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;Now we get structured logs.&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;"cold_start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"function_arn"&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:lambda:us-east-1:123456790:function:LoggerPowertools"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"function_memory_size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"function_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;"LoggerPowertools"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"function_request_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;"6eeaa0c9-e58f-45a7-bed2-a4b9d7e65d7e"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"INFO"&lt;/span&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;"Here is some info!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"service_undefined"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2022-07-19T12:09:28.537Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"xray_trace_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;"1-62d69ef6-dfdbc4be0f59a76c57c52cf8"&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;This is going to be much easier to search and doesn't include useless stringified methods. Plus we get a &lt;a href="https://github.com/awslabs/aws-lambda-powertools-typescript/blob/main/packages/commons/src/Utility.ts#L62" rel="noopener noreferrer"&gt;cold_start&lt;/a&gt; boolean tossed in.&lt;/p&gt;

&lt;p&gt;For the sake of comparison, I threw in another implementation of the function using the popular and enduring &lt;a href="https://github.com/winstonjs/winston" rel="noopener noreferrer"&gt;winston&lt;/a&gt; library. Let's see how they did.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Function&lt;/th&gt;
&lt;th&gt;Avg Cold Start&lt;/th&gt;
&lt;th&gt;Avg Duration&lt;/th&gt;
&lt;th&gt;Code Size&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;LoggerConsole&lt;/td&gt;
&lt;td&gt;137.24&lt;/td&gt;
&lt;td&gt;1.63&lt;/td&gt;
&lt;td&gt;771&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LoggerPowertools&lt;/td&gt;
&lt;td&gt;157.58&lt;/td&gt;
&lt;td&gt;1.86&lt;/td&gt;
&lt;td&gt;78550&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LoggerWinston&lt;/td&gt;
&lt;td&gt;184.17&lt;/td&gt;
&lt;td&gt;1.62&lt;/td&gt;
&lt;td&gt;233142&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The no-dependencies version is always going to be the fastest. Powertools adds around 78kb while winston is much heavier at 232kb. In either case we're not adding a lot of latency, but Powertools is smaller and thus faster and it gives us that &lt;code&gt;cold_start&lt;/code&gt; metric.&lt;/p&gt;

&lt;h2&gt;
  
  
  Metrics&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Often when it comes to metrics, we think about CPU, latency and other operational metrics and AWS services usually provide those out of the box. This kind of thinking can be flawed when we end up having to use 3rd parties such as google analytics to infer critical business events. A simpler solution is to have the application emit a metric when a business event (say a customer signup) occurs. We have a few options for doing this: We can use aws-sdk, we can use the &lt;a href="https://github.com/awslabs/aws-embedded-metrics-node" rel="noopener noreferrer"&gt;aws-embedded-metrics&lt;/a&gt; lib and now we can use Powertools Metrics. Which is the best? Let's see.&lt;/p&gt;

&lt;p&gt;To baseline this, let's use a function that doesn't emit metrics.&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="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;APIGatewayProxyEventV2&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="s1"&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="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;APIGatewayProxyEventV2&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;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;workflowSuccess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;workflowSuccess&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;The workflow was successful!&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;else&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="s1"&gt;The workflow failed.&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;To understand whether or not our workflow is successful, we'll need to query logs. Ugh!&lt;/p&gt;

&lt;p&gt;Let's try emitting metrics using the &lt;code&gt;@aws-sdk/client-cloudwatch&lt;/code&gt; library from aws-sdk-v3.&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;CloudWatchClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;MetricDatum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;PutMetricDataCommand&lt;/span&gt;&lt;span class="p"&gt;,&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="s1"&gt;@aws-sdk/client-cloudwatch&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="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;APIGatewayProxyEventV2&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="s1"&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="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="nc"&gt;CloudWatchClient&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="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;APIGatewayProxyEventV2&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;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;workflowSuccess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;metric&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MetricDatum&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;workflowSuccess&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;The workflow was successful!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;metric&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;MetricName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;WorkflowSuccess&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Count&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;else&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="s1"&gt;The workflow failed.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;metric&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;MetricName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;WorkflowFailure&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Count&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;command&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;PutMetricDataCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;MetricData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;metric&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;Namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SdkV3Metrics&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;await&lt;/span&gt; &lt;span class="nx"&gt;client&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="nx"&gt;command&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 we have some nice metrics in CloudWatch!&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%2Fwlq8bkaw7n48utkdzmes.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%2Fwlq8bkaw7n48utkdzmes.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The downside of using aws-sdk for this is that it relies on API calls and is somewhat slow. We can try to achieve the same thing using aws-embedded-metrics. How is this different from Cloudwatch custom metrics? It's better and fellow Community Builder Vishnu Prassad can &lt;a href="https://dev.to/aws-builders/cloudwatch-custom-metrics-with-cloudwatch-embedded-metric-format-452j"&gt;tell you why&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createMetricsLogger&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="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-embedded-metrics&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="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;APIGatewayProxyEventV2&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="s1"&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="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;APIGatewayProxyEventV2&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;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;workflowSuccess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.5&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;metrics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createMetricsLogger&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;putDimensions&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="s1"&gt;EMF&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;workflowSuccess&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;putMetric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;WorkflowSuccess&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&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="nx"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;putMetric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;WorkflowFailure&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&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="nx"&gt;Count&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="nx"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flush&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;In addition to having the advantage of EMF, the code is a bit more concise.&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;LambdaInterface&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="s1"&gt;@aws-lambda-powertools/commons&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;Metrics&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MetricUnits&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="s1"&gt;@aws-lambda-powertools/metrics&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="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;APIGatewayProxyEventV2&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="s1"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;metrics&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;Metrics&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Workflow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Lambda&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;LambdaInterface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logMetrics&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;handler&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;APIGatewayProxyEventV2&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&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;workflowSuccess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;workflowSuccess&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addMetric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;WorkflowSuccess&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MetricUnits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addMetric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;WorkflowFailure&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MetricUnits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myFunction&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;Lambda&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="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;myFunction&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;The Powertools version is just a tad more verbose due to the need to use class decorators, but still not bad. In the end we'll care more about performance, so let's run those numbers.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Function&lt;/th&gt;
&lt;th&gt;Avg Cold Start&lt;/th&gt;
&lt;th&gt;Avg Duration&lt;/th&gt;
&lt;th&gt;Code Size&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MetricsNone&lt;/td&gt;
&lt;td&gt;135.25&lt;/td&gt;
&lt;td&gt;0.93&lt;/td&gt;
&lt;td&gt;826&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MetricsEMF&lt;/td&gt;
&lt;td&gt;146.75&lt;/td&gt;
&lt;td&gt;1.33&lt;/td&gt;
&lt;td&gt;29456&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MetricsSDKV3&lt;/td&gt;
&lt;td&gt;225.37&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;257026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MetricsPowertools&lt;/td&gt;
&lt;td&gt;141.3&lt;/td&gt;
&lt;td&gt;1.33&lt;/td&gt;
&lt;td&gt;7942&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I might've thought the Powertools implementation here would wrap aws-embedded-metrics, but obviously not! Although the actual performance benefit of Powertools over aws-embedded-metrics is marginal, you really have to appreciate how they've kept the size down.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tracer&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;In the case of the Tracer module, it actually does wrap the aws-xray-sdk. So why would we use Tracer instead? If the API is nicer and it's not adding much in the way of latency, it could be worth it. Here's an example of using aws-xray-sdk. In this case the Lambda Function is tracing a separate function as well as an SDK call to (somewhat uselessly) get function properties.&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;GetFunctionCommand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;GetFunctionCommandOutput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;LambdaClient&lt;/span&gt;&lt;span class="p"&gt;,&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="s1"&gt;@aws-sdk/client-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;captureAsyncFunc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;captureAWSv3Client&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="s1"&gt;aws-xray-sdk-core&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="kd"&gt;type&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="s1"&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="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="nc"&gt;LambdaClient&lt;/span&gt;&lt;span class="p"&gt;({});&lt;/span&gt;

&lt;span class="nf"&gt;captureAWSv3Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&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;getFunction&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;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="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;GetFunctionCommandOutput&lt;/span&gt;&lt;span class="o"&gt;&amp;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;command&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;GetFunctionCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;FunctionName&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;functionName&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="nx"&gt;client&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="nx"&gt;command&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="o"&gt;=&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;unknown&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;GetFunctionCommandOutput&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;captureAsyncFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;methodWithCustomTrace&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="nx"&gt;subsegment&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;fn&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;getFunction&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;subsegment&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;fn&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;That &lt;code&gt;captureAsyncFunc&lt;/code&gt; part is a bit awkward. How does Powertools do this?&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;LambdaInterface&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="s1"&gt;@aws-lambda-powertools/commons&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;Tracer&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="s1"&gt;@aws-lambda-powertools/tracer&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;GetFunctionCommand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;GetFunctionCommandOutput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;LambdaClient&lt;/span&gt;&lt;span class="p"&gt;,&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="s1"&gt;@aws-sdk/client-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="kd"&gt;type&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="s1"&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="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="nc"&gt;LambdaClient&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;tracer&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;Tracer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;serviceName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;powertoolsTracer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;captureAWSv3Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Lambda&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;LambdaInterface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;captureMethod&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;methodWithCustomTrace&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;GetFunctionCommandOutput&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;command&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;GetFunctionCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;FunctionName&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;functionName&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="nx"&gt;client&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="nx"&gt;command&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="nd"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;captureLambdaHandler&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;handler&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;unknown&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;GetFunctionCommandOutput&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;methodWithCustomTrace&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="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;handlerClass&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;Lambda&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="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;handlerClass&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;I'm really starting to warm to class decorators for cases like this. Function decorators would be better, but &lt;a href="https://dev.to/aws-builders/first-look-at-lambda-powertools-typescript-2k3p#decorators"&gt;as previously discussed&lt;/a&gt;, they don't currently exist in TypeScript.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Function&lt;/th&gt;
&lt;th&gt;Avg Cold Start&lt;/th&gt;
&lt;th&gt;Avg Duration&lt;/th&gt;
&lt;th&gt;Code Size&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TracerXRay&lt;/td&gt;
&lt;td&gt;266.91&lt;/td&gt;
&lt;td&gt;50.56&lt;/td&gt;
&lt;td&gt;410514&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TracerPowertools&lt;/td&gt;
&lt;td&gt;265.42&lt;/td&gt;
&lt;td&gt;48.25&lt;/td&gt;
&lt;td&gt;417980&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Checking the performance numbers, Powertools is extremely light, still weighing in at around 7kb and barely impacting performance at all. In this case, Powertools is slightly faster, but I suspect over the long run it's costing a couple of ms, so worth it for the devexp.&lt;/p&gt;

&lt;h2&gt;
  
  
  Roadmap&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;One final thing to consider when deciding whether to adopt Lambda Powertools is what features may be in store in the future. The more mature libraries &lt;a href="https://awslabs.github.io/aws-lambda-powertools-python/latest/" rel="noopener noreferrer"&gt;Lambda Powertools Python&lt;/a&gt; and &lt;a href="https://awslabs.github.io/aws-lambda-powertools-java/" rel="noopener noreferrer"&gt;Lambda Powertools Java&lt;/a&gt; include a number of useful utilities that we may like to see in Lambda Powertools TypeScript. The overall &lt;a href="https://github.com/awslabs/aws-lambda-powertools-roadmap/projects/1" rel="noopener noreferrer"&gt;Lambda Powertools Roadmap&lt;/a&gt; doesn't tell us very much other than pending dotnet and golang libraries, but we can drill down into &lt;a href="https://github.com/awslabs/aws-lambda-powertools-typescript/projects/2?query=is%3Aopen+sort%3Aupdated-desc" rel="noopener noreferrer"&gt;TypeScript-specific issues&lt;/a&gt; and glimpse the immediate future. It looks like stability is still the main priority, but there are some interesting items like &lt;a href="https://github.com/awslabs/aws-lambda-powertools-typescript/issues/445" rel="noopener noreferrer"&gt;RFC: Testing Factories for AWS Data Objects&lt;/a&gt; in the leftmost column.&lt;/p&gt;

&lt;p&gt;Community engagement and voting will no doubt help to drive the Powertools roadmap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;To me, this is a no-brainer. One of the challenges in writing Lambda is that many of our dependencies were not designed for Lambda. This library obviously was and the team obviously took care to deliver maximum value in a minimal package. These core utilities promote best practices in a way that is accessible and easy to use. Whether you're writing in TypeScript or JavaScript, you can enjoy good IDE support and a high-level API to implement logging, metrics and tracing in Lambda.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://commons.wikimedia.org/wiki/File:Raijyu-kuniyoshi-676x1024.jpg" rel="noopener noreferrer"&gt;COVER&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>typescript</category>
      <category>lambda</category>
    </item>
    <item>
      <title>How to Use Source Maps in TypeScript Lambda Functions (with Benchmarks)</title>
      <dc:creator>Matt Morgan</dc:creator>
      <pubDate>Mon, 24 Jan 2022 11:50:51 +0000</pubDate>
      <link>https://dev.to/aws-builders/how-to-use-source-maps-in-typescript-lambda-functions-with-benchmarks-4bo4</link>
      <guid>https://dev.to/aws-builders/how-to-use-source-maps-in-typescript-lambda-functions-with-benchmarks-4bo4</guid>
      <description>&lt;p&gt;TypeScript is a popular language for developers of all kinds and it's made its mark on the serverless space. Most of the major Lambda frameworks now have solid TypeScript support. The days of struggling with webpack configurations are mostly behind us.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Stack Traces&lt;/li&gt;
&lt;li&gt;Emitting Source Maps&lt;/li&gt;
&lt;li&gt;Source Map Support&lt;/li&gt;
&lt;li&gt;
CDK Example

&lt;ul&gt;
&lt;li&gt;Serverless Stack&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
SAM Example

&lt;ul&gt;
&lt;li&gt;Architect&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Serverless Framework Example&lt;/li&gt;
&lt;li&gt;
Benchmarks

&lt;ul&gt;
&lt;li&gt;Not Minified without Source Maps&lt;/li&gt;
&lt;li&gt;Minified without Source Maps&lt;/li&gt;
&lt;li&gt;Minified with Source Maps&lt;/li&gt;
&lt;li&gt;Error Minified without Source Maps&lt;/li&gt;
&lt;li&gt;Error Minified with Source Maps&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Stack Traces&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;If we're already transpiling our code to convert the TypeScript source to Lambda-friendly JavaScript, we might as well go ahead and &lt;a href="https://esbuild.github.io/api/#minify"&gt;minify&lt;/a&gt; and &lt;a href="https://esbuild.github.io/api/#tree-shaking"&gt;tree-shake&lt;/a&gt; the code as well. Smaller bundles can make deployments faster and may even help with cold start and execution time. However, this can make debugging difficult when we start seeing stack traces that look like this:&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;"errorType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SyntaxError"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"errorMessage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Unexpected end of JSON input"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"stack"&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;"SyntaxError: Unexpected end of JSON input"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"    at JSON.parse (&amp;lt;anonymous&amp;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;"    at VA (/var/task/index.js:25:69708)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"    at Runtime.R8 [as handler] (/var/task/index.js:25:69808)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"    at Runtime.handleOnce (/var/runtime/Runtime.js:66:25)"&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 error message here lets us know that we're failing to parse a JSON string, but the stack trace itself is useless for finding the line where the code is failing. We'll have no choice but to look through our code and hope to find the error. We might be able to do a text search for &lt;code&gt;JSON.parse&lt;/code&gt; but if that is happening in one of our dependencies, searching won't work. What's next? Add a bunch of log statements to the code? No! If we use Source Maps, we can get more useful stack traces:&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;"errorType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SyntaxError"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"errorMessage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Unexpected end of JSON input"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"stack"&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;"SyntaxError: Unexpected end of JSON input"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"    at JSON.parse (&amp;lt;anonymous&amp;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;"    at VA (/fns/db.ts:39:8)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"    at Runtime.R8 (/fns/list.ts:6:24)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"    at Runtime.handleOnce (/var/runtime/Runtime.js:66:25)"&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;Now we can see the stack includes a call from &lt;a href="https://github.com/elthrasher/lambda-sourcemaps/blob/main/fns/list.ts#L6"&gt;line 6&lt;/a&gt; of &lt;code&gt;list.ts&lt;/code&gt; which finally fails on &lt;a href="https://github.com/elthrasher/lambda-sourcemaps/blob/main/fns/db.ts#L39"&gt;line 39&lt;/a&gt; of &lt;code&gt;db.ts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To enable Source Maps in our application, we need to tell our build tool to emit Source Maps and we need to enable Source Map support in our runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Emitting Source Maps&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Emitting Source Maps is very easy with &lt;a href="https://esbuild.github.io/"&gt;esbuild&lt;/a&gt;. We simply set the boolean property in our &lt;a href="https://esbuild.github.io/api/#sourcemap"&gt;configuration&lt;/a&gt;. Now when we run the build, we'll get an &lt;code&gt;index.js.map&lt;/code&gt; file as well as our &lt;code&gt;index.js&lt;/code&gt;. This file must be uploaded to the Lambda service. We'll see how to do that with AWS CDK, AWS SAM and the Serverless Framework a bit later in this article.&lt;/p&gt;

&lt;h2&gt;
  
  
  Source Map Support&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Having the &lt;code&gt;index.js.map&lt;/code&gt; file in our Lambda runtime isn't sufficient to enable Source Maps. We also have to make sure the runtime knows to make use of them. Fortunately this is very easy ever since &lt;a href="https://nodejs.org/dist/latest-v12.x/docs/api/cli.html#cli_enable_source_maps"&gt;Node.js version 12.12.0&lt;/a&gt;. We just have to set the &lt;code&gt;--enable-source-maps&lt;/code&gt; command line option. Command line options can be set in AWS Lambda by setting the &lt;code&gt;NODE_OPTIONS&lt;/code&gt; environment variable. At the time of this writing, AWS Lambda supports Node.js versions 12 and 14. AWS does not publish the minor versions in use in Lambda, but we can discover it by logging out &lt;code&gt;process.version&lt;/code&gt; in a function. As of late January, 2022, the Node.js versions in use in Lambda in us-east-1 are &lt;code&gt;v12.22.7&lt;/code&gt; and &lt;code&gt;v14.18.1&lt;/code&gt; so we'll have no trouble using Source Maps.&lt;/p&gt;

&lt;p&gt;If we needed to enable Source Maps in a runtime that doesn't support the native version, we could always use &lt;a href="https://github.com/evanw/node-source-map-support"&gt;Source Map Support&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  CDK Example&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;All example code is available on &lt;a href="https://github.com/elthrasher/lambda-sourcemaps"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;AWS CDK is my preferred tool for writing and deploying serverless applications, in part because of the &lt;a href="https://dev.to/elthrasher/aws-cdk-aws-lambda-nodejs-module-9ic"&gt;aws-lambda-nodejs&lt;/a&gt; construct. This construct makes it very easy to work with TypeScript. It wraps esbuild and exposes options. It also supports setting environment variables.&lt;/p&gt;

&lt;p&gt;When I'm working with multiple Lambda functions, I often find it helpful to create a single props object that then gets shared among multiple functions.&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;lambdaProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;architecture&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Architecture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ARM_64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;bundling&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;minify&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="na"&gt;sourceMap&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="na"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;NODE_OPTIONS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--enable-source-maps&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;logRetention&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RetentionDays&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ONE_DAY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODEJS_14_X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;memorySize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&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;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="s1"&gt;FuncOne&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;lambdaProps&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="s2"&gt;`&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="s2"&gt;/../fns/one.ts`&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;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="s1"&gt;FuncTwo&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;lambdaProps&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="s2"&gt;`&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="s2"&gt;/../fns/two.ts`&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 we can see, it's very simple to enable Source Maps when already using AWS CDK and NodejsFunction.&lt;/p&gt;

&lt;h3&gt;
  
  
  Serverless Stack&lt;a&gt;
&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://serverless-stack.com/"&gt;Serverless Stack&lt;/a&gt; is a very cool value add that builds on top of AWS CDK delivering an awesome developer experience and dashboard. SST's own version of NodejsFunction, &lt;a href="https://docs.serverless-stack.com/constructs/Function"&gt;Function&lt;/a&gt; automatically emits source maps. Their use can be enabled simply by &lt;a href="https://docs.serverless-stack.com/advanced/source-maps"&gt;setting NODE_OPTIONS as described&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  SAM Example&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;SAM support for TypeScript has been lagging for some time, but a &lt;a href="https://github.com/aws/aws-lambda-builders/pull/307"&gt;pull request&lt;/a&gt; was just merged that should change that. It looks like we'll be able to add an &lt;a href="https://github.com/aws/aws-lambda-builders/blob/develop/aws_lambda_builders/workflows/nodejs_npm/DESIGN.md#activating-the-bundler-workflow"&gt;aws_sam&lt;/a&gt; key in the package.json file to enable building within the SAM engine as part of &lt;code&gt;sam build&lt;/code&gt;. Although this PR has been merged to aws-lambda-builders, the engine behind &lt;code&gt;sam build&lt;/code&gt;, it will still need to be added to &lt;a href="https://github.com/aws/aws-sam-cli"&gt;aws-sam-cli&lt;/a&gt; and released (to much fanfare, one expects) before it can be used with SAM.&lt;/p&gt;

&lt;p&gt;Meanwhile - or if we're considering other options for deploying our functions - we can add an extra build step. We'll create an &lt;code&gt;esbuild.ts&lt;/code&gt; &lt;a href="https://github.com/elthrasher/lambda-sourcemaps/blob/main/sam/esbuild.ts"&gt;file&lt;/a&gt; that transpiles the functions and then point our SAM &lt;code&gt;template.yaml&lt;/code&gt; file at the output of that step.&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;build&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;BuildOptions&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="s1"&gt;esbuild&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;buildOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BuildOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;bundle&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="na"&gt;entryPoints&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="s1"&gt;create/index&lt;/span&gt;&lt;span class="dl"&gt;'&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;__dirname&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/../fns/create.ts`&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="s1"&gt;delete/index&lt;/span&gt;&lt;span class="dl"&gt;'&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;__dirname&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/../fns/delete.ts`&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="s1"&gt;list/index&lt;/span&gt;&lt;span class="dl"&gt;'&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;__dirname&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/../fns/list.ts`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;minify&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="na"&gt;outbase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fns&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;outdir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sam/build&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;sourcemap&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="nx"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buildOptions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SAM templates will take everything in the &lt;code&gt;CodeUri&lt;/code&gt; so the above code will transpile a TypeScript file at &lt;code&gt;fns/list.ts&lt;/code&gt; and output &lt;code&gt;sam/build/list/index.js&lt;/code&gt; and &lt;code&gt;sam/build/list/index.js.map&lt;/code&gt;. By setting &lt;code&gt;CodeUri: sam/build/list&lt;/code&gt;, SAM will package the two files and upload them to Lambda.&lt;/p&gt;

&lt;p&gt;Now we just need to set the environment variable. This is easy enough to do in a SAM template. We can set it as a global so it only needs to be in the &lt;a href="https://github.com/elthrasher/lambda-sourcemaps/blob/main/template.yaml"&gt;template&lt;/a&gt; once&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Globals&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Function&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;NODE_OPTIONS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--enable-source-maps'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order to make sure we always build before deploying, we can add some &lt;a href="https://github.com/elthrasher/lambda-sourcemaps/blob/main/package.json#L6"&gt;npm scripts&lt;/a&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="nl"&gt;"scripts"&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;"build:lambda"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npm run clean &amp;amp;&amp;amp; ts-node --files sam/esbuild.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"clean"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rimraf cdk.out sam/build"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"deploy:sam"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npm run build:lambda &amp;amp;&amp;amp; sam deploy --template template.yaml"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"destroy:sam"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sam delete"&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;This works well enough, but does take some extra effort. SAM users are no doubt eagerly awaiting better TypeScript support.&lt;/p&gt;

&lt;h3&gt;
  
  
  Architect&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Alternately, use &lt;a href="https://arc.codes/"&gt;Architect&lt;/a&gt;. Architect is a 3rd party developer experience that builds on top of AWS SAM. Architect includes a &lt;a href="https://github.com/architect/plugin-typescript"&gt;TypeScript plugin&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Serverless Framework Example&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://www.serverless.com/"&gt;Serverless Framework&lt;/a&gt; is known for its plugin system. &lt;a href="https://floydspace.github.io/serverless-esbuild/"&gt;serverless-esbuild&lt;/a&gt; brings bundling and all the options we need to support Source Maps in TypeScript into the &lt;code&gt;sls package&lt;/code&gt; and &lt;code&gt;sls deploy&lt;/code&gt; commands.&lt;/p&gt;

&lt;p&gt;The plugin is configured in our &lt;a href="https://github.com/elthrasher/lambda-sourcemaps/blob/main/template.yaml"&gt;&lt;code&gt;serverless.yml&lt;/code&gt;&lt;/a&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;custom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;esbuild&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;bundle&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;minify&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;sourcemap&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then we just point our functions at our TypeScript handlers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;functions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fns/create.handler&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Much like SAM, Serverless lets us set global environment variables for our functions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws&lt;/span&gt;
  &lt;span class="na"&gt;lambdaHashingVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20201221&lt;/span&gt;
  &lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nodejs14.x&lt;/span&gt;
  &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;NODE_OPTIONS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--enable-source-maps'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Much like AWS CDK, this is a good experience for TypeScript developers. Those already using Serverless Framework should have an easy time adding Source Maps to their applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benchmarks&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;There's a lot of guidance against using Source Maps in production because of a supposed negative performance impact. Let's measure the admittedly-simple &lt;code&gt;list&lt;/code&gt; function and see if minification or the use of source maps has any noticeable effect.&lt;/p&gt;

&lt;p&gt;I used &lt;a href="https://github.com/mcollina/autocannon"&gt;autocannon&lt;/a&gt; to test the function at 100 concurrent executions for 30 seconds. I also used &lt;a href="https://github.com/alexcasalboni/aws-lambda-power-tuning"&gt;Lambda Power Tuning&lt;/a&gt; to find the ideal memory configuration, which proved to be 512MB. All the results are &lt;a href="https://github.com/elthrasher/lambda-sourcemaps/blob/main/BENCHMARKS.md"&gt;available&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Not Minified without Source Maps&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;The unminified function is 1.2MB in size. This is mostly from &lt;a href="https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-dynamodb/index.html"&gt;&lt;code&gt;@aws-sdk/client-dynamodb&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/modules/_aws_sdk_lib_dynamodb.html"&gt;&lt;code&gt;@aws-sdk/lib-dynamodb&lt;/code&gt;&lt;/a&gt;. Whether sticking with SDK v2 might be better performance would be a good topic for another post. This function is over one MB, despite a small amount of custom code.&lt;/p&gt;

&lt;p&gt;Running the test, the function has an average execution of 46.99ms and a max of 914ms. 99% of my requests are at or below 90ms.&lt;/p&gt;

&lt;h3&gt;
  
  
  Minified without Source Maps&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Minifying the function drops the size to 534.8kb, less than half the size. My test showed an average response of 47.83ms, a max of 1010ms and 90ms at the 99th percentile. This is slightly worse, but not statistically significant. I expect if I ran these tests over and over again, I would see that minifying the code has no real effect on performance. This is not a surprise. 1.2MB is still fairly small and I don't expect to see much in the way of added latency at that size.&lt;/p&gt;

&lt;h3&gt;
  
  
  Minified with Source Maps&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;The minified function is still 534.8kb, of course, but the Source Map is 1.5MB so this will be the largest upload, not that ~2MB is a lot or will significantly slow down our deployments.&lt;/p&gt;

&lt;p&gt;The average response time for this test is 46.52ms, max is 968mb and the 99th percentile is 82ms. This is the best result so far, but not statistically significant. I would say this is truly a three-way tie which tells us that adding Source Maps to this function did not increase latency.&lt;/p&gt;

&lt;p&gt;That's because of the native support in Node.js! Since no stack traces were emitted, the Source Maps were never referenced. It's possible if we had to rely on a library for this capability, we wouldn't have the same outcome.&lt;/p&gt;

&lt;h3&gt;
  
  
  Error Minified without Source Maps&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Will triggering error conditions make a difference? Let's see. I ran the same test but this time the function has that &lt;code&gt;JSON.parse&lt;/code&gt; error in it. This happens before the call to DynamoDB, so we can expect it to be a little faster than the successful function. We get 37.86ms as the average, 1004ms as the max and 58ms at 99%. It's impressive the call to DynamoDB only seems to add about 10ms of latency!&lt;/p&gt;

&lt;h3&gt;
  
  
  Error Minified with Source Maps&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Enabling Source Maps for the errors does impact performance. Our average has dropped to 97.54ms, max at 1129ms and 99% is 243. This is a significant increase. Source Maps &lt;em&gt;do&lt;/em&gt; impact latency when an error occurs. This makes sense and confirms the idea that the Source Maps are only referenced when an error occurs - but now that Source Map must be parsed and that takes time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Use Source Maps in production! In my view, if you are getting so many errors that the performance hit from Source Maps is impacting your bottom line, you should probably go and fix those errors. It's very easy to implement Source Maps in a variety of popular Lambda frameworks and they don't impact successful execution. When functions do fail, the useful stack trace is going to be worth the added latency. Developer hours are always going to be more expensive than a few milliseconds of Lambda execution.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Carta_marina"&gt;COVER&lt;/a&gt;&lt;/p&gt;

</description>
      <category>lambda</category>
      <category>aws</category>
      <category>typescript</category>
      <category>serverless</category>
    </item>
    <item>
      <title>First Look at Lambda Powertools TypeScript</title>
      <dc:creator>Matt Morgan</dc:creator>
      <pubDate>Mon, 10 Jan 2022 12:41:01 +0000</pubDate>
      <link>https://dev.to/aws-builders/first-look-at-lambda-powertools-typescript-2k3p</link>
      <guid>https://dev.to/aws-builders/first-look-at-lambda-powertools-typescript-2k3p</guid>
      <description>&lt;p&gt;AWS Senior Solutions Architect &lt;a href="https://twitter.com/Sarutule" rel="noopener noreferrer"&gt;Sara Gerion&lt;/a&gt; announced on January 5, 2022 that &lt;a href="https://awslabs.github.io/aws-lambda-powertools-typescript/latest/" rel="noopener noreferrer"&gt;Lambda Powertools TypeScript&lt;/a&gt; had &lt;a href="https://twitter.com/Sarutule/status/1478763457625350144" rel="noopener noreferrer"&gt;entered public beta&lt;/a&gt;. Lambda Powertools is an AWS-sponsored open source project with the goal of improving developer experience and adoption of best practices when using AWS Lambda. Lambda Powertools TypeScript joins &lt;a href="https://awslabs.github.io/aws-lambda-powertools-java/" rel="noopener noreferrer"&gt;Java&lt;/a&gt; and &lt;a href="https://awslabs.github.io/aws-lambda-powertools-python/latest/" rel="noopener noreferrer"&gt;Python&lt;/a&gt; Lambda Powertools libraries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Node.js Tooling for Lambda&lt;/li&gt;
&lt;li&gt;Decorators&lt;/li&gt;
&lt;li&gt;No Decorators&lt;/li&gt;
&lt;li&gt;Capabilities&lt;/li&gt;
&lt;li&gt;Tracer&lt;/li&gt;
&lt;li&gt;Logger&lt;/li&gt;
&lt;li&gt;Metrics&lt;/li&gt;
&lt;li&gt;Package Size&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Node.js Tooling for Lambda&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;I'm a big fan of TypeScript and in fact &lt;a href="https://www.amazon.com/gp/product/B093Y29GW3" rel="noopener noreferrer"&gt;co-authored a book&lt;/a&gt; about it. I don't find myself using Java or Python much, so while I've been interested in Lambda Powertools, I never tried it out until now. Lambda Powertools TypeScript joins &lt;a href="https://middy.js.org/" rel="noopener noreferrer"&gt;middy&lt;/a&gt; and &lt;a href="https://github.com/getndazn/dazn-lambda-powertools" rel="noopener noreferrer"&gt;DAZN Lambda Powertools&lt;/a&gt; in the Lambda tooling space for the Node.js runtime. Two things that differentiate Lambda Powertools TypeScript from comparable libraries are it is sponsored by AWS and it supports decorators.&lt;/p&gt;

&lt;p&gt;Lambda Powertools TypeScript supports both v2 and v3 of the AWS SDK for JavaScript and examples are given for both versions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decorators&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Opinions vary on decorators, but I find them to be useful abstractions and have used them heavily in object-oriented TypeScript. However, there's a fairly major limitation in TypeScript and that is that we can put decorators on class methods, but not functions. This means, unfortunately, code that looks like this is out of reach for now.&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;// This doesn't work :(&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;doSomethingGreat&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="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="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;It's a pity because that would be an excellent developer experience. To attach the &lt;code&gt;doSomethingGreat&lt;/code&gt; decorator to our handler, we need to write something like this instead.&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;class&lt;/span&gt; &lt;span class="nc"&gt;Lambda&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;doSomethingGreat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;handler&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handlerClass&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;Lambda&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="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;handlerClass&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;It's five extra lines - perhaps not a big deal. In any case, there's not a lot the Powertools team can do as it may well be years &lt;a href="https://github.com/microsoft/TypeScript/issues/7318" rel="noopener noreferrer"&gt;before decorators are supported&lt;/a&gt; for functions. Keep in mind if we do choose to use decorators and classes in Lambda, we need to be careful around the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this" rel="noopener noreferrer"&gt;this reference&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Decorators and TypeScript aren't supported out of the box in Lambda (without using &lt;a href="https://github.com/hayd/deno-lambda" rel="noopener noreferrer"&gt;deno&lt;/a&gt;) so we'll also need a transpilation step if we go this route. Fortunately this is a mostly solved problem for &lt;a href="https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_lambda_nodejs-readme.html" rel="noopener noreferrer"&gt;AWS CDK&lt;/a&gt;, &lt;a href="https://aws.amazon.com/blogs/mt/introducing-typescript-support-for-building-aws-cloudformation-resource-types/" rel="noopener noreferrer"&gt;AWS SAM&lt;/a&gt; and &lt;a href="https://www.serverless.com/plugins/serverless-plugin-typescript" rel="noopener noreferrer"&gt;Serverless Framework&lt;/a&gt; users. If you want or need to roll your own, &lt;a href="https://esbuild.github.io/" rel="noopener noreferrer"&gt;esbuild&lt;/a&gt; is a great place to start and seems to be the bundler of choice for this purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  No Decorators&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;We may wish to opt out of using classes or transpilers. Lambda Powertools TypeScript can be used without decorators and in fact without TypeScript. We could use the library with vanilla Node.js.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://awslabs.github.io/aws-lambda-powertools-typescript/latest/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; for Lambda Powertools TypeScript is pretty good and provides several examples with even more &lt;a href="https://github.com/awslabs/aws-lambda-powertools-typescript/tree/main/examples/cdk" rel="noopener noreferrer"&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Capabilities&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;All three versions of Lambda Powertools include Metrics, Logger and Tracer as core utilities. &lt;a href="https://awslabs.github.io/aws-lambda-powertools-python/latest/" rel="noopener noreferrer"&gt;Lambda Powertools Python&lt;/a&gt; includes an Event Handler and several other useful utilities supporting Batch Processing, Idempotency, Validation and more.&lt;/p&gt;

&lt;p&gt;Each version of Lambda Powertools is developed independently and the capabilities are custom-tailored to the distinct needs of the different runtimes. This is in contrast to AWS CDK using &lt;a href="https://github.com/aws/jsii" rel="noopener noreferrer"&gt;jsii&lt;/a&gt; to publish the same constructs to multiple runtimes. Although it may be frustrating to wait for some of these capabilities, this is likely the right approach as it would just add to the complexity to think about generic code that compiles to something that can decorate custom code supported in multiple runtimes.&lt;/p&gt;

&lt;p&gt;To test this out, I've implemented all three of the Lambda Powertools TypeScript utilities in one of my sample projects. I chose my &lt;a href="https://dev.to/aws-builders/testing-the-async-cloud-with-aws-cdk-33aj"&gt;CDK Async Testing&lt;/a&gt; project because it contains several Lambda functions and includes asynchronous workflows via EventBridge and Step Functions.&lt;/p&gt;

&lt;p&gt;If you want to see my instrumented code, it is available in &lt;a href="https://github.com/elthrasher/cdk-async-testing-example/tree/powertools" rel="noopener noreferrer"&gt;this branch&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tracer&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;In order to instrument my functions with the Tracer, I needed to rewrite them as classes. I chose to use decorators because I'm really undecided about whether to start using classes everywhere, so I need to get a feel for it. Starting off with &lt;code&gt;collect.ts&lt;/code&gt;, here's the function as it was originally at eleven lines of 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;Payment&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="s1"&gt;../models/payment&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="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;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;Payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Payment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Payment&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;Payment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Payment&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;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;min&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&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;max&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&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;Status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&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="nx"&gt;max&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;min&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;min&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;Status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;Payment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Payment&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's the refactor to include the Tracer.&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;Tracer&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="s1"&gt;@aws-lambda-powertools/tracer&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="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;LambdaInterface&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="s1"&gt;@aws-lambda-powertools/commons&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="kd"&gt;type&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="s1"&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="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Payment&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="s1"&gt;../models/payment&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;tracer&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;Tracer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;serviceName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;paymentCollections&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Lambda&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;LambdaInterface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;captureLambdaHandler&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;Payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Payment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Payment&lt;/span&gt; &lt;span class="p"&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="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;Payment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Payment&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;min&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&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;max&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&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;Status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&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="nx"&gt;max&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;min&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;min&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;Status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;Payment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Payment&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;handlerClass&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;Lambda&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="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;handlerClass&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;The function is now 25 lines of code, which isn't horrible. It ends up at 33 lines after adding in the Metrics and Logger utilities. Of course it's expected that my function will be larger as it has more capabilities now. We should see the increase as additive, not multiplicative. My 11 line function became 25 lines. Had it originally been 111 lines, it would've grown to 125, not doubled.&lt;/p&gt;

&lt;p&gt;So what do we get for that? The Tracer module wraps the &lt;a href="https://github.com/aws/aws-xray-sdk-node" rel="noopener noreferrer"&gt;AWS X-Ray SDK&lt;/a&gt; (as a transitive dependency). It doesn't really add any net new capabilities, but makes the SDK easier to work with. In my experience, that SDK is a bit of a bear so this may be well worth it. We can decorate class methods to introduce new trace segments in a single line of code. We can also use the imperative form to add new traces where we see fit. We can capture AWS clients, but that simply exposes the X-Ray SDK.&lt;/p&gt;

&lt;p&gt;One thing that isn't solved here (yet?) is the oddness of working with the X-Ray SDK and DynamoDB DocumentClient. When using DocumentClient with X-Ray, we need to do a workaround as the SDK needs access to the DocumentClient service property, but that's not exposed on the type. Here's how I've solved that problem in the past.&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="nx"&gt;DynamoDB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;DocumentClient&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="s1"&gt;aws-sdk/clients/dynamodb&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;captureAWSClient&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="s1"&gt;aws-xray-sdk-core&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="nc"&gt;DocumentClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nf"&gt;captureAWSClient&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;DocumentClient&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&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="nx"&gt;DynamoDB&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;UPDATE!&lt;a&gt;&lt;/a&gt; This was solved in &lt;a href="https://github.com/awslabs/aws-lambda-powertools-typescript/releases/tag/v0.5.0" rel="noopener noreferrer"&gt;release 0.5.0&lt;/a&gt;! The above code can now be written as:&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;DocumentClient&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="s1"&gt;aws-sdk/clients/dynamodb&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;captureAWSClient&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="s1"&gt;aws-xray-sdk-core&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="nc"&gt;DocumentClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nf"&gt;captureAWSClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Very much appreciate the quick turnaround on this improvement! Now that I have better DX, here's what my instrumented app looks like.&lt;/p&gt;

&lt;p&gt;
  original text
  &lt;br&gt;
And now with Lambda Powertools TypeScript.&lt;br&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;DynamoDB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;DocumentClient&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="s1"&gt;aws-sdk/clients/dynamodb&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;Tracer&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="s1"&gt;@aws-lambda-powertools/tracer&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;tracer&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;Tracer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;serviceName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;paymentCollections&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="nc"&gt;DocumentClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;captureAWSClient&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;DocumentClient&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&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="nx"&gt;DynamoDB&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;So basically the same thing. The advantage of this utility will really only come into play vs just using the X-Ray SDK if we're adding several segments to our code. I don't think I'm unlocking that in my sample application, but I did enable tracing in all of my functions and my state machine and the result is pretty good.&lt;br&gt;
&lt;/p&gt;

&lt;br&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%2Fvtw31ru7xj7nq77s6ytm.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%2Fvtw31ru7xj7nq77s6ytm.png" alt="ServiceLens Map" width="800" height="504"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Not only do I get this great service map, I also get detailed traces.&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%2Ff6mlay2n1c3z86pxjtbi.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%2Ff6mlay2n1c3z86pxjtbi.png" alt="Trace #1" width="800" height="518"&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%2Ffn0c6pso1cu34992n3id.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%2Ffn0c6pso1cu34992n3id.png" alt="Trace #2" width="800" height="523"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Tracer module is adding the &lt;code&gt;## index.handler&lt;/code&gt; part seen on these screenshots. I'll want to add more traces to really take advantage of this tool. Overall getting these detailed traces of my entire application through all the functions and services is quite impressive and useful. X-Ray is doing most of the work, but a better developer experience means we'll wind up with more applications instrumented properly and that's certainly a good thing.&lt;/p&gt;

&lt;p&gt;Getting ahead of myself slightly here, but traces also include logs and logs for the instrumented segments will be found alongside the traces in CloudWatch.&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%2F9dhus5xdbec96cxbo5ix.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%2F9dhus5xdbec96cxbo5ix.png" alt="Trace logs" width="800" height="306"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Again, this is great. I'm able to write my application in a distributed way that uses single-purpose functions, but see the whole picture when it executes.&lt;/p&gt;

&lt;p&gt;X-Ray can be a fairly cheap service to use provided we remember to set a sampling rate on high-throughput applications. Pricing seems to be based on trace so adding additional segments to a trace doesn't add to the cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Logger&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The Logger utility is a drop-in replacement for any logger, including console. The value add Logger brings is the ability to inject the Lambda context into each logging message. When we annotate our handler method with &lt;code&gt;@logger.injectLambdaContext()&lt;/code&gt;, then use logger.info, we'll see log messages that look like this.&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;"cold_start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"function_arn"&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:lambda:us-east-1:1234567890:function:openCollection"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"function_memory_size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"function_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;"openCollection"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"function_request_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;"df21844d-f4b6-49b4-b246-da54183ce5cf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"INFO"&lt;/span&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;"Payment set to collection"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"paymentCollections"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2022-01-09T18:46:20.622Z"&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;If we plan to ingest logs into a search index or even if we just want to use CloudWatch Logs Insights, this can be really handy as the structure will help us to search and filter log messages. On the other hand, if we're just going through a few log messages, this can be a bit noisy. We should keep in mind that any log service (including CloudWatch) is going to bill on volume and extremely verbose logs can be expensive.&lt;/p&gt;

&lt;p&gt;With that in mind, there are a lot of good features for the Logger utility and we can structure our logs however we like. Additionally Logger includes a sampling rate feature which can be used to keep costs down.&lt;/p&gt;

&lt;p&gt;By default logger methods take one or more arguments. The first argument needs to be a string or object with a message key. I noticed that if I gave a string as a subsequent argument, the string would be converted into a character array and printed like that, so that's something to watch out for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Metrics&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The Metrics utility is used to publish custom CloudWatch metrics. Although Lambda automatically publishes a number of useful metrics like latency, concurrent executions, throttling, etc., custom metrics give us the opportunity to complete the observability story by adding relevant business events to our metrics.&lt;/p&gt;

&lt;p&gt;Tracking reliability is important, but it &lt;em&gt;does not tell the whole story!&lt;/em&gt; If anything, custom metrics should be the most important ones. How many customers signed up this week? How many of them were able to complete valuable workflows? The answers to these questions lie in our code and if we emit custom metrics, they can also be in our dashboards.&lt;/p&gt;

&lt;p&gt;Custom metrics have a &lt;a href="https://aws.amazon.com/cloudwatch/pricing/" rel="noopener noreferrer"&gt;pricing structure&lt;/a&gt; which can be expensive. &lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format.html" rel="noopener noreferrer"&gt;Embedded Metrics Format&lt;/a&gt; can help manage the cost and is supported by Lambda Powertools TypeScript. Again, &lt;a href="https://awslabs.github.io/aws-lambda-powertools-typescript/latest/core/metrics/" rel="noopener noreferrer"&gt;the docs&lt;/a&gt; here are pretty good, so no need for me to break it down. Instead let's look at the experience. I've added a custom metric of "collectionSuccess" to my collectionSuccess function. In my hypothetical app, some payments wind up in collections and here I'm marking whether or not the collection resulted in a payment.&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;Logger&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="s1"&gt;@aws-lambda-powertools/logger&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;Metrics&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MetricUnits&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="s1"&gt;@aws-lambda-powertools/metrics&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;Tracer&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="s1"&gt;@aws-lambda-powertools/tracer&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;PaymentEntity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PaymentStatus&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="s1"&gt;../models/payment&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="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;LambdaInterface&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="s1"&gt;@aws-lambda-powertools/commons&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="kd"&gt;type&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="s1"&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="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Payment&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="s1"&gt;../models/payment&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;powerToolsConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;payments&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;serviceName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;paymentCollections&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;logger&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;Logger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;powerToolsConfig&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;metrics&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;Metrics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;powerToolsConfig&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;tracer&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;Tracer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;powerToolsConfig&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Lambda&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;LambdaInterface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;injectLambdaContext&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logMetrics&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;captureColdStartMetric&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="nd"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;captureLambdaHandler&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;Payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Payment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Payment&lt;/span&gt; &lt;span class="p"&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;PaymentEntity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&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="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Payment&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="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PaymentStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COLLECTION_SUCCESS&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="nx"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addMetric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;collectionSuccess&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MetricUnits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&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="nx"&gt;logger&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="s1"&gt;Failed to record collection success&lt;/span&gt;&lt;span class="dl"&gt;'&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="k"&gt;throw&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="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;handlerClass&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;Lambda&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="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;handlerClass&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;Adding &lt;code&gt;@metrics.logMetrics&lt;/code&gt; will cause any metrics we emit to also be logged to CloudWatch. We may or may not want that (keeping costs in mind again). To add a custom metric, we simply use &lt;code&gt;metrics.addMetric&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I instrumented my app to emit metrics for cold starts as well as some custom metrics that describe important events in my application, such as successful and failed payments and collections. Because the point of my application is to demonstrate an integration test, I also instrumented custom metrics that indicate when the test is run.&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%2Foiyplsqdt5p9w91gngg6.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%2Foiyplsqdt5p9w91gngg6.png" alt="CloudWatch Metrics" width="800" height="153"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These metrics can be found in CloudWatch Metrics, placed in a dashboard or exported via API to a 3rd party tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Package Size&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Adding all of the utilities to my project seemed to add about 600kb unminified or 200kb to minified bundles. Given the value and the need to chain some dependencies into the AWS SDK or X-Ray SDK, this seems quite reasonable and the team has done a good job of staying with their &lt;strong&gt;Keep it lean&lt;/strong&gt; tenent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Lambda Powertools does an excellent job putting focus on the kinds of utilities developers really need to improve their applications and follow best practices. The core modules here all focus on observability and that emphasis is needed and appreciated. The team has done a good job developing an API that will be attractive to developers who want to use decorators as well as those who don't.&lt;/p&gt;

&lt;p&gt;I eagerly await the general availability of this library and will follow the &lt;a href="https://github.com/awslabs/aws-lambda-powertools-roadmap/projects/1" rel="noopener noreferrer"&gt;roadmap&lt;/a&gt; to see what's coming up and how I can get involved.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://publicdomainreview.org/essay/revolutionary-colossus" rel="noopener noreferrer"&gt;COVER&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>typescript</category>
      <category>lambda</category>
    </item>
    <item>
      <title>Testing the Async Cloud with AWS CDK</title>
      <dc:creator>Matt Morgan</dc:creator>
      <pubDate>Mon, 13 Sep 2021 11:30:28 +0000</pubDate>
      <link>https://dev.to/aws-builders/testing-the-async-cloud-with-aws-cdk-33aj</link>
      <guid>https://dev.to/aws-builders/testing-the-async-cloud-with-aws-cdk-33aj</guid>
      <description>&lt;p&gt;One of the best optimizations we can make when adopting cloud and serverless technologies is to take advantage of asynchronous processing. Services like &lt;a href="https://aws.amazon.com/eventbridge/" rel="noopener noreferrer"&gt;EventBridge&lt;/a&gt;, &lt;a href="https://aws.amazon.com/step-functions/" rel="noopener noreferrer"&gt;Step Functions&lt;/a&gt;, &lt;a href="https://aws.amazon.com/sns/" rel="noopener noreferrer"&gt;SNS&lt;/a&gt; and &lt;a href="https://aws.amazon.com/sqs/" rel="noopener noreferrer"&gt;SQS&lt;/a&gt; can form the backbone of highly scalable, reliable, cost-effective and performant job processing. Cloud services like these mean we no longer have to worry about a single process going out of memory or a spike in traffic causing critical business functions to fail when we need them most.&lt;/p&gt;

&lt;p&gt;Adopting asynchronous architectures introduces new challenges around test automation. Nearly all automation testing is built around the idea of call and response synchronous processing. We call the web server and test the html string returned. We POST to the &lt;code&gt;/widgets&lt;/code&gt; endpoint, receive a 200 OK response and then can GET our new widget. Tools like &lt;a href="https://www.selenium.dev/" rel="noopener noreferrer"&gt;Selenium&lt;/a&gt; and &lt;a href="https://www.cypress.io/" rel="noopener noreferrer"&gt;Cypress&lt;/a&gt; aren't suited to workflows that involve asynchronous background processing. At best, you can fire an event and then poll for the eventual desired result.&lt;/p&gt;

&lt;p&gt;While the idea of testing frameworks for asynchronous processing hasn't really hit the mainstream, there are some great articles out there. I highly recommend following the work of &lt;a href="https://medium.com/serverless-transformation/bridge-integrity-integration-testing-strategy-for-eventbridge-based-serverless-architectures-b73529397251" rel="noopener noreferrer"&gt;Sarah Hamilton&lt;/a&gt; and &lt;a href="https://serverlessfirst.com/eventbridge-testing-guide/" rel="noopener noreferrer"&gt;Paul Swail&lt;/a&gt; and their approaches to solving this problem using libraries like &lt;a href="https://github.com/erezrokah/aws-testing-library" rel="noopener noreferrer"&gt;aws-testing-library&lt;/a&gt; and &lt;a href="https://github.com/Theodo-UK/sls-test-tools" rel="noopener noreferrer"&gt;sls-test-tools&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;These approaches share some of the same drawbacks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;They rely on an external credential or key to access the AWS account in question.&lt;/li&gt;
&lt;li&gt;They require a wait or sleep step to allow AWS time to process the asynchronous event.&lt;/li&gt;
&lt;li&gt;There's no real CI/CD integration happening. A failing test won't cause a deployment to roll back.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Read enough already? Check out my &lt;a href="https://github.com/elthrasher/cdk-async-testing-example" rel="noopener noreferrer"&gt;source code&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;External Credentials&lt;/li&gt;
&lt;li&gt;Don't Sleep&lt;/li&gt;
&lt;li&gt;Automatic Rollback&lt;/li&gt;
&lt;li&gt;Custom Resource Provider Framework for Test&lt;/li&gt;
&lt;li&gt;Payment Collections App&lt;/li&gt;
&lt;li&gt;Test Event&lt;/li&gt;
&lt;li&gt;Complete Event&lt;/li&gt;
&lt;li&gt;Provider&lt;/li&gt;
&lt;li&gt;Test Run&lt;/li&gt;
&lt;li&gt;Bonus: EventBridgeWebSocket&lt;/li&gt;
&lt;li&gt;Infrastructure Testing&lt;/li&gt;
&lt;li&gt;Next Steps&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  External Credentials&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;When writing tests using something like &lt;a href="https://github.com/erezrokah/aws-testing-library" rel="noopener noreferrer"&gt;aws-testing-library&lt;/a&gt; or &lt;a href="https://github.com/Theodo-UK/sls-test-tools" rel="noopener noreferrer"&gt;sls-test-tools&lt;/a&gt;, we must have credentials to an AWS account that at least lets us send a few events and subscribe to or query the results in order to perform assertions. Depending on the stance of our organization about cloud access, this could be completely fine or it could be a never-gonna-happen dealbreaker. Often this kind of approach will be OK for a development environment, but it could be unlikely to fly in production.&lt;/p&gt;

&lt;p&gt;We may be restricted to running such tests in a CI/CD pipeline. That could get us past a security review, but it might also slow the feedback loop of writing tests, resulting in fewer overall tests written.&lt;/p&gt;

&lt;p&gt;The safest way to solve this problem is to remove external access from the equation. If we run our tests from a resource within the AWS account, such as a Lambda function, then while we still need normal IAM permissions, we don't need to create any additional external roles. Tests can be run in production without violating the least privilege principle of giving our deployment role the minimum grants required to create the resources we need.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't Sleep&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Sleep or wait statements are the bane of test automation. When a test fails, can we "fix" it by increasing the timeout? Does doing so reflect the normal operation of using cloud or have we introduced some instability to our system that is now being normalized by making the tests take longer?&lt;/p&gt;

&lt;p&gt;In the end, we have to pin our sleep statement to the longest our process can possibly take or decide to be tolerant of test failures. We can mitigate this problem by using polling instead of sleep. To be fair, the solutions given by Sarah and Paul don't preclude polling, but you are on your own to implement it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automatic Rollback&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Once we have a really good automation suite, we should wish to connect it to our CI/CD process such that if the tests fail, we roll back the deployment. Assuming we are using AWS and CloudFormation, the only lever we really have to pull there is to attach an alarm to a test suite. This could be achieved by having the test publish custom metrics that trigger the alarm and a potential rollback. This would be yet another permission needed to our test runner role and isn't something natively supported by any testing library.&lt;/p&gt;

&lt;h2&gt;
  
  
  Custom Resource Provider Framework for Test&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;This is &lt;a href="https://dev.to/elthrasher/exploring-aws-cdk-a-million-a-minute-dynamodb-and-providerframework-e92"&gt;not the first time&lt;/a&gt; I've written about &lt;a href="https://docs.aws.amazon.com/cdk/api/latest/docs/custom-resources-readme.html#provider-framework" rel="noopener noreferrer"&gt;Provider Framework&lt;/a&gt;. I think the CDK implementation of &lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html" rel="noopener noreferrer"&gt;CloudFormation Custom Resources&lt;/a&gt; is one of the coolest things I've come across recently. I'm constantly impressed by the things I can do with it. Let's examine how it solves some of the problems I've outlined above.&lt;/p&gt;

&lt;p&gt;Our tests will be executed by Lambda functions instead of via some external tool. These functions will still need normal IAM roles to access the resources needed to perform the test, but the functions themselves are not accessible to any resource or role external from the account. This means we can run tests in production without risking any data exposure.&lt;/p&gt;

&lt;p&gt;We get built-in polling in the &lt;a href="https://docs.aws.amazon.com/cdk/api/latest/docs/custom-resources-readme.html#asynchronous-providers-iscomplete" rel="noopener noreferrer"&gt;isComplete&lt;/a&gt; step. This is a Lambda function that can be executed at &lt;code&gt;queryInterval&lt;/code&gt; that can perform some sort of assertion and then return a boolean value indicating whether the test is complete or we should continue polling.&lt;/p&gt;

&lt;p&gt;Provider Framework also has built-in &lt;a href="https://docs.aws.amazon.com/cdk/api/latest/docs/custom-resources-readme.html#handling-provider-framework-error" rel="noopener noreferrer"&gt;error-handling&lt;/a&gt;. Throwing any error will pass a "FAILED" response to CloudFormation, which in turn will trigger a rollback.&lt;/p&gt;

&lt;p&gt;These advantages are balanced by there not being a good assertion library ready to use in a Lambda function and by the potential for a poor developer experience if we find ourselves debugging the custom resource itself and having to perform frequent deployments to write a test. I think those problems can be solved with some additional tooling, but nothing exists to date.&lt;/p&gt;

&lt;h2&gt;
  
  
  Payment Collections App&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;So let's see a test in action. The design of this application is that we receive an asynchronous notification from our payment provider indicating whether or not a payment was successful. If the payment was successful, we update our database to indicate that. Otherwise we trigger a collections workflow.&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%2Fv9vcbee6kx4dfkcp64db.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%2Fv9vcbee6kx4dfkcp64db.png" alt="Payment Collections Architecture"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Payment events (success or failure) come to us via EventBridge. Our Collections workflow is managed by Step Functions. The record of payment and current status is stored in DynamoDB.&lt;/p&gt;

&lt;p&gt;The scenarios we'd like to test are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Receive a successful payment event and record it in our table.&lt;/li&gt;
&lt;li&gt;Receive a failed payment event and kick off the collections workflow, run the collection, and record the eventual result in our table.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Test Event&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;To write the test we start with a Lambda function that will initiate the events we want to test. In this case, we're going to send two events to our event bus. Here is a TypeScript function that will do exactly that.&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="nx"&gt;EventBridge&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-sdk/clients/eventbridge&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="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CloudFormationCustomResourceEvent&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="s1"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;eb&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;EventBridge&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="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;CloudFormationCustomResourceEvent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;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;if &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;RequestType&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Delete&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;return&lt;/span&gt;&lt;span class="p"&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Version&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&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;ResourceProperties&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;events&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="s1"&gt;success&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;failure&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;status&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;eb&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;putEvents&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;Entries&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;EventBusName&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;BUS_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;payments&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;DetailType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;Time&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;Date&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
              &lt;span class="na"&gt;Detail&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;id&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;Version&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;status&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="p"&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;promise&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&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="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="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;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Integration Test failed!&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;We're able to take advantage of the &lt;code&gt;CloudFormationCustomResourceEvent&lt;/code&gt; type from the &lt;code&gt;@types/aws-lambda&lt;/code&gt; package. Because Custom Resources support the full lifecycle of CloudFormation, we need to evaluate "Delete" as a no-op. We don't want the test to run if the stack is being deleted, as it would obviously fail since the necessary resources won't exist.&lt;/p&gt;

&lt;p&gt;The rest of the function simply uses &lt;code&gt;putEvents&lt;/code&gt; to propagate two test events, a success and a failure. Note that we are pulling the &lt;code&gt;Version&lt;/code&gt; from the Custom Resource and passing that as the event detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Complete Event&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The complete event will be called at the interval specified in our code (defaulting to 5 seconds) until it returns a positive result, throws an error or &lt;code&gt;totalTimeout&lt;/code&gt; (default 30 minutes and max of two hours) elapses. As before, if this is a stack deletion event, we want the test to just pass as our business logic shouldn't be under test. If we're undergoing a create or update event, then we'll want to query the database to see if the job is finished yet. We are using the same &lt;code&gt;Version&lt;/code&gt; from the original Custom Resource to make sure we can query the same item in the table.&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;CloudFormationCustomResourceEvent&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="s1"&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;PaymentEntity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PaymentStatus&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="s1"&gt;../models/payment&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="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;CloudFormationCustomResourceEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Result&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="nl"&gt;IsComplete&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;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;if &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;RequestType&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Delete&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;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;IsComplete&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Version&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&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;ResourceProperties&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="c1"&gt;// Query the DynamoDB table to get the meta record. If it has some processed records&lt;/span&gt;
    &lt;span class="c1"&gt;// and the processed count is equal to validated count, the test passes.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;successResponse&lt;/span&gt; &lt;span class="o"&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;PaymentEntity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-success`&lt;/span&gt; &lt;span class="p"&gt;})).&lt;/span&gt;&lt;span class="nx"&gt;Item&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;failureResponse&lt;/span&gt; &lt;span class="o"&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;PaymentEntity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-failure`&lt;/span&gt; &lt;span class="p"&gt;})).&lt;/span&gt;&lt;span class="nx"&gt;Item&lt;/span&gt; &lt;span class="o"&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Success Response: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;successResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&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="s1"&gt;Failure Response: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;failureResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&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;IsComplete&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="nx"&gt;successResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;PaymentStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SUCCESS&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;PaymentStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COLLECTION_FAILURE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PaymentStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COLLECTION_SUCCESS&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;failureResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;IsComplete&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Payment &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-success finished with status &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;successResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; and payment &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-failure finished with status &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;failureResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&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="nx"&gt;IsComplete&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;IsComplete&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="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;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;throw&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="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function must return an object with &lt;code&gt;IsComplete&lt;/code&gt; and a boolean value. If &lt;code&gt;IsComplete&lt;/code&gt; is true, then it may also include a &lt;code&gt;Data&lt;/code&gt; attribute with a JSON payload. In this case I've defined that as &lt;code&gt;{ Result: string }&lt;/code&gt;. My intent is to print that string in the console to provide some detail on the test.&lt;/p&gt;

&lt;p&gt;Note that we lack the convenience of a nice assertion library, but this example is simple enough to get by with imperative code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Provider&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;We need to write a little CDK code to make all this work. In this case, I wrote the entire integration test as a &lt;a href="https://github.com/elthrasher/cdk-async-testing-example/blob/main/cdk/integration-test-stack.ts" rel="noopener noreferrer"&gt;nested stack&lt;/a&gt;, which gives it a nice isolation from the actual application. In addition to just organizing our code an potentially avoiding stack limits, this also lets us hedge our best a little by making it easy to disable the nested stack in the event of a test failure blocking a critical deployment.&lt;/p&gt;

&lt;p&gt;The stack creates the functions and grants necessary permissions to put events to EventBridge and query DynamoDB. Over the course of the test, we'll see a few events come across EventBridge and also trigger Step Functions. Tests to trigger other asynchronous workflows using SNS or SQS can be done in a similar fashion. Adding the functions to Provider Framework only takes a few lines of 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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;intTestProvider&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;Provider&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="s1"&gt;IntTestProvider&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;isCompleteHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;logRetention&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RetentionDays&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ONE_DAY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;onEventHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;totalTimeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&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;testResource&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;CustomResource&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="s1"&gt;IntTestResource&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;properties&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;serviceToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;intTestProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;serviceToken&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 pass in our two handlers to the &lt;code&gt;Provider&lt;/code&gt; construct, then create a &lt;code&gt;CustomResource&lt;/code&gt; that uses the service token from the Provider. We are also establishing that &lt;code&gt;Version&lt;/code&gt; property which will be the current timestamp in milliseconds as a string. This can serve as a unique key, assuming two tests don't kick off in the same millisecond. If we think that might happen, then using some kind of uuid generator would be more appropriate.&lt;/p&gt;

&lt;p&gt;It's important that we include some kind of unique value here because if we don't then update events will not detect any change and will not run our test! Setting some kind of unique value here is critical.&lt;/p&gt;

&lt;p&gt;Finally the &lt;code&gt;testResource&lt;/code&gt; is stored as a member of the &lt;code&gt;IntegrationTestStack&lt;/code&gt; so that we can access the output and print it to the console from the main stack.&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;new&lt;/span&gt; &lt;span class="nc"&gt;CfnOutput&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="s1"&gt;IntTestResult&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;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;intTestStack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;testResource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Result&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;h2&gt;
  
  
  Test Run&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;With all that done, we can now deploy our application as normal, whether that's a &lt;code&gt;cdk deploy&lt;/code&gt; from our laptop or something more sophisticated like a CI/CD pipeline.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Outputs:
payments-app-stack.IntTestResult &lt;span class="o"&gt;=&lt;/span&gt; Payment 1631477097557-success finished with status SUCCESS and payment 1631477097557-failure finished with status COLLECTION_FAILURE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2F1svrbf287x4r1aj7fnst.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%2F1svrbf287x4r1aj7fnst.png" alt="Payments in DynamoDB"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can check out the visualization of a few step function runs.&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%2Fl3zaoz9dojntthciu91b.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%2Fl3zaoz9dojntthciu91b.png" alt="Step Function Succeeded"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: EventBridgeWebSocket&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;I didn't want to pass up the advantage to give a quick look to &lt;a href="https://www.boyney.io/" rel="noopener noreferrer"&gt;David Boyne's&lt;/a&gt; &lt;a href="https://boyney123.github.io/cdk-eventbridge-socket/" rel="noopener noreferrer"&gt;EventBridgeWebSocket&lt;/a&gt; construct. With just a few lines of code, I'm able to actively monitor EventBridge traffic while my test runs!&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;new&lt;/span&gt; &lt;span class="nc"&gt;EventBridgeWebSocket&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="s1"&gt;sockets&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;bus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;eventBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eventBusName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;eventPattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;source&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="s1"&gt;payments&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;stage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dev&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;There's really nothing to this. I just have to provide the bus name and an optional pattern. Now using &lt;a href="https://github.com/vi/websocat" rel="noopener noreferrer"&gt;websocat&lt;/a&gt;, I get output like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;% websocat wss://MY_API_ID.execute-api.us-east-1.amazonaws.com/dev
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"version"&lt;/span&gt;:&lt;span class="s2"&gt;"0"&lt;/span&gt;,&lt;span class="s2"&gt;"id"&lt;/span&gt;:&lt;span class="s2"&gt;"e8d8989a-c870-85b2-9f56-aa7867701eae"&lt;/span&gt;,&lt;span class="s2"&gt;"detail-type"&lt;/span&gt;:&lt;span class="s2"&gt;"success"&lt;/span&gt;,&lt;span class="s2"&gt;"source"&lt;/span&gt;:&lt;span class="s2"&gt;"payments"&lt;/span&gt;,&lt;span class="s2"&gt;"account"&lt;/span&gt;:&lt;span class="s2"&gt;"MY_ACCOUNT_ID"&lt;/span&gt;,&lt;span class="s2"&gt;"time"&lt;/span&gt;:&lt;span class="s2"&gt;"2021-09-12T20:22:03Z"&lt;/span&gt;,&lt;span class="s2"&gt;"region"&lt;/span&gt;:&lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;,&lt;span class="s2"&gt;"resources"&lt;/span&gt;:[],&lt;span class="s2"&gt;"detail"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;:&lt;span class="s2"&gt;"1631478043585-success"&lt;/span&gt;&lt;span class="o"&gt;}}&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"version"&lt;/span&gt;:&lt;span class="s2"&gt;"0"&lt;/span&gt;,&lt;span class="s2"&gt;"id"&lt;/span&gt;:&lt;span class="s2"&gt;"34f6d36b-0cda-65e2-9851-194a9c1be01d"&lt;/span&gt;,&lt;span class="s2"&gt;"detail-type"&lt;/span&gt;:&lt;span class="s2"&gt;"failure"&lt;/span&gt;,&lt;span class="s2"&gt;"source"&lt;/span&gt;:&lt;span class="s2"&gt;"payments"&lt;/span&gt;,&lt;span class="s2"&gt;"account"&lt;/span&gt;:&lt;span class="s2"&gt;"MY_ACCOUNT_ID"&lt;/span&gt;,&lt;span class="s2"&gt;"time"&lt;/span&gt;:&lt;span class="s2"&gt;"2021-09-12T20:22:04Z"&lt;/span&gt;,&lt;span class="s2"&gt;"region"&lt;/span&gt;:&lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;,&lt;span class="s2"&gt;"resources"&lt;/span&gt;:[],&lt;span class="s2"&gt;"detail"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;:&lt;span class="s2"&gt;"1631478043585-failure"&lt;/span&gt;&lt;span class="o"&gt;}}&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"version"&lt;/span&gt;:&lt;span class="s2"&gt;"0"&lt;/span&gt;,&lt;span class="s2"&gt;"id"&lt;/span&gt;:&lt;span class="s2"&gt;"8444d579-c553-4c91-e07a-9de3f9cc17c0"&lt;/span&gt;,&lt;span class="s2"&gt;"detail-type"&lt;/span&gt;:&lt;span class="s2"&gt;"collections"&lt;/span&gt;,&lt;span class="s2"&gt;"source"&lt;/span&gt;:&lt;span class="s2"&gt;"payments"&lt;/span&gt;,&lt;span class="s2"&gt;"account"&lt;/span&gt;:&lt;span class="s2"&gt;"MY_ACCOUNT_ID"&lt;/span&gt;,&lt;span class="s2"&gt;"time"&lt;/span&gt;:&lt;span class="s2"&gt;"2021-09-12T20:22:06Z"&lt;/span&gt;,&lt;span class="s2"&gt;"region"&lt;/span&gt;:&lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;,&lt;span class="s2"&gt;"resources"&lt;/span&gt;:[],&lt;span class="s2"&gt;"detail"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;:&lt;span class="s2"&gt;"1631478043585-failure"&lt;/span&gt;&lt;span class="o"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This isn't explicitly a testing tool of course, but can be of great help when debugging issues and the cost of entry is amazingly low. Just make sure you don't implement this in production, at least without adding an authorizer to the WebSocketApi.&lt;/p&gt;

&lt;h2&gt;
  
  
  Infrastructure Testing&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Now that we've established Provider Framework as a good basis for testing, are there other applications? Yes! Instead of writing a test against our application, we could use aws-sdk to perform assertions against our infrastructure. This has the same advantages outlined above:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We don't need external keys to perform these assertions.&lt;/li&gt;
&lt;li&gt;Using &lt;code&gt;addDependency&lt;/code&gt; can guarantee the test only runs after the resources are created or updated.&lt;/li&gt;
&lt;li&gt;If the test fails, the deployment will automatically roll back.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's worth mentioning that AWS CDK already has an &lt;a href="https://github.com/aws/aws-cdk-rfcs/issues/31" rel="noopener noreferrer"&gt;RFC for integration testing&lt;/a&gt; so we might end up with something even better. In the meantime, if you are serious about integration testing, time to give AWS CDK and Provider Framework a look!&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The weakness of this approach is the imperative coding and lack of convenience methods exposed by libraries like &lt;code&gt;aws-testing-library&lt;/code&gt; and &lt;code&gt;sls-test-tools&lt;/code&gt; offer. It definitely feels a lot more ergonomic to write &lt;code&gt;expect({...}).toHaveLog(expectedLog)&lt;/code&gt; than to query a database and try to test properties on the returned item. I'm not sure if a library like jest is really suited to running in Lambda, but there's definitely room for innovation on a better assertion engine here.&lt;/p&gt;

&lt;p&gt;That said, I think this approach is strong enough on its own. I've been using just such a test for about three months now and find it to be very reliable and a good way to guarantee quality delivery.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://www.perdurabo.co.uk/2020/12/public-domain-art-from-wikimedia-commons.html" rel="noopener noreferrer"&gt;COVER IMAGE&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>eventdriven</category>
      <category>testing</category>
      <category>cdk</category>
    </item>
    <item>
      <title>Your Complete API Gateway and CORS Guide</title>
      <dc:creator>Matt Morgan</dc:creator>
      <pubDate>Tue, 20 Apr 2021 12:29:35 +0000</pubDate>
      <link>https://dev.to/aws-builders/your-complete-api-gateway-and-cors-guide-11jb</link>
      <guid>https://dev.to/aws-builders/your-complete-api-gateway-and-cors-guide-11jb</guid>
      <description>&lt;p&gt;I recently spent some quality time with some colleagues who were implementing a web app using &lt;a href="https://aws.amazon.com/api-gateway/" rel="noopener noreferrer"&gt;API Gateway&lt;/a&gt;. It's really a very useful service, but navigating it can be challenging due to the sheer number of options available. We wound up grinding out a solution and I wanted to share my learnings, not just the "how" but also the "why" in hopes it'll help others come to some of these decisions more easily.&lt;/p&gt;

&lt;p&gt;I will probably make this part of a series, but for this article, I want to go into the ways to enable &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" rel="noopener noreferrer"&gt;Cross-Origin Resource Sharing&lt;/a&gt; or CORS in API Gateway. It's not as intuitive as it could be and depending on the sort of integration you want to use, you'll need a different implementation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/cdk/" rel="noopener noreferrer"&gt;AWS CDK&lt;/a&gt; is my infrastructure-as-code tool of choice. I'll provided examples in TypeScript,  but also show some console screenshots and walk through some generated CloudFormation. Examples are &lt;a href="https://github.com/elthrasher/apigw-cors" rel="noopener noreferrer"&gt;available here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;API Gateway&lt;/li&gt;
&lt;li&gt;CORS&lt;/li&gt;
&lt;li&gt;HTTP API vs REST API&lt;/li&gt;
&lt;li&gt;Custom Integration with REST API&lt;/li&gt;
&lt;li&gt;Proxy Integration with REST API&lt;/li&gt;
&lt;li&gt;Proxy Integration with HTTP API&lt;/li&gt;
&lt;li&gt;REST API Service Integrations&lt;/li&gt;
&lt;li&gt;HTTP API Service Integrations&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  API Gateway&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;If you're in the AWS ecosystem, you have several choices for granting external users access to your application. API Gateway is a full-featured serverless option. It is often compared to &lt;a href="https://aws.amazon.com/elasticloadbalancing/application-load-balancer/" rel="noopener noreferrer"&gt;Application Load Balancer&lt;/a&gt;. ALB has fewer features and is not serverless, but may be cheaper for high-throughput applications. There are some good resources out there that go into this topic at depth, so I won't.&lt;/p&gt;

&lt;p&gt;API Gateway is often used for invoking Lambda functions, but can be connected to many other AWS services as well as HTTP integrations. API Gateway offers support for request validation, throttling, transformation and various authorization mechanisms. Taking full advantage of API Gateway can do a lot to offset the higher price point but there can be a high cognitive load in doing so.&lt;/p&gt;

&lt;h2&gt;
  
  
  CORS&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;CORS is a security mechanism supported by all major web browsers. If you are dealing with web apps, you are going to contend with CORS one way or another. I have worked in environments where the CORS headers were added by some kind of ingress gateway and I've worked in environments where the headers had to be set explicitly by the applications. API Gateway supports both models, as we shall see.&lt;/p&gt;

&lt;p&gt;I urge you to read up on best practices and make the correct choices for your application. Some of my examples use wildcards (&lt;code&gt;*&lt;/code&gt;) for allowed domains. You'll typically want something more restrictive than that in a web application.&lt;/p&gt;

&lt;p&gt;In brief, what we're going to need to do to support CORS is to add an &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS" rel="noopener noreferrer"&gt;HTTP OPTIONS&lt;/a&gt; method for each of our route and then set the &lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt; header on our responses. There are other CORS headers that can be optionally set, such as &lt;code&gt;Access-Control-Allow-Credentials&lt;/code&gt;, &lt;code&gt;Access-Control-Allow-Headers&lt;/code&gt;, &lt;code&gt;Access-Control-Allow-Methods&lt;/code&gt; and &lt;code&gt;Access-Control-Max-Age&lt;/code&gt;. Setting these headers works in exactly the same way, so I'll just focus on &lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  HTTP API vs REST API&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Before we can begin any API Gateway implementation, we need to decide which API implementation we're using. AWS API Gateway first launched in 2015 and has gained features steadily since, as AWS services tend to do. In 2019, AWS announced &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api.html" rel="noopener noreferrer"&gt;HTTP API&lt;/a&gt; as a lower-cost alternative to original, which became known as &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-rest-api.html" rel="noopener noreferrer"&gt;REST API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Just to editorialize for a moment, while it's not hard to keep them straight, these are bad names, since both implementations of the service use HTTP and REST and as such, the names are not at all differentiating. Oh well, never mind, AWS is &lt;a href="https://twitter.com/QuinnyPig/status/1070451608050315264" rel="noopener noreferrer"&gt;known for having this problem&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To add further confusion, HTTP API isn't Version 2 of API Gateway, but there &lt;em&gt;is&lt;/em&gt; a version 2 of the spec. Payload format version 2.0 was initially rolled out for &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api.html" rel="noopener noreferrer"&gt;WebSocket APIs&lt;/a&gt; and is now available for HTTP API but not REST API. If you want to understand the differences, &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html" rel="noopener noreferrer"&gt;the official docs&lt;/a&gt; do a good job of giving us a side-by-side comparison. It's clear the intent of version 2.0 is to provide a simpler format, but you should be aware of the differences if you are used to payload version 1.0&lt;/p&gt;

&lt;p&gt;So the three API implementations provided by API Gateway are REST API (payload format 1.0), HTTP API (choose either) and Websocket API (payload format 2.0). I won't provide any information on CORS headers for WebSocket API as it isn't part of the &lt;a href="https://stackoverflow.com/a/37837709/1487358" rel="noopener noreferrer"&gt;WebSocket spec&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-vs-rest.html" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt; explains the feature differences between HTTP API and REST API and goes well beyond the scope of this article. I will mention that HTTP API is cheaper than REST API and thus generally the guidance is to choose it if it has all the features you need.&lt;/p&gt;

&lt;h2&gt;
  
  
  Proxy Integration with REST API&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;What, you thought you'd exhausted the decision tree? We're just getting started. There's a &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-api-integration-types.html" rel="noopener noreferrer"&gt;huge section just on the different integration types for REST API&lt;/a&gt; alone. Here we're going to be dealing with just one of those integration patterns, the &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html" rel="noopener noreferrer"&gt;proxy integration&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Most of the API Gateway/Lambda stacks I've seen have used proxy integrations. In this integration, we pass the request object to the function handler and construct a response object in the function as well, which is then passed back via API Gateway. That doesn't mean we're talking only about lambdaliths here! In fact, in &lt;a href="https://twitter.com/NIDeveloper" rel="noopener noreferrer"&gt;Matt Coulter's&lt;/a&gt; &lt;a href="https://dev.to/cdkpatterns/learn-the-3-aws-lambda-states-today-the-single-purpose-function-the-fat-lambda-and-the-lambda-lith-361j"&gt;excellent article on common Lambda patterns&lt;/a&gt;, he implements all three patterns using proxy integrations.&lt;/p&gt;

&lt;p&gt;To enable CORS in a proxy integration, we need to do two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Return a response to an OPTIONS request for each route we want to enable for CORS.&lt;/li&gt;
&lt;li&gt;Return valid CORS headers from our Lambda function.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;AWS CDK gives us a nice shortcut for setting those OPTIONS responses. We can use &lt;code&gt;defaultCorsPreflightOptions&lt;/code&gt; in our RestApiProps. That might look something like this:&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="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="s1"&gt;ProxyCorsRestApi&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;defaultCorsPreflightOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;allowOrigins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Cors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ALL_ORIGINS&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;Setting this produces the following CloudFormation:&lt;/p&gt;

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

  &lt;span class="na"&gt;ProxyCorsRestApiOPTIONSEF3C92C2&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::ApiGateway::Method&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;HttpMethod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;OPTIONS&lt;/span&gt;
      &lt;span class="na"&gt;ResourceId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Fn::GetAtt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ProxyCorsRestApiB964F16B&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;RootResourceId&lt;/span&gt;
      &lt;span class="na"&gt;RestApiId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ProxyCorsRestApiB964F16B&lt;/span&gt;
      &lt;span class="na"&gt;AuthorizationType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NONE&lt;/span&gt;
      &lt;span class="na"&gt;Integration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;IntegrationResponses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;ResponseParameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;method.response.header.Access-Control-Allow-Headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent'"&lt;/span&gt;
              &lt;span class="na"&gt;method.response.header.Access-Control-Allow-Origin&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;'*'"&lt;/span&gt;
              &lt;span class="na"&gt;method.response.header.Access-Control-Allow-Methods&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;'OPTIONS,GET,PUT,POST,DELETE,PATCH,HEAD'"&lt;/span&gt;
            &lt;span class="na"&gt;StatusCode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;204'&lt;/span&gt;
        &lt;span class="na"&gt;RequestTemplates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;statusCode:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;200&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}'&lt;/span&gt;
        &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MOCK&lt;/span&gt;
      &lt;span class="na"&gt;MethodResponses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;ResponseParameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;method.response.header.Access-Control-Allow-Headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
            &lt;span class="na"&gt;method.response.header.Access-Control-Allow-Origin&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
            &lt;span class="na"&gt;method.response.header.Access-Control-Allow-Methods&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;StatusCode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;204'&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;And in a nutshell, this is why I like CDK! &lt;a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-api-corsconfiguration.html" rel="noopener noreferrer"&gt;AWS SAM also lets you cut down on your yaml quite a bit&lt;/a&gt;. I definitely prefer one of these options over raw CloudFormation, but any of them should do the job.&lt;/p&gt;

&lt;p&gt;We also need to create an integration. The CDK for that is quite nice:&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;proxyIntegration&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;LambdaIntegration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;proxyFn&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="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="s1"&gt;get&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;proxyIntegration&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;This will produce CloudFormation:&lt;/p&gt;

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

  &lt;span class="na"&gt;ProxyCorsRestApiget33C342A8&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::ApiGateway::Method&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;HttpMethod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GET&lt;/span&gt;
      &lt;span class="na"&gt;ResourceId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Fn::GetAtt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ProxyCorsRestApiB964F16B&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;RootResourceId&lt;/span&gt;
      &lt;span class="na"&gt;RestApiId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ProxyCorsRestApiB964F16B&lt;/span&gt;
      &lt;span class="na"&gt;AuthorizationType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NONE&lt;/span&gt;
      &lt;span class="na"&gt;Integration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;IntegrationHttpMethod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POST&lt;/span&gt;
        &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS_PROXY&lt;/span&gt;
        &lt;span class="na"&gt;Uri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;Fn::Join&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;arn:'&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Partition&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;:apigateway:us-east-1:lambda:path/2015-03-31/functions/&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Fn::GetAtt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;RestProxyFn04E83FA9&lt;/span&gt;
                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Arn&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/invocations&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;This is verbose in comparison to the CDK, but it helps us understand what exactly is happening here. In order to invoke the Lambda service, API Gateway is making an HTTP POST on the appropriate &lt;code&gt;/invocations&lt;/code&gt; endpoint. Good to know, but I'd definitely take a &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/services-apigateway-template.html" rel="noopener noreferrer"&gt;SAM template&lt;/a&gt; over this if forced into yaml.&lt;/p&gt;

&lt;p&gt;Setting the headers is done in the actual Lambda function. Here's a simple TypeScript example:&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;APIGatewayProxyResult&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="s1"&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="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="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;APIGatewayProxyResult&lt;/span&gt;&lt;span class="o"&gt;&amp;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;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="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ok&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="na"&gt;headers&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="s1"&gt;Access-Control-Allow-Origin&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;*&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;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="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;Pretty straightforward in that we just return an object with the headers set, but it must be done in &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-output-format" rel="noopener noreferrer"&gt;this exact format&lt;/a&gt; in order for API Gateway to map the response correctly - mapping still occurs in a proxy integration, you just don't have direct control over it.&lt;/p&gt;

&lt;p&gt;Let's give our integrated route a look in the console.&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%2Fphjn2191toi8wyvj8kyc.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%2Fphjn2191toi8wyvj8kyc.png" alt="CORS-enabled route"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice the "Integration Response" section is grayed out. Most of the other areas here don't have a lot going on. This integration pattern puts most of the work on Lambda and remains quite simple here in API Gateway.&lt;/p&gt;

&lt;p&gt;Let's see if we can make that OPTIONS request the web browser will need. OPTIONS requests typically return an &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204" rel="noopener noreferrer"&gt;HTTP 204&lt;/a&gt; response if successful.&lt;/p&gt;

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

% curl &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; OPTIONS https://gzpd0b4wle.execute-api.us-east-1.amazonaws.com/prod/
HTTP/2 204
&lt;span class="nb"&gt;date&lt;/span&gt;: Tue, 20 Apr 2021 12:11:46 GMT
x-amzn-requestid: f409a183-4bed-4f3c-8a2b-d26b77665a99
access-control-allow-origin: &lt;span class="k"&gt;*&lt;/span&gt;
access-control-allow-headers: Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent
x-amz-apigw-id: &lt;span class="nv"&gt;eFO4aEnZIAMFkYw&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
access-control-allow-methods: OPTIONS,GET,PUT,POST,DELETE,PATCH,HEAD
x-cache: Miss from cloudfront
via: 1.1 b051e9c33308597b659c33b8999b521d.cloudfront.net &lt;span class="o"&gt;(&lt;/span&gt;CloudFront&lt;span class="o"&gt;)&lt;/span&gt;
x-amz-cf-pop: IAD89-C2
x-amz-cf-id: ubVksQvwI3Xd7O4cli00MKp6QMnVpu6ofh2aaS7Ui7Mc3fAx-7Oaqw&lt;span class="o"&gt;==&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;And then call the GET endpoint to verify the header is present.&lt;/p&gt;

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

% curl &lt;span class="nt"&gt;-i&lt;/span&gt; https://gzpd0b4wle.execute-api.us-east-1.amazonaws.com/prod/
HTTP/2 200
content-type: application/json
content-length: 14
&lt;span class="nb"&gt;date&lt;/span&gt;: Tue, 20 Apr 2021 12:12:16 GMT
x-amzn-requestid: 8335bb7d-bfd0-4814-9b16-cbbef3fad49f
access-control-allow-origin: &lt;span class="k"&gt;*&lt;/span&gt;
x-amz-apigw-id: &lt;span class="nv"&gt;eFO88GedIAMFcMw&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
x-amzn-trace-id: &lt;span class="nv"&gt;Root&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1-607ec51f-791687c52f7630d700b81a35&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nv"&gt;Sampled&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
x-cache: Miss from cloudfront
via: 1.1 27eb501c8caff149895f88cac34554af.cloudfront.net &lt;span class="o"&gt;(&lt;/span&gt;CloudFront&lt;span class="o"&gt;)&lt;/span&gt;
x-amz-cf-pop: IAD89-C2
x-amz-cf-id: b-o4OnglA0hpW8ij-dGCGU1wnZjUyY6W43hJ2IDs46fZJjuQJ3r4kw&lt;span class="o"&gt;==&lt;/span&gt;

&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"state"&lt;/span&gt;:&lt;span class="s2"&gt;"ok"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;So it's relatively easy to set up CORS for this pattern, but the downside is you have to add the headers to each function, which could produce a lot of boilerplate in larger stacks. I would definitely seek to add some kind of &lt;a href="https://github.com/middyjs/middy/tree/main/packages/http-cors" rel="noopener noreferrer"&gt;middleware solution&lt;/a&gt; when adding CORS headers across many functions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Custom Integration with REST API&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The next pattern is a bit more complicated.&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-custom-integrations.html" rel="noopener noreferrer"&gt;custom integration&lt;/a&gt; abstracts the HTTP request and response away from our Lambda function. We will have to map any parts of the request we want our function to see and then we'll have to map its response to something API Gateway can send back to the client. This pattern maximizes the work for API Gateway to do and minimizes how much we do in Lambda. Mappings are done using &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/rest-api-data-transformations.html" rel="noopener noreferrer"&gt;Velocity Template Language (VTL)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Why choose this integration pattern? &lt;a href="https://velocity.apache.org/engine/devel/vtl-reference.html" rel="noopener noreferrer"&gt;VTL&lt;/a&gt; isn't exactly developer-friendly, but it's powerful. Additionally, API Gateway bills per request while Lambda bills per millisecond spent in startup and execution. If you have transformations that take meaningful amounts of time on a high-throughput API, using a custom integration could save you some money.&lt;/p&gt;

&lt;p&gt;The other reason to think about a custom integration is it opens the door to non-Lambda integrations that use the same templates. Consider an API Gateway that serves some requests to Lambda, because some compute layer is needed for them, and integrates other requests directly with DynamoDB or S3 because they are simple enough to skip the compute layer. Such an integration would need to use mapping templates for the DynamoDB or S3 integrations already, so why not use them for Lambda as well?&lt;/p&gt;

&lt;p&gt;Perhaps that's an edge case, but it's worth considering how best to shave your AWS bill. Service integrations and VTL could mean major savings, but only if the pattern doesn't increase development complexity by too much.&lt;/p&gt;

&lt;p&gt;To add CORS to a custom integration we will need three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Return a response to an OPTIONS request for each route as before.&lt;/li&gt;
&lt;li&gt;Add the desired headers to our integration responses.&lt;/li&gt;
&lt;li&gt;Map the desired headers from our integration responses into our method responses.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first part works just the same as it does in proxy integrations:&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="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="s1"&gt;CustomCorsRestApi&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;defaultCorsPreflightOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;allowOrigins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Cors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ALL_ORIGINS&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;Now we need to add the header to our integration response which means it's going to be in the CDK code.&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;customIntegration&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;LambdaIntegration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;customFn&lt;/span&gt;&lt;span class="p"&gt;,&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;responseParameters&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="s1"&gt;method.response.header.Access-Control-Allow-Origin&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;'*'&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;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="s1"&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="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;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$util.parseJson($input.body)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ok&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="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="s1"&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="na"&gt;passthroughBehavior&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;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="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="s1"&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="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;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;this is the input&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;p&gt;This code contains a pair of request/response templates, which is one way to do custom integrations. The important part here is the &lt;code&gt;responseParameter&lt;/code&gt; which will set the &lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt; header to a wildcard value. Note also I need to set &lt;code&gt;proxy: false&lt;/code&gt;. CDK considers a proxy integration to be the default integration.&lt;/p&gt;

&lt;p&gt;The third thing we'll need is to set &lt;code&gt;responseParameters&lt;/code&gt; in the method's &lt;code&gt;methodResponses&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&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="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="s1"&gt;get&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;customIntegration&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="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="s1"&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;responseParameters&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="s1"&gt;method.response.header.Access-Control-Allow-Origin&lt;/span&gt;&lt;span class="dl"&gt;'&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;span class="p"&gt;});&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;The boolean value here is only whether or not the parameter is required and this would still work with a value of &lt;code&gt;false&lt;/code&gt; but it will not work if this mapping isn't in place. Note that for the sake of brevity, I've only mapped a 200 OK response. In a real application, we'd need to handle error codes in both the request and response templates and mappings. If we fail to do this, we're likely to get an error directly from the API Gateway service that will be challenging to debug.&lt;/p&gt;

&lt;p&gt;Now that this code is in place, we'll see the OPTIONS request in our CloudFormation template, as with the proxy integration and we'll also be able to see the mappings in the GET method we've added:&lt;/p&gt;

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

  &lt;span class="na"&gt;CustomCorsRestApiget57AF1EA7&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::ApiGateway::Method&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;HttpMethod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GET&lt;/span&gt;
      &lt;span class="na"&gt;ResourceId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Fn::GetAtt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CustomCorsRestApi3D2B9919&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;RootResourceId&lt;/span&gt;
      &lt;span class="na"&gt;RestApiId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CustomCorsRestApi3D2B9919&lt;/span&gt;
      &lt;span class="na"&gt;AuthorizationType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NONE&lt;/span&gt;
      &lt;span class="na"&gt;Integration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;IntegrationHttpMethod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POST&lt;/span&gt;
        &lt;span class="na"&gt;IntegrationResponses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;ResponseParameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;method.response.header.Access-Control-Allow-Origin&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;'*'"&lt;/span&gt;
            &lt;span class="na"&gt;ResponseTemplates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{"message":{"output":"$util.parseJson($input.body)"},"state":"ok"}'&lt;/span&gt;
            &lt;span class="na"&gt;StatusCode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;200"&lt;/span&gt;
        &lt;span class="na"&gt;PassthroughBehavior&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NEVER&lt;/span&gt;
        &lt;span class="na"&gt;RequestTemplates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{"input":"this&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;input"}'&lt;/span&gt;
        &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS&lt;/span&gt;
        &lt;span class="na"&gt;Uri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;Fn::Join&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;arn:"&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Partition&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;:apigateway:us-east-1:lambda:path/2015-03-31/functions/&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Fn::GetAtt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CustomFnFE03B841&lt;/span&gt;
                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Arn&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/invocations&lt;/span&gt;
      &lt;span class="na"&gt;MethodResponses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;ResponseParameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;method.response.header.Access-Control-Allow-Origin&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;StatusCode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;200"&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;The upside of our custom integration is our function can basically be anything we like. We aren't bound by any particular API, but we do need to map to our templates. Here's my sample function:&lt;/p&gt;

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

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;inputEvent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;input&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;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="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;inputEvent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;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="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input&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;I defined an interface that maps to my request template and returns a string which is output in the response template. There's a lot that can be done here but that will have to be the subject of other investigations.&lt;/p&gt;

&lt;p&gt;The console view is a bit more relevant now. If we drill down into that Integration Response, we can find the header mappings setting CORS headers for as well as the request template.&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%2F86o95hwuo56bsek9ry78.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%2F86o95hwuo56bsek9ry78.png" alt="CORS Custom Integration"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's run the same test again for OPTIONS.&lt;/p&gt;

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

% curl &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; OPTIONS https://153hn2leyd.execute-api.us-east-1.amazonaws.com/prod/
HTTP/2 204
&lt;span class="nb"&gt;date&lt;/span&gt;: Tue, 20 Apr 2021 12:13:41 GMT
x-amzn-requestid: 4e7b8882-56d6-497a-8850-cd4b3a274968
access-control-allow-origin: &lt;span class="k"&gt;*&lt;/span&gt;
access-control-allow-headers: Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent
x-amz-apigw-id: &lt;span class="nv"&gt;eFPKaHsioAMFliA&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
access-control-allow-methods: OPTIONS,GET,PUT,POST,DELETE,PATCH,HEAD
x-cache: Miss from cloudfront
via: 1.1 c84ecfd128e1f4c41a53a2b42410f3b8.cloudfront.net &lt;span class="o"&gt;(&lt;/span&gt;CloudFront&lt;span class="o"&gt;)&lt;/span&gt;
x-amz-cf-pop: IAD89-C3
x-amz-cf-id: 8adT0FmbEfTm3wxzoK6B5ZgM2sHoxPpML2F2wSJU-X6UUjfZCI0nDw&lt;span class="o"&gt;==&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;And on the GET endpoint.&lt;/p&gt;

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

% curl &lt;span class="nt"&gt;-i&lt;/span&gt; https://153hn2leyd.execute-api.us-east-1.amazonaws.com/prod/
HTTP/2 200
content-type: application/json
content-length: 55
&lt;span class="nb"&gt;date&lt;/span&gt;: Tue, 20 Apr 2021 12:13:46 GMT
x-amzn-requestid: bf6d4d09-cf3a-4772-bb19-fb2b0119e0ec
access-control-allow-origin: &lt;span class="k"&gt;*&lt;/span&gt;
x-amz-apigw-id: &lt;span class="nv"&gt;eFPLIF_ToAMFTbQ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
x-amzn-trace-id: &lt;span class="nv"&gt;Root&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1-607ec57a-1e08f2330043a36640aa8aac&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nv"&gt;Sampled&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
x-cache: Miss from cloudfront
via: 1.1 ba82151bf51e4c722c5305c983d8b71e.cloudfront.net &lt;span class="o"&gt;(&lt;/span&gt;CloudFront&lt;span class="o"&gt;)&lt;/span&gt;
x-amz-cf-pop: IAD89-C3
x-amz-cf-id: dPiy6f4MhSYlvUz35vmWTFqiAWfGynZw0X_oKvrdWSWZA-nLUxWjlA&lt;span class="o"&gt;==&lt;/span&gt;

&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"message"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"output"&lt;/span&gt;:&lt;span class="s2"&gt;"this is the input"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;,&lt;span class="s2"&gt;"state"&lt;/span&gt;:&lt;span class="s2"&gt;"ok"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;The header is set correctly!&lt;/p&gt;

&lt;h2&gt;
  
  
  Proxy Integration with HTTP API&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;It's pretty obvious that one of the goals in developing HTTP API was to simplify. This is exemplified in the fact that HTTP API &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html" rel="noopener noreferrer"&gt;only supports proxy integrations&lt;/a&gt; with Lambda.&lt;/p&gt;

&lt;p&gt;That said, setting CORS for a proxy integration works differently in HTTP API than it does in REST API. Anybody transitioning from REST API to HTTP API is likely to get caught up by the change. Let's walk through the CDK code:&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="nc"&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="s1"&gt;ProxyCorsHttpApi&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;corsPreflight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;allowMethods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;CorsHttpMethod&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="na"&gt;allowOrigins&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="s1"&gt;*&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;This looks a lot like how it's done in REST API, however when we look at the generated CloudFormation, there's a sizable difference.&lt;/p&gt;

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

  &lt;span class="na"&gt;ProxyCorsHttpApiC5DA21B9&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::ApiGatewayV2::Api&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;CorsConfiguration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;AllowMethods&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;
        &lt;span class="na"&gt;AllowOrigins&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;
      &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ProxyCorsHttpApi&lt;/span&gt;
      &lt;span class="na"&gt;ProtocolType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HTTP&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;That's it! You do not have to manually set OPTIONS requests or map headers. Instead the API Gateway service reads the CORS configuration and manages all of this for you. If you are describing your API Gateway in CloudFormation, you're definitely going to appreciate this innovation.&lt;/p&gt;

&lt;p&gt;Likewise the AWS Console for API Gateway has a specific section for configuring CORS.&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%2Fwhvl3zdk99vp5ct4x0kj.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%2Fwhvl3zdk99vp5ct4x0kj.png" alt="HTTP API CORS"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Just for the sake of knowledge, I tried and you can actually skip all of this and return headers from your Lambda function. That works, but then if you configure CORS in API Gateway, it'll overwrite the headers your function returns.&lt;/p&gt;

&lt;p&gt;So let's make an OPTIONS request to our endpoint.&lt;/p&gt;

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

% curl &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; OPTIONS https://6as8srxaw2.execute-api.us-east-1.amazonaws.com
HTTP/2 204
&lt;span class="nb"&gt;date&lt;/span&gt;: Tue, 20 Apr 2021 12:15:20 GMT
apigw-requestid: &lt;span class="nv"&gt;eFPZ2jtpoAMEVjQ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Now for the trick. Everything is looking good, so I'll call my endpoint.&lt;/p&gt;

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

% curl &lt;span class="nt"&gt;-i&lt;/span&gt; https://6as8srxaw2.execute-api.us-east-1.amazonaws.com
HTTP/2 200
&lt;span class="nb"&gt;date&lt;/span&gt;: Tue, 20 Apr 2021 12:15:27 GMT
content-type: text/plain&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;utf-8
content-length: 14
apigw-requestid: &lt;span class="nv"&gt;eFPa1hVpIAMEVRw&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;

&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"state"&lt;/span&gt;:&lt;span class="s2"&gt;"ok"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;No CORS header! What happened? It turns out that unlike REST API, HTTP API will only set the header if you pass in an &lt;code&gt;Origin&lt;/code&gt; header on the request. Let's try that again.&lt;/p&gt;

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

% curl &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Origin: https://mysite.com"&lt;/span&gt; https://6as8srxaw2.execute-api.us-east-1.amazonaws.com
HTTP/2 200
&lt;span class="nb"&gt;date&lt;/span&gt;: Tue, 20 Apr 2021 12:16:19 GMT
content-type: text/plain&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;utf-8
content-length: 14
access-control-allow-origin: &lt;span class="k"&gt;*&lt;/span&gt;
apigw-requestid: &lt;span class="nv"&gt;eFPjFitBIAMEVIw&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;

&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"state"&lt;/span&gt;:&lt;span class="s2"&gt;"ok"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;This is probably fine and correct behavior, but I'm certain it has caused stress and annoyance from developers trying to build on HTTP API. The &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-cors.html" rel="noopener noreferrer"&gt;official docs&lt;/a&gt; do mention CORS headers returned from Lambda will be ignored, but do not mention that the request must contain an &lt;code&gt;Origin&lt;/code&gt; header for CORS to work.&lt;/p&gt;

&lt;h2&gt;
  
  
  REST API Service Integrations&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;As mentioned above, service integrations (meaning API Gateway invokes an AWS service directly without Lambda), follow the same pattern as custom integrations. You will need to take the same steps to enable CORS. Although I don't talk about CORS, I do have an &lt;a href="https://dev.to/elthrasher/aws-cdk-api-gateway-service-integration-with-dynamodb-2ek0"&gt;article on integrating DynamoDB with REST API&lt;/a&gt;. There isn't a lot of information in the &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-api-integration-types.html" rel="noopener noreferrer"&gt;official docs&lt;/a&gt; on how to build out service integrations or even what services can be integrated with in this way, but think of it as API Gateway being configured to make SDK calls and you'll be somewhere in the vicinity of figuring this out. &lt;/p&gt;

&lt;h2&gt;
  
  
  HTTP API Service Integrations&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Continuing the pattern we've seen throughout, HTTP API service integrations are simpler, but more limited. The &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-aws-services-reference.html" rel="noopener noreferrer"&gt;docs&lt;/a&gt; limit to just five different services you can integrate with at the time of this writing. The good news if you want to use one of these integrations and need to support CORS is you'll do the exact same thing you needed to do for an HTTP API Lambda proxy integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;It's not too hard to navigate any one of these solutions, but it might be hard to keep them straight and the documentation isn't always clear. It's helpful to be able to correlate the implementation with the API choices you're making. Once we have that down, we can stop the churn and appreciate the gains we get from this service.&lt;/p&gt;

&lt;p&gt;I'm interested in covering authorization and request/response validation in future articles. Feel free to drop a comment if there's any topic you'd like to see explored at depth!&lt;/p&gt;

&lt;p&gt;COVER: &lt;a href="https://commons.wikimedia.org/wiki/File:Offenbach_Voyage_stereoscope_3.jpg" rel="noopener noreferrer"&gt;Published by Actualités Théâtrales J.M. (Paris) ca. 1880–1890&lt;/a&gt;, Public domain, via Wikimedia Commons&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cdk</category>
      <category>cloudnative</category>
      <category>serverless</category>
    </item>
    <item>
      <title>AWS CDK - Fullstack Polyglot with Asset Bundling</title>
      <dc:creator>Matt Morgan</dc:creator>
      <pubDate>Wed, 24 Mar 2021 11:24:37 +0000</pubDate>
      <link>https://dev.to/aws-builders/aws-cdk-fullstack-polyglot-with-asset-bundling-318h</link>
      <guid>https://dev.to/aws-builders/aws-cdk-fullstack-polyglot-with-asset-bundling-318h</guid>
      <description>&lt;p&gt;This is my third article that deals with &lt;a href="https://docs.aws.amazon.com/cdk/latest/guide/assets.html"&gt;asset bundling&lt;/a&gt; in &lt;a href="https://aws.amazon.com/cdk/"&gt;AWS CDK&lt;/a&gt;. For more context, you may also be interested in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/elthrasher/aws-cdk-aws-lambda-nodejs-module-9ic"&gt;Lambda NodeJS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/aws-builders/aws-cdk-one-step-s3-websites-with-esbuild-2e3h"&gt;S3 Websites&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Asset management is one of the better features of CDK. We provide a path on the local filesystem and CDK will automatically stage the files in S3 and produce valid CloudFormation to use the files in Lambda, an S3 website, or any other use I may have for assets. The ability to bundle application code with infrastructure and push them together is a superpower and beats the heck out of anything that requires an s3 sync as an extra step.&lt;/p&gt;

&lt;p&gt;Even better is when we let CDK handle the bundling internally. Not only does this make for easy one-step builds, but we can also set rules for managing change. We can tell CDK how to diff our output, thereby ensuring updates are only made when necessary. This will save enormous amounts of time on larger stacks.&lt;/p&gt;

&lt;p&gt;I've covered use cases for TypeScript applications in Lambda and S3 websites in the articles linked above. In this article I'll explore how to add a function written in Go to a TypeScript CDK stack and gain all the same benefits we enjoy using a high-level construct like &lt;a href="https://docs.aws.amazon.com/cdk/api/latest/docs/aws-lambda-nodejs-readme.html"&gt;aws-lambda-nodejs&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Why Go?&lt;/li&gt;
&lt;li&gt;Asset Hash&lt;/li&gt;
&lt;li&gt;Local Bundling&lt;/li&gt;
&lt;li&gt;Docker Bundling&lt;/li&gt;
&lt;li&gt;BYO Dockerfile&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Go?&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;I'm going to focus on running a build for a Lambda function written in Go in this post, but many of the same techniques could be leveraged for any kind of asset bundling. Go is a language that compiles to a binary while TypeScript compiles to JavaScript, so we're dipping our toes in very different worlds here. I've only recently begun to properly learn Go  (thanks &lt;a href="https://cloudacademy.com/"&gt;Cloud Academy&lt;/a&gt;), so I've got a mix of googled hacks and assumed best practices. That's good enough to bundle into a CDK project.&lt;/p&gt;

&lt;p&gt;I used Go for a recent exploration of &lt;a href=""&gt;Open Policy Agent&lt;/a&gt; in my &lt;a href="https://rego.fyi"&gt;rego.fyi&lt;/a&gt; website. I'm going to show snippets from that app. &lt;a href="https://github.com/elthrasher/rego.fyi"&gt;The full repo can be found here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Asset Hash&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Let's start with the best part first. The &lt;code&gt;AssetOptions&lt;/code&gt; interface has three optional properties: &lt;code&gt;assetHash&lt;/code&gt;, &lt;code&gt;assetHashType&lt;/code&gt; and &lt;code&gt;bundling&lt;/code&gt;. In fact, giving the asset options is optional. We can simply write:&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="nx"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fromAsset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/path/to/my/stuff&lt;/span&gt;&lt;span class="dl"&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 will add the specified path as an asset. It isn't modified at all and the input path will be used to detect changes. The above code can be part of a Lambda function. Example:&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;Code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Function&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;LambdaFunction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Runtime&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="s1"&gt;@aws-cdk/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;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="s1"&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;new&lt;/span&gt; &lt;span class="nx"&gt;LambdaFunction&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="s1"&gt;myFn&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;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fromAsset&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="s1"&gt;handler&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
  &lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODEJS_14_X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;index.handler&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or it could just be an asset you want uploaded to S3:&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;Asset&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="s1"&gt;@aws-cdk/aws-s3-assets&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="s1"&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;new&lt;/span&gt; &lt;span class="nx"&gt;Asset&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="s1"&gt;MyAsset&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;path&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="s1"&gt;myfile.txt&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;There's obviously a difference here. &lt;code&gt;Code.fromAsset&lt;/code&gt; takes the path as the first arg, then has an optional &lt;code&gt;AssetOptions&lt;/code&gt; argument while &lt;code&gt;new Asset&lt;/code&gt; is declared as a standalone construct that requires a scope and an id. The &lt;code&gt;Asset&lt;/code&gt; construct takes a required third argument of &lt;code&gt;AssetProps&lt;/code&gt; which extends &lt;code&gt;AssetOptions&lt;/code&gt; but adds the required &lt;code&gt;path&lt;/code&gt; property.&lt;/p&gt;

&lt;p&gt;But wait there's more! You might be bundling for an S3 website.&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;BucketDeployment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Source&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="s1"&gt;@aws-cdk/aws-s3-deployment&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="s1"&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;new&lt;/span&gt; &lt;span class="nx"&gt;BucketDeployment&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="s1"&gt;DeployWebsite&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;destinationBucket&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;Bucket&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="s1"&gt;WebsiteBucket&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;autoDeleteObjects&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="na"&gt;publicReadAccess&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="na"&gt;removalPolicy&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;DESTROY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;websiteIndexDocument&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;index.html&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;distribution&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;distributionPaths&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="s1"&gt;/*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;asset&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="s1"&gt;../path/to/asset&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;Or maybe you're building a Docker image?&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;DockerImageAsset&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="s1"&gt;@aws-cdk/aws-ecr-assets&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="s1"&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;new&lt;/span&gt; &lt;span class="nx"&gt;DockerImageAsset&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="s1"&gt;MyBuildImage&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;directory&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="s1"&gt;my-image&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;Seems confusing? It's actually not that bad. We have a common interface used for building Lambda functions, miscellaneous assets, S3 websites, Docker images and anything else you may wish to transform in a build pipeline. Now that that's clear, let's dig into that &lt;code&gt;AssetOptions&lt;/code&gt; interface.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;assetHash&lt;/code&gt; property would be appropriate if you wanted to generate your own hash instead of letting CDK manage that for you. Most of the time you won't want to use this, but it's good it's there if you need it.&lt;/p&gt;

&lt;p&gt;I'm more interested in &lt;code&gt;assetHashType&lt;/code&gt;. We get a couple of options to consider: &lt;code&gt;OUTPUT&lt;/code&gt; and &lt;code&gt;SOURCE&lt;/code&gt;. There is also &lt;code&gt;BUNDLE&lt;/code&gt;, deprecated by &lt;code&gt;OUTPUT&lt;/code&gt;, and &lt;code&gt;CUSTOM&lt;/code&gt;, which just means &lt;code&gt;assetHash&lt;/code&gt; is provided. Between &lt;code&gt;OUTPUT&lt;/code&gt; and &lt;code&gt;SOURCE&lt;/code&gt;, &lt;code&gt;SOURCE&lt;/code&gt; is the default, but I think I prefer &lt;code&gt;OUTPUT&lt;/code&gt;. If my inputs change (perhaps a library bump) but my output doesn't (that library bump had no impact on the bundled code), then do I really want to deploy new code?&lt;/p&gt;

&lt;p&gt;I'll go into another reason for preferring &lt;code&gt;OUTPUT&lt;/code&gt; later in the article. The takeaway here is we have a lot of flexibility to hash the input, output or provide our own hash. That's great because controlling this feature will make deployments faster. We'll spend less time staring at &lt;a href="http://progressquest.com/"&gt;progress bars&lt;/a&gt; and we'll also be able to respond more quickly to operational issues. This will speed up our dev cycle as well. It's a great feeling running a stack with 11 Lambda Functions, making a change to just one of them and then having a very quick deployment as CDK identifies the one that changed and deploys an update only for that one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local Bundling&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Let's move onto the final optional property of &lt;code&gt;AssetOptions&lt;/code&gt;, bundling. We're going to need to use this if we want to run any kind of build or compilation process on our code. We &lt;em&gt;could&lt;/em&gt; run that as a separate process and then use the output of that process as our asset, but doing it as part of the CDK build is much better.&lt;/p&gt;

&lt;p&gt;The default for bundling is to use Docker, even if you aren't aiming to produce an image. This makes sense for some purposes and will cause problems for others. If our build environment runs a different operating system than our production environment and we are compiling binaries, we may need Docker builds. On the other hand, we might be building in an environment that doesn't run Docker. We could even be building in a Docker environment where spawning a new container from the current one (Docker-in-Docker) is not allowed!&lt;/p&gt;

&lt;p&gt;Let's start from a place where we do &lt;strong&gt;not&lt;/strong&gt; want to use Docker. Unfortunately we can't just leave Docker out entirely. The &lt;code&gt;image&lt;/code&gt; property of &lt;code&gt;bundling&lt;/code&gt; is required. Instead let's just let the user know that we aren't supporting a Docker build.&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;new&lt;/span&gt; &lt;span class="nx"&gt;LambdaFunction&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="s1"&gt;MyFn&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;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fromAsset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;assetHashType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AssetHashType&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="na"&gt;bundling&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;command&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="s1"&gt;sh&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;-c&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;echo "Docker build not supported. Please install go."&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DockerImage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fromRegistry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;alpine&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="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;main&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GO_1_X&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;If local bundling fails, now we at least have a meaningful error message to the user. Of course the above code will fail every build, so we have more work to do. Let's add local bundling. The &lt;code&gt;local&lt;/code&gt; property of &lt;code&gt;bundling&lt;/code&gt; must implement &lt;code&gt;ILocalBundling&lt;/code&gt;, an object with one method, &lt;code&gt;tryBundle&lt;/code&gt; that receives the &lt;code&gt;outputDir&lt;/code&gt; as an argument and must return a boolean type. If &lt;code&gt;tryBundle&lt;/code&gt; returns true and some code artifact is found in &lt;code&gt;outputDir&lt;/code&gt;, then CDK will see the build as successful and skip the Docker build. &lt;code&gt;tryBundle&lt;/code&gt; is synchronous and will not resolve promises so we need a strategy for running the build synchronously and accurately reporting whether or not it was successful.&lt;/p&gt;

&lt;p&gt;To synchronously run our build, we'll use &lt;code&gt;execSync&lt;/code&gt; from the nodejs &lt;a href="https://nodejs.org/dist/latest-v14.x/docs/api/child_process.html#child_process_child_process_execsync_command_options"&gt;child_process&lt;/a&gt; module. &lt;code&gt;tryBundle&lt;/code&gt; should implement try/catch to ensure it returns a boolean about the state of our build. It's common practice to execute some kind of test to see if the runtime supports our build. We could use &lt;code&gt;go version&lt;/code&gt; to see if our runtime can support a build in Go.&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;Code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Function&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;LambdaFunction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Runtime&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="s1"&gt;@aws-cdk/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;AssetHashType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;DockerImage&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="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@aws-cdk/core&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;execSync&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ExecSyncOptions&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="s1"&gt;child_process&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="s1"&gt;path&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;execOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ExecSyncOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;stdio&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="s1"&gt;ignore&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;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;inherit&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;goPath&lt;/span&gt; &lt;span class="o"&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="s1"&gt;..&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;LambdaFunction&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="s1"&gt;MyFn&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;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fromAsset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;goPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;assetHashType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AssetHashType&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="na"&gt;bundling&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;command&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="s1"&gt;sh&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;-c&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;echo "Docker build not supported. Please install go."&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DockerImage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fromRegistry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;alpine&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;local&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;tryBundle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;outputDir&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;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;execSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;go version&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;execOptions&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="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="nx"&gt;execSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`GOARCH=amd64 GOOS=linux go build -ldflags="-s -w" -o &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;outputDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;main&lt;/span&gt;&lt;span class="dl"&gt;'&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="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;execOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;cwd&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;goPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fns/go&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;return&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;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;main&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GO_1_X&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;I've included some options for standard output from &lt;code&gt;execSync&lt;/code&gt; that work well for me on my terminal. For this project, I've structured things in such a way that my &lt;code&gt;go.mod&lt;/code&gt; and &lt;code&gt;go.sum&lt;/code&gt; are in the root of my project and my go source is in &lt;code&gt;fns/go&lt;/code&gt;. Because of that, I need to set the path of the build to the project root, then run the build command in &lt;code&gt;fns/go&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For the uninitiated, &lt;code&gt;go.mod&lt;/code&gt; and &lt;code&gt;go.sum&lt;/code&gt; are for dependency management using &lt;a href="https://blog.golang.org/using-go-modules"&gt;go modules&lt;/a&gt;. This is not completely unlike &lt;code&gt;package.json&lt;/code&gt; and &lt;code&gt;package-lock.json&lt;/code&gt;. I'm not crystal clear on best practices myself, but it does seem like these belong in the root. It is also typical to put a &lt;code&gt;main.go&lt;/code&gt; file in the root of a project, but that felt wrong for a polyglot project. By putting my dependency files in the root and my function code in directory parallel to &lt;code&gt;fns/ts&lt;/code&gt; (for TypeScript), I'm able to run my tests from the root of the project with &lt;code&gt;go test ./...&lt;/code&gt;, while organizing my project in a way that makes sense to me.&lt;/p&gt;

&lt;p&gt;This is why setting &lt;code&gt;AssetHashType&lt;/code&gt; to &lt;code&gt;OUTPUT&lt;/code&gt; is so important, because I'm actually setting my source to the root of the project and I do not want to use the entire project as change detection for this one function.&lt;/p&gt;

&lt;p&gt;In summation, I've organized my project in a way that makes sense to me and seems efficient. Is it the best practice? You tell me. I spent some time searching for answers and couldn't come up with one.&lt;/p&gt;

&lt;p&gt;A couple of other things to mention here about the build. Using local bundling, I very well may be running a build in an environment (MacOS in this case) that is different than my execution environment (Lambda, which is Amazon Linux). If I don't run a build that targets my environment, it's not going to work. Fortunately Go has me covered and by adding the &lt;code&gt;GOARCH=amd64 GOOS=linux&lt;/code&gt; flags to my build, I'll target the right architecture and OS. As for &lt;code&gt;ldflags&lt;/code&gt;, I admit that's a copy-paste. I read the docs and I'm still not sure why I'm doing it! Maybe it'll dawn on me some day.&lt;/p&gt;

&lt;p&gt;Finally, I do have the option of making &lt;code&gt;tryBundle&lt;/code&gt; a little more concise. Since &lt;code&gt;execSync&lt;/code&gt; will correctly throw an error if my build fails, I could skip &lt;code&gt;go version&lt;/code&gt; and just write:&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="nx"&gt;tryBundle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outputDir&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;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;execSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`GOARCH=amd64 GOOS=linux go build -ldflags="-s -w" -o &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;outputDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;main&lt;/span&gt;&lt;span class="dl"&gt;'&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="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;execOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;cwd&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;goPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fns/go&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;return&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="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&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;It's just a matter of taste here. Do you like the go version being output?&lt;/p&gt;

&lt;h2&gt;
  
  
  Docker Bundling&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Docker bundling could well be appropriate if we don't want to install Go on the build system. Using Docker builds might allow a polyglot app to be checked out and worked on by developers who don't use every language in the app. It might also work out well for apps written in languages that have many different versions available that could be on contributors' workstations. The Docker build could pin us to specific languages and versions. Let's see how that looks.&lt;/p&gt;

&lt;p&gt;We have already provided the &lt;code&gt;command&lt;/code&gt; and &lt;code&gt;image&lt;/code&gt; arguments to shy users away from Docker. Now we'll flip it around and use those commands to run a Docker build. First we need a base image other than &lt;code&gt;alpine&lt;/code&gt;. CDK is able to provide a suggestion here. The &lt;code&gt;Runtime&lt;/code&gt; constant exposes a &lt;code&gt;bundlingDockerImage&lt;/code&gt; for each available runtime.&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="nx"&gt;bundling&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;command&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="s1"&gt;sh&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;-c&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;echo "Need a command"&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GO_1_X&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bundlingDockerImage&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;For the NodeJS, Python, Java and some DotNet runtimes, this image will point to &lt;code&gt;amazon/aws-sam-cli-build-image-*&lt;/code&gt; images, that are now maintained by the &lt;a href="https://aws.amazon.com/serverless/sam/"&gt;AWS SAM&lt;/a&gt; project. For whatever reason, the Go image still (at the time of this writing) uses &lt;code&gt;lambci/lambda:build-go1.x&lt;/code&gt; from Michael Hart's &lt;a href="https://www.lambci.org/"&gt;lambci&lt;/a&gt; project that originally inspired SAM. This image is perfectly good for my purposes, but by using a CDK constant, I do run the risk that some day this changes to another image, so I should keep an eye on that.&lt;/p&gt;

&lt;p&gt;The way the Docker build works here is CDK will automatically map a volume of my build path (specified as the first argument of &lt;code&gt;fromAsset&lt;/code&gt;) to &lt;code&gt;/asset-input&lt;/code&gt; in the container. My build is expected to produce output to &lt;code&gt;/asset-output&lt;/code&gt; in order to be considered successful.&lt;/p&gt;

&lt;p&gt;As I noted above, my dependency files (&lt;code&gt;go.mod&lt;/code&gt; and &lt;code&gt;go.sum&lt;/code&gt;) are in the root of my project, so I need to set the input to the root of my project and the entire project is shared as volume. This is a little inefficient, but still relatively fast. The entire build is taking less than 20 seconds on my MBP.&lt;/p&gt;

&lt;p&gt;To make this work, we'll set the &lt;code&gt;user&lt;/code&gt; and &lt;code&gt;workingDirectory&lt;/code&gt; properties. For &lt;code&gt;workingDirectory&lt;/code&gt;, we can map right into the directory with the go files &lt;code&gt;/asset-input/fns/go&lt;/code&gt; and save ourselves a clumsy &lt;code&gt;cd&lt;/code&gt; in the command. We'll also need to specify the user as &lt;code&gt;root&lt;/code&gt; in order to create the go cache. For those experienced in Docker, this will stick out as a problem, but running a build as root isn't a big deal so long as we aren't running a production workload as root.&lt;/p&gt;

&lt;p&gt;Finally we set the command to basically the same thing we had for local bundling and now we have a working build!&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="nx"&gt;bundling&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;command&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="s1"&gt;sh&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;-c&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;GOARCH=amd64 GOOS=linux go build -ldflags="-s -w" -o /asset-output/main&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GO_1_X&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bundlingDockerImage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;workingDirectory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/asset-input/fns/go&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;Specifying the architecture and OS may not be necessary since we should be building in a Lambda-friendly environment, but it also doesn't hurt. Notice the build outputs directly to &lt;code&gt;/asset-output/main&lt;/code&gt;. From there, CDK picks up the artifact and stages it for upload. You'll be able to see it under &lt;code&gt;cdk.out&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  BYO Dockerfile&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;If you're at all picky about your Docker builds, you probably want to provide your own Dockerfile. Of course you can! This is most emphatically &lt;strong&gt;not&lt;/strong&gt; an article on Docker best practices, but I took at shot at building a Dockerfile that ticks at least most of the boxes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; lambci/lambda:build-go1.x&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; APP_USER app&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; APP_HOME /usr/app&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; ASSET_DIR /asset&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; GOPATH $APP_HOME/go&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; GOARCH=amd64 GOOS=linux &lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;groupadd &lt;span class="nv"&gt;$APP_USER&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; useradd &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="nv"&gt;$APP_USER&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="nv"&gt;$APP_USER&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nv"&gt;$APP_HOME&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; &lt;span class="nv"&gt;$APP_USER&lt;/span&gt;:&lt;span class="nv"&gt;$APP_USER&lt;/span&gt; &lt;span class="nv"&gt;$APP_HOME&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nv"&gt;$ASSET_DIR&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; &lt;span class="nv"&gt;$APP_USER&lt;/span&gt;:&lt;span class="nv"&gt;$APP_USER&lt;/span&gt; &lt;span class="nv"&gt;$ASSET_DIR&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; $APP_HOME&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; $APP_USER&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; go.mod go.sum ./&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;go mod download &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; go mod verify

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; /fns/go/authorizer.go ./&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;go build &lt;span class="nt"&gt;-ldflags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"-s -w"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;$ASSET_DIR&lt;/span&gt;/main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'm using a non-root user, caching dependencies and explicitly using &lt;code&gt;COPY&lt;/code&gt; instead of sharing the volume. Is that perfect? The point is you can build your own according to whatever practices you follow and CDK isn't going to silently share volumes or try to run as a particular user.&lt;/p&gt;

&lt;p&gt;Implementing this Dockerfile is pretty easy too. We can simply do this:&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;new&lt;/span&gt; &lt;span class="nx"&gt;LambdaFunction&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="s1"&gt;AuthZFun&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;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fromDockerBuild&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="s1"&gt;..&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
  &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;main&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GO_1_X&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;One gotcha here is that you &lt;strong&gt;must&lt;/strong&gt; use an absolute path to your Dockerfile. I don't know why this is. It seems like a bug, as Docker can certainly build off a relative path, but when I try it my build fails with &lt;code&gt;GlobIgnoreStrategy expects an absolute file path&lt;/code&gt;. Just use the full path and you'll be fine.&lt;/p&gt;

&lt;p&gt;What about combining local bundling and a fallback to a Dockerfile? Sure, we can support that!&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;new&lt;/span&gt; &lt;span class="nx"&gt;LambdaFunction&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="s1"&gt;AuthZFun&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;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fromAsset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;goPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;assetHashType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AssetHashType&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="na"&gt;bundling&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;command&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="s1"&gt;sh&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;-c&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;cp /asset/main /asset-output/main&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DockerImage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fromBuild&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="s1"&gt;..&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
      &lt;span class="na"&gt;local&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;tryBundle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;outputDir&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;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;execSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;go version&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;execOptions&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="cm"&gt;/* istanbul ignore next */&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="nx"&gt;execSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`GOARCH=amd64 GOOS=linux go build -ldflags="-s -w" -o &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;outputDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;main&lt;/span&gt;&lt;span class="dl"&gt;'&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="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;execOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;cwd&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;goPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fns/go&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;return&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="na"&gt;workingDirectory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/usr/app&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="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;main&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GO_1_X&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;Using the same Dockerfile, we now need to set our workingDirectory to &lt;code&gt;/usr/app&lt;/code&gt;, as specified by the Dockerfile. Otherwise, it'll default to &lt;code&gt;/asset-input&lt;/code&gt;. Unfortunately this method will still do the (here unneeded and unnecessary) volume share, but we can dead-end that by ignoring the share in our Docker build. Note that we do need this command to copy our build from &lt;code&gt;/asset/main&lt;/code&gt; to &lt;code&gt;/asset-output/main&lt;/code&gt;. For one thing, the output dir isn't configurable in this build and for another, because the output is being delivered via another Docker share, just running the build won't do anything, even if the we output to &lt;code&gt;/asset-output/main&lt;/code&gt; during the Docker build. That's because the volume share doesn't happen until the built image is run with that command.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;All this said and done, I still prefer local builds and focus on those in my own work, but it's definitely useful to understand the other builds that are available here. I had a tough time figuring out some of the ins and outs of managing Docker builds.&lt;/p&gt;

&lt;p&gt;I've said before that I find the API for bundling a little less intuitive than it needs to be, but the output is excellent. I can make a change to my stack, say, modifying the API Gateway &lt;code&gt;throttlingRateLimit&lt;/code&gt;. Then when I deploy, I'm treated to something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;regofyi: creating CloudFormation changeset...
&lt;span class="o"&gt;[&lt;/span&gt;··························································] &lt;span class="o"&gt;(&lt;/span&gt;0/2&lt;span class="o"&gt;)&lt;/span&gt;

8:43:31 PM | UPDATE_IN_PROGRESS   | AWS::CloudFormation::Stack           | regofyi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even though all my code was re-bundled, CDK correctly determined the build output hadn't changed, so it wasn't included in the changeset and I wound up with a really simple deployment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://picryl.com/media/frieze-with-sea-monsters-778b2f"&gt;COVER IMAGE&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cdk</category>
      <category>go</category>
      <category>typescript</category>
    </item>
    <item>
      <title>rego.fyi: A Study in Serverless Authorization with Open Policy Agent</title>
      <dc:creator>Matt Morgan</dc:creator>
      <pubDate>Mon, 08 Mar 2021 12:12:58 +0000</pubDate>
      <link>https://dev.to/aws-builders/rego-fyi-a-study-in-serverless-authorization-with-open-policy-agent-1ipi</link>
      <guid>https://dev.to/aws-builders/rego-fyi-a-study-in-serverless-authorization-with-open-policy-agent-1ipi</guid>
      <description>&lt;p&gt;&lt;a href="https://www.openpolicyagent.org/docs/latest/" rel="noopener noreferrer"&gt;Open Policy Agent&lt;/a&gt; is a decision engine built on a declarative language called &lt;a href="https://www.openpolicyagent.org/docs/latest/#rego" rel="noopener noreferrer"&gt;Rego&lt;/a&gt;. OPA is pronounced "oh-pa", but &lt;em&gt;to na kang setóp da mesach!&lt;/em&gt; so go ahead and say "oh-pee-ayy" because I know you want to. OPA is general-purpose and written in Go. It allows the creation and compilation of policies written in Rego which can then be hydrated with some kind of data source and then compared against an input to produce a result. OPA docs, linked above, are excellent so check them out for more information.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;tldr&lt;/li&gt;
&lt;li&gt;Why OPA?&lt;/li&gt;
&lt;li&gt;Serverless OPA&lt;/li&gt;
&lt;li&gt;rego.fyi&lt;/li&gt;
&lt;li&gt;Rego Policy&lt;/li&gt;
&lt;li&gt;OPA Authorizer&lt;/li&gt;
&lt;li&gt;Web App&lt;/li&gt;
&lt;li&gt;AWS CDK&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  tl;dr&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/elthrasher/rego.fyi" rel="noopener noreferrer"&gt;The code is here!&lt;/a&gt;&lt;br&gt;
&lt;a href="https://rego.fyi" rel="noopener noreferrer"&gt;rego.fyi&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Why OPA?&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The paradigm presented by OPA is compelling when applied to the use case of multi-tenant SaaS applications. Such applications have complex authorization rules which may include ensuring a user acts within their tenant or personal data, ensuring a user has the right role or permission, ensuring the user belongs to a tenant that is subscribed to the service being provided and many others. Often these rules are implemented in imperative logic throughout the application. A check for subscription may be implemented in middleware while limiting the scope of tenant access is often found in the WHERE clause of a SQL query. Spreading authorization concerns throughout an application makes it very hard to audit and understand what rules the application are actually enforcing and it makes it easy for bugs to creep in.&lt;/p&gt;

&lt;p&gt;The more complex the application, the more authorization becomes a problem that needs a single solution. This is the problem OPA can solve. It's enticing to think about authorizing a microservice architecture with OPA. This would allow developers to focus on the problem the service needs to solve and share a common authorization abstraction.&lt;/p&gt;
&lt;h2&gt;
  
  
  Serverless OPA&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The usual way to implement OPA for microservices is to stand up an authorization service implementing OPA and have other services invoke it over http using some of the &lt;a href="https://www.openpolicyagent.org/docs/latest/ecosystem/" rel="noopener noreferrer"&gt;published middleware&lt;/a&gt;. Even the &lt;a href="https://github.com/adaptant-labs/openfaas-function-auth-opa/blob/master/opa-auth/handler.go#L35" rel="noopener noreferrer"&gt;OpenFaaS version written in Go&lt;/a&gt; depends on a standalone authorization service. I wanted to see if I could use OPA in a 100% serverless environment, making policy decisions in an API Gateway request authorizer without the overhead of additional http requests or the need to run a separate service. I found inspiration in this excellent &lt;a href="https://github.com/zotoio/sls-lambda-opa" rel="noopener noreferrer"&gt;sls-lambda-opa&lt;/a&gt; repo.&lt;/p&gt;

&lt;p&gt;I think of this solution as a layered architecture, where the bottom layer is the authorizer implementing the OPA library, capable of compiling Rego policies. On top of that is the actual policy that states I want to compare a claim like &lt;code&gt;permissions&lt;/code&gt; or &lt;code&gt;subscriptions&lt;/code&gt; or I'm interested in the HTTP resource and method. Above that is the service or endpoint-specific data that states the actual resources, methods and subscriptions that will be evaluated. Then finally we have the user's session or context, delivered in a &lt;a href="https://jwt.io" rel="noopener noreferrer"&gt;JSON Web Token (or JWT)&lt;/a&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%2F48x897ff2gjbotbaj2a5.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%2F48x897ff2gjbotbaj2a5.png" alt="Layers of OPA"&gt;&lt;/a&gt;OPA sandwich?&lt;/p&gt;

&lt;p&gt;Each of these layers can be decoupled from the others. An authorizer function implementation might be used across several services with the same policy but different data hydrating the policy.&lt;/p&gt;
&lt;h2&gt;
  
  
  rego.fyi&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;My first attempt at putting all this together was a fairly terrible demo that needed a REST client and the copying of tokens. I had the urge to build a little fullstack app and so I came up with &lt;a href="https://rego.fyi" rel="noopener noreferrer"&gt;rego.fyi&lt;/a&gt;. In some ways, it's a less impressive take on &lt;a href="https://play.openpolicyagent.org/" rel="noopener noreferrer"&gt;The Rego Playground&lt;/a&gt;, but mine is serverless and built on the kind of architecture I want to work with. I'll also caveat that I am absolute trash when it comes to visual design. I'm just awful at it. Respect to those who are good, but I'm not one of you.&lt;/p&gt;

&lt;p&gt;The architecture of my app is a little different than what I'd envision using in production. I wanted to be able to experiment with different policies, so the policies, along with data and the user input are all sent to be evaluated by my authorizer function. The authorizer compiles the policy in real time, makes the policy decision based on the data and user input, then returns an IAM policy document specifying whether my Lambda function can be invoked, as appropriate.&lt;/p&gt;

&lt;p&gt;In a real application, I likely wouldn't want to compile the policy on request, but instead compile it once on startup. I don't have the expectation of needing to change policies on the fly, though if I did, the policy could be loaded from S3 or a database. What I would probably do in a real application is load the policy from a Lambda Layer.&lt;/p&gt;
&lt;h2&gt;
  
  
  Rego Policy&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;I am by no means an expert on the rego language and won't give an overview here when there are already &lt;a href="https://www.openpolicyagent.org/docs/latest/#rego" rel="noopener noreferrer"&gt;useful docs&lt;/a&gt;. I did manage to put together a workable policy for my experiment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rego"&gt;&lt;code&gt;&lt;span class="ow"&gt;package&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt;

&lt;span class="ow"&gt;import&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="ow"&gt;import&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permissions&lt;/span&gt;
&lt;span class="ow"&gt;import&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subscriptions&lt;/span&gt;

&lt;span class="ow"&gt;default&lt;/span&gt; &lt;span class="n"&gt;allow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

&lt;span class="n"&gt;allow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;check_policy&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;check_policy&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="ow"&gt;some&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;match_with_wildcard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="ow"&gt;some&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;match_with_wildcard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subscriptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subscriptions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;match_with_wildcard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;match_with_wildcard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;match_with_wildcard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;allowed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;allowed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"*"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;match_with_wildcard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;allowed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;allowed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The syntax of this policy is &lt;a href="https://www.openpolicyagent.org/docs/latest/policy-language/" rel="noopener noreferrer"&gt;explained well in the docs&lt;/a&gt;, but just to call out a few things, everything in &lt;code&gt;check_policy&lt;/code&gt; can be considered an AND comparison while the duplicative call signature of &lt;code&gt;match_with_wildcard&lt;/code&gt; makes it an OR comparison. &lt;code&gt;some i; match_with_wildcard(permissions, input.permissions[i])&lt;/code&gt; is a fairly elegant one-liner that makes sure one item in the left-side array matches at least one item in the right-side array.&lt;/p&gt;

&lt;p&gt;Note the policy defines fields I care about, namely the HTTP &lt;code&gt;method&lt;/code&gt; and &lt;code&gt;resource&lt;/code&gt; as well as my custom claims of &lt;code&gt;permissions&lt;/code&gt; and &lt;code&gt;subscriptions&lt;/code&gt;. This policy doesn't include anything about the values that should be compared to, but does describe the shape of the data and how it should be compared. In order to give those values, we need a data file.&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;"requests"&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;"methods"&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="s2"&gt;"GET"&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="s2"&gt;"/orders"&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;"permissions"&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="s2"&gt;"start_order"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"view_invoice"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"subscriptions"&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="s2"&gt;"newsletter"&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;This data file could apply to one API while another one with permissions like &lt;code&gt;cuddle_hedgehogs&lt;/code&gt; or &lt;code&gt;introspect_navel&lt;/code&gt; protects another one. That's the power of this layered approach! But the best part is rego ships with a &lt;a href="https://www.openpolicyagent.org/docs/latest/policy-testing/" rel="noopener noreferrer"&gt;testing framework&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The best way to experience a good separation of concerns is with some solid unit tests. They are quite easy to write.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rego"&gt;&lt;code&gt;&lt;span class="ow"&gt;package&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt;

&lt;span class="n"&gt;test_get_allowed&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;allow&lt;/span&gt; &lt;span class="ow"&gt;with&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="ow"&gt;as&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"permissions"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"start_order"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;"resource"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"/orders"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"method"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"subscriptions"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"newsletter"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;test_get_wrong_subcription_denied&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;allow&lt;/span&gt; &lt;span class="ow"&gt;with&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="ow"&gt;as&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"permissions"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"start_order"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;"resource"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"/orders"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"method"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"subscriptions"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"pizza_of_the_month"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;test_get_wrong_permission_denied&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;allow&lt;/span&gt; &lt;span class="ow"&gt;with&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="ow"&gt;as&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"permissions"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"change_password"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;"resource"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"/orders"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"method"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"subscriptions"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"newsletter"&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;I'm providing the different user inputs and expecting them to either be allowed or not. Rego also gives me test coverage out of the box!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;% opa &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"files"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"policy.rego"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;"covered"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
        &lt;span class="o"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"start"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"row"&lt;/span&gt;: 7
          &lt;span class="o"&gt;}&lt;/span&gt;,
          &lt;span class="s2"&gt;"end"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"row"&lt;/span&gt;: 7
          &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;,
        &lt;span class="o"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"start"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"row"&lt;/span&gt;: 9
          &lt;span class="o"&gt;}&lt;/span&gt;,
          &lt;span class="s2"&gt;"end"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"row"&lt;/span&gt;: 10
          &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;,
        &lt;span class="o"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"start"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"row"&lt;/span&gt;: 13
          &lt;span class="o"&gt;}&lt;/span&gt;,
          &lt;span class="s2"&gt;"end"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"row"&lt;/span&gt;: 18
          &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;,
        &lt;span class="o"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"start"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"row"&lt;/span&gt;: 22
          &lt;span class="o"&gt;}&lt;/span&gt;,
          &lt;span class="s2"&gt;"end"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"row"&lt;/span&gt;: 22
          &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;,
        &lt;span class="o"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"start"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"row"&lt;/span&gt;: 24
          &lt;span class="o"&gt;}&lt;/span&gt;,
          &lt;span class="s2"&gt;"end"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"row"&lt;/span&gt;: 25
          &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
      &lt;span class="o"&gt;]&lt;/span&gt;,
      &lt;span class="s2"&gt;"not_covered"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
        &lt;span class="o"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"start"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"row"&lt;/span&gt;: 21
          &lt;span class="o"&gt;}&lt;/span&gt;,
          &lt;span class="s2"&gt;"end"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"row"&lt;/span&gt;: 21
          &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
      &lt;span class="o"&gt;]&lt;/span&gt;,
      &lt;span class="s2"&gt;"coverage"&lt;/span&gt;: 92.3
    &lt;span class="o"&gt;}&lt;/span&gt;,
    &lt;span class="s2"&gt;"policy_test.rego"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;"covered"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
        &lt;span class="o"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"start"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"row"&lt;/span&gt;: 3
          &lt;span class="o"&gt;}&lt;/span&gt;,
          &lt;span class="s2"&gt;"end"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"row"&lt;/span&gt;: 4
          &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;,
        &lt;span class="o"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"start"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"row"&lt;/span&gt;: 7
          &lt;span class="o"&gt;}&lt;/span&gt;,
          &lt;span class="s2"&gt;"end"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"row"&lt;/span&gt;: 8
          &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;,
        &lt;span class="o"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"start"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"row"&lt;/span&gt;: 11
          &lt;span class="o"&gt;}&lt;/span&gt;,
          &lt;span class="s2"&gt;"end"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"row"&lt;/span&gt;: 12
          &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
      &lt;span class="o"&gt;]&lt;/span&gt;,
      &lt;span class="s2"&gt;"coverage"&lt;/span&gt;: 100
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;,
  &lt;span class="s2"&gt;"coverage"&lt;/span&gt;: 94.75
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Okay, that's a bit verbose and looks like I need another test, but still really useful. There's a &lt;code&gt;--format=pretty&lt;/code&gt; option, but it doesn't seem to do anything. Perhaps it's a WIP.&lt;/p&gt;

&lt;p&gt;Anyway, this is great! Our old applications with some of the authorization logic in middleware, some in SQL and some in between just can't compete with the ability to unit test the policy logic separate from any application code.&lt;/p&gt;

&lt;h2&gt;
  
  
  OPA Authorizer&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The only difference between a RequestAuthorizer and a TokenAuthorizer is the TokenAuthorizer only sees the specified token while a RequestAuthorizer sees the entire request. This is a better fit, since I want to look at things like the HTTP method and path.&lt;/p&gt;

&lt;p&gt;My authorizer function needs to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Unpack my "token" (which really consists of the policy, data and user token, base64 encoded for demo purposes)&lt;/li&gt;
&lt;li&gt;Compile the policy with the provided data.&lt;/li&gt;
&lt;li&gt;Compare the user input and request to the policy.&lt;/li&gt;
&lt;li&gt;Return an appropriate IAM policy to allow or deny access to the function handler.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This authorizer needs to be written in Go because the supporting OPA libraries are only available to the Go runtime. I normally write TypeScript, but this was my second try at Go and I think I did okay, thanks to countless examples and tutorials across the Internet. In fact, it's fair to say that &lt;a href="https://github.com/elthrasher/rego.fyi/blob/main/fns/go/authorizer.go" rel="noopener noreferrer"&gt;imitation&lt;/a&gt; is a sincere form of &lt;a href="https://github.com/zotoio/sls-lambda-opa/blob/master/opacheck/main.go" rel="noopener noreferrer"&gt;flattery&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This was also the first time I've used Go with Lambda and I must say, this might be addicting.&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%2F5b0310hx5k2pylykx5f4.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%2F5b0310hx5k2pylykx5f4.png" alt="Lambda Console"&gt;&lt;/a&gt;Millisecond billing, yeah!&lt;/p&gt;

&lt;p&gt;I'm doing around 50ms with cold starts and single-digits otherwise, even though I'm doing all of those things (unpacking, compiling, deciding, generating) on every request. If the policy is compiled at start time, it's even faster.&lt;/p&gt;

&lt;h2&gt;
  
  
  Web App&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;My poor design skills notwithstanding, I'm reasonably good at programming in React. I built off a &lt;a href="https://dev.to/aws-builders/aws-cdk-one-step-s3-websites-with-esbuild-2e3h"&gt;previous effort&lt;/a&gt; to do a simple fullstack non-CRA* React app with esbuild. I think it works pretty well.&lt;/p&gt;

&lt;p&gt;In order to keep my app from looking like complete trash, I used &lt;a href="https://material-ui.com/" rel="noopener noreferrer"&gt;Material-UI&lt;/a&gt; from Google for the components and that was pretty easy. I also delved into &lt;a href="https://testing-library.com/docs/react-testing-library/intro/" rel="noopener noreferrer"&gt;Testing Library&lt;/a&gt; and I find it quite nice and loved that I could write tests without having to render anything shallowly. I used &lt;a href="https://reactjs.org/docs/context.html" rel="noopener noreferrer"&gt;React Context&lt;/a&gt; for state and I like that a lot better than working with Redux. You can check my code or query about this in the comments as I'm not going to do into great detail here, but as someone who doesn't program in React every day, it's nice checking in and seeing these innovations.&lt;/p&gt;

&lt;p&gt;The app is just one page with no routing. When thinking about how to do this app, I actually thought about trying to figure out some kind of client-side JWT or perhaps do a round trip to a backend to get a user token. Ultimately I decided that cryptographic signing is beyond the scope of what I wanted to do in this app, so all it does is stringify the various form fields, base64 encode that string and finally pass the whole thing as an Authentication header. This allows me to have a decoding piece in my authorizer but of course it's in no way secure.&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS CDK&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;I love working with CDK. My post on &lt;a href="https://dev.to/aws-builders/aws-cdk-one-step-s3-websites-with-esbuild-2e3h"&gt;CDK S3 websites&lt;/a&gt; covers most of topics relating to asset bundling, but it's worth remarking on here. My CDK app handles all the asset bundling of my React web app (written in TypeScript), my authorizer function (written in Go) and my function handler (written in TypeScript). Because of the way asset bundling works in CDK, I can safely &lt;code&gt;cdk deploy&lt;/code&gt; and only the parts of the application that have changed will deploy. This is great for a fullstack application and makes deployments very fast. I'll dig into this topic a bit more in a future post.&lt;/p&gt;

&lt;p&gt;I also was able to get a unified &lt;code&gt;npm test&lt;/code&gt; command that runs all the tests for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CDK infrastructure as code written in TypeScript&lt;/li&gt;
&lt;li&gt;React web app written in TypeScript&lt;/li&gt;
&lt;li&gt;Lambda function handler written in TypeScript&lt;/li&gt;
&lt;li&gt;Authorizer function written in Go&lt;/li&gt;
&lt;li&gt;Policy written in Rego
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;% npm t

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; cdk-esbuild-s3-website@0.0.1 pretest /Users/mattmorgan/mine/rego.fyi
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; npm run lint


&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; cdk-esbuild-s3-website@0.0.1 lint /Users/mattmorgan/mine/rego.fyi
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; eslint &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--ext&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.js,.ts


&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; cdk-esbuild-s3-website@0.0.1 &lt;span class="nb"&gt;test&lt;/span&gt; /Users/mattmorgan/mine/rego.fyi
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; npm run &lt;span class="nb"&gt;test&lt;/span&gt;:opa &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run &lt;span class="nb"&gt;test&lt;/span&gt;:go &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run &lt;span class="nb"&gt;test&lt;/span&gt;:ts


&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; cdk-esbuild-s3-website@0.0.1 &lt;span class="nb"&gt;test&lt;/span&gt;:opa /Users/mattmorgan/mine/rego.fyi
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; opa &lt;span class="nb"&gt;test&lt;/span&gt; ./opa

PASS: 3/3

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; cdk-esbuild-s3-website@0.0.1 &lt;span class="nb"&gt;test&lt;/span&gt;:go /Users/mattmorgan/mine/rego.fyi
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; go &lt;span class="nb"&gt;test&lt;/span&gt; ./...

ok      _/Users/mattmorgan/mine/rego.fyi/fns/go 1.222s

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; cdk-esbuild-s3-website@0.0.1 &lt;span class="nb"&gt;test&lt;/span&gt;:ts /Users/mattmorgan/mine/rego.fyi
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; jest &lt;span class="nt"&gt;--coverage&lt;/span&gt; &lt;span class="nt"&gt;--silent&lt;/span&gt;

 PASS   node  fns/ts/lambdalith.spec.ts
 PASS   dom  ui/providers/PayloadsProvider.spec.tsx
Bundling asset Default/AuthZFun/Code/Stage...
go version go1.16 darwin/amd64
Bundling asset WebTestStack/DeployWebsite/Asset1/Stage...
Bundling asset ApiTestStack/AuthZFun/Code/Stage...
0.8.56
go version go1.16 darwin/amd64
Bundling asset TestStack/DeployWebsite/Asset1/Stage...
0.8.56

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; cdk-esbuild-s3-website@0.0.1 build /Users/mattmorgan/mine/rego.fyi
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; npm run clean &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run build:website


&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; cdk-esbuild-s3-website@0.0.1 build /Users/mattmorgan/mine/rego.fyi
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; npm run clean &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run build:website

 PASS   dom  ui/components/Header.spec.tsx
Bundling asset Default/LambdalithFn/Code/Stage...
 PASS   dom  ui/components/TextArea.spec.tsx

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; cdk-esbuild-s3-website@0.0.1 clean /Users/mattmorgan/mine/rego.fyi
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; rimraf cdk.out coverage website/js


&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; cdk-esbuild-s3-website@0.0.1 clean /Users/mattmorgan/mine/rego.fyi
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; rimraf cdk.out coverage website/js

Bundling asset ApiTestStack/LambdalithFn/Code/Stage...
 PASS   node  cdk/lambda.spec.ts
 PASS   dom  ui/components/RequestControl.spec.tsx

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; cdk-esbuild-s3-website@0.0.1 build:website /Users/mattmorgan/mine/rego.fyi
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;NODE_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production ts-node &lt;span class="nt"&gt;--files&lt;/span&gt; esbuild.ts build

 PASS   dom  ui/App.spec.tsx

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; cdk-esbuild-s3-website@0.0.1 build:website /Users/mattmorgan/mine/rego.fyi
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;NODE_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production ts-node &lt;span class="nt"&gt;--files&lt;/span&gt; esbuild.ts build

 PASS   node  cdk/restApi.spec.ts
Running build...
Running build...
Bundling asset TestStack/AuthZFun/Code/Stage...
 PASS   node  cdk/website.spec.ts &lt;span class="o"&gt;(&lt;/span&gt;7.308 s&lt;span class="o"&gt;)&lt;/span&gt;
go version go1.16 darwin/amd64
Bundling asset TestStack/LambdalithFn/Code/Stage...
 PASS   node  cdk/rego.fyi-stack.spec.ts &lt;span class="o"&gt;(&lt;/span&gt;8.261 s&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nt"&gt;-----------------------&lt;/span&gt;|---------|----------|---------|---------|-------------------
File                   | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line &lt;span class="c"&gt;#s&lt;/span&gt;
&lt;span class="nt"&gt;-----------------------&lt;/span&gt;|---------|----------|---------|---------|-------------------
All files              |     100 |      100 |     100 |     100 |
 cdk                   |     100 |      100 |     100 |     100 |
  getCFAndZone.ts      |     100 |      100 |     100 |     100 |
  lambda.ts            |     100 |      100 |     100 |     100 |
  rego.fyi-stack.ts    |     100 |      100 |     100 |     100 |
  restApi.ts           |     100 |      100 |     100 |     100 |
  website.ts           |     100 |      100 |     100 |     100 |
 fns/ts                |     100 |      100 |     100 |     100 |
  lambdalith.ts        |     100 |      100 |     100 |     100 |
 opa                   |     100 |      100 |     100 |     100 |
  policy.rego          |     100 |      100 |     100 |     100 |
 ui                    |     100 |      100 |     100 |     100 |
  App.tsx              |     100 |      100 |     100 |     100 |
 ui/components         |     100 |      100 |     100 |     100 |
  Header.tsx           |     100 |      100 |     100 |     100 |
  RequestControl.tsx   |     100 |      100 |     100 |     100 |
  Sidebar.tsx          |     100 |      100 |     100 |     100 |
  TextArea.tsx         |     100 |      100 |     100 |     100 |
 ui/pages              |     100 |      100 |     100 |     100 |
  Rego.tsx             |     100 |      100 |     100 |     100 |
 ui/providers          |     100 |      100 |     100 |     100 |
  PayloadsProvider.tsx |     100 |      100 |     100 |     100 |
&lt;span class="nt"&gt;-----------------------&lt;/span&gt;|---------|----------|---------|---------|-------------------

Test Suites: 10 passed, 10 total
Tests:       27 passed, 27 total
Snapshots:   6 passed, 6 total
Time:        8.804 s, estimated 9 s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thanks to &lt;a href="https://github.com/elthrasher/rego.fyi/blob/main/jest.config.js#L28" rel="noopener noreferrer"&gt;jest projects&lt;/a&gt;, I'm able to run my &lt;code&gt;.tsx&lt;/code&gt; tests with the &lt;code&gt;jsdom&lt;/code&gt; test environment and the &lt;code&gt;.ts&lt;/code&gt; tests with &lt;code&gt;node&lt;/code&gt;. Fullstack testing!&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;My success at getting this working as a RequestAuthorizer makes a very compelling case for introducing OPA into serverless projects. We can join the delegation of complex authorization rules with &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-method-request-validation.html" rel="noopener noreferrer"&gt;API Gateway request validation&lt;/a&gt; and find ourselves in a world where our function handlers are very simple - or maybe even &lt;a href="https://dev.to/elthrasher/aws-cdk-api-gateway-service-integration-with-dynamodb-2ek0"&gt;skip them entirely&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://publicdomainreview.org/collection/theatrum-chemicum" rel="noopener noreferrer"&gt;COVER IMAGE&lt;/a&gt;&lt;/p&gt;

</description>
      <category>cdk</category>
      <category>security</category>
      <category>serverless</category>
      <category>opa</category>
    </item>
  </channel>
</rss>
