<?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: Maxime Vivier</title>
    <description>The latest articles on DEV Community by Maxime Vivier (@maximevivier).</description>
    <link>https://dev.to/maximevivier</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%2F868780%2F11e5f87b-4cc7-426f-9d69-f8b7af086689.png</url>
      <title>DEV Community: Maxime Vivier</title>
      <link>https://dev.to/maximevivier</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/maximevivier"/>
    <language>en</language>
    <item>
      <title>🚀 Lambda Test Revolution: Master Mocking &amp; Slash Costs with HTTP-Interceptor!</title>
      <dc:creator>Maxime Vivier</dc:creator>
      <pubDate>Fri, 15 Dec 2023 12:47:11 +0000</pubDate>
      <link>https://dev.to/slsbytheodo/lambda-test-revolution-master-mocking-slash-costs-with-http-interceptor-1i9l</link>
      <guid>https://dev.to/slsbytheodo/lambda-test-revolution-master-mocking-slash-costs-with-http-interceptor-1i9l</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR 📰
&lt;/h2&gt;

&lt;p&gt;Learn how to perform integration tests iso prod with aws serverless services. Using lambda-http-interceptor you can easily intercept and mock http calls coming from your deployed lambda functions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why should you use it:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You want to test your lambda functions in a iso-prod environment 🧪&lt;/li&gt;
&lt;li&gt;You want to save money while running integration tests by not triggering costly third party APIs 💸&lt;/li&gt;
&lt;li&gt;You want to control the behavior of third party APIs to test edge cases 🎯&lt;/li&gt;
&lt;li&gt;You don't want to change your lambda code to make it testable 👷&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And maybe you can use it for all theses reasons at the same time!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/MaximeVivier/lambda-http-interceptor" class="ltag_cta ltag_cta--branded"&gt;Try lambda-http-interceptor here 😉&lt;/a&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Intercept http calls in lambda functions 🪝
&lt;/h2&gt;

&lt;p&gt;The lib is made of a CDK Construct to instantiate in your stack.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;HttpInterceptor&lt;/code&gt; &lt;strong&gt;construct needs to be instantiated in the stack&lt;/strong&gt;, or inside another Construct. And then &lt;strong&gt;the interceptor needs to be applied to the lambda function&lt;/strong&gt; http calls need to be intercepted from.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;applyHttpInterceptor&lt;/code&gt; uses &lt;code&gt;Aspects&lt;/code&gt; in order to apply it on each &lt;code&gt;NodeLambdaFunction&lt;/code&gt; it finds, thus &lt;code&gt;applyHttpInterceptor&lt;/code&gt; takes any Construct as input.&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;Stack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;StackProps&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aws-cdk-lib&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;Construct&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;constructs&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;HttpInterceptor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;applyHttpInterceptor&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lambda-http-interceptor&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;NodejsFunction&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aws-cdk-lib/aws-lambda-nodejs&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;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="s2"&gt;aws-cdk-lib/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;class&lt;/span&gt; &lt;span class="nc"&gt;MyStack&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Stack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Construct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;StackProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&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;interceptor&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;HttpInterceptor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HttpInterceptor&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;myLambdaFunctionThatMakesExternalCalls&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;NodejsFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MakeExternalCalls&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;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_18_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="s2"&gt;index.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;entry&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.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;applyHttpInterceptor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myLambdaFunctionThatDoesExternalCalls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;interceptor&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;After deploying, everything is setup on the stack to then perform integration tests.&lt;/p&gt;

&lt;p&gt;The second part of the lib is a set of &lt;strong&gt;tools to perform integration tests&lt;/strong&gt;. They are gathered in the &lt;code&gt;HttpLambdaInterceptorClient&lt;/code&gt; class.&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;fetch&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node-fetch&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;expect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;it&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vitest&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HTTP_INTERCEPTOR_TABLE_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;table-name-from-construct&amp;gt;&lt;/span&gt;&lt;span class="dl"&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;HttpLambdaInterceptorClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lambda-http-interceptor&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;triggerMyLambdaFunctionThatMakesExternalCalls&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;./utils&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my test&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="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;interceptorClient&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;HttpLambdaInterceptorClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;myLambdaFunctionThatMakesExternalCalls-name&amp;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="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tests my lambda function&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;interceptorClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createConfigs&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api-1/*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&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="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="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;errorMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Not found&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;}),&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api-2/path&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;passThrough&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="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;triggerMyLambdaFunctionThatMakesExternalCalls&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;interceptedCalls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;interceptorClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pollInterceptedCalls&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;numberOfCallsToExpect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&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="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;interceptedCalls&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How does it work? 📚
&lt;/h2&gt;

&lt;h3&gt;
  
  
  lambda-http-interceptor stores the configurations and the call made in DynamoDB table 🏪
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;HttpInterceptor&lt;/code&gt; instantiates a DynamoDB table in the stack. The table is used to store the configurations of the http calls to intercept. When performing integration tests, filling up the table with configuration is done using the &lt;code&gt;createConfigs&lt;/code&gt; method of the &lt;code&gt;HttpLambdaInterceptorClient&lt;/code&gt; class.&lt;/p&gt;

&lt;p&gt;Then assertions can be made on the calls made by the lambda after they are fetched using the &lt;code&gt;pollInterceptedCalls&lt;/code&gt; method of the &lt;code&gt;HttpLambdaInterceptorClient&lt;/code&gt; class.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 Don't forget to give the user you're using to perform the integration tests the right to read in the table. In general, we use AdministratorAccess role for the user performing these tasks.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  lambda-http-interceptor uses an internal extension to intercept http calls in lambda functions 📡
&lt;/h3&gt;

&lt;p&gt;The internal extension that the interceptor deploys on the lambda functions overrides the &lt;code&gt;http&lt;/code&gt; module of nodejs that is used to make http calls.&lt;/p&gt;

&lt;p&gt;For each call made by the lambda, it fetches the http calls configuration stored in DynamoDB and either passes through the call or returns the response value configured at the start.&lt;/p&gt;

&lt;p&gt;It keeps track of the http calls listed that are listed in the configuration. If the response of a call doesn't need to be changed but it still needs to be tracked in order to make assertions on it, the configuration of the call doesn't change and the response only contains &lt;code&gt;passthrough: true&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you want to deep dive the functioning of the interceptor, you can check out &lt;a href="https://dev.to/slsbytheodo/power-up-your-serverless-application-with-aws-lambda-extensions-3a31"&gt;this article&lt;/a&gt; that presents extensions really clearly using a simple example.&lt;/p&gt;

&lt;h2&gt;
  
  
  lambda-http-interceptor has everything built in 💪
&lt;/h2&gt;

&lt;p&gt;The setup is fairly easy and it can be used to make assertions on the calls made by your deployed lambda functions. And you don't edit the code of your lambda handler.&lt;/p&gt;

&lt;p&gt;The documentation on github is far more exhaustive to get you started.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/MaximeVivier/lambda-http-interceptor" class="ltag_cta ltag_cta--branded"&gt;Try lambda-http-interceptor here 😉&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;Don't hesitate to star it ⭐️&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>lambda</category>
      <category>integrationtests</category>
      <category>aws</category>
    </item>
    <item>
      <title>No execution logs on AWS Express workflow? 🤔 📖</title>
      <dc:creator>Maxime Vivier</dc:creator>
      <pubDate>Thu, 13 Apr 2023 14:26:01 +0000</pubDate>
      <link>https://dev.to/slsbytheodo/no-execution-logs-on-aws-express-workflow-240p</link>
      <guid>https://dev.to/slsbytheodo/no-execution-logs-on-aws-express-workflow-240p</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;In this article you will learn how to add &lt;strong&gt;logs in an Express workflow in Step Functions&lt;/strong&gt; with the CDK because it is not enabled by default. Since they are enabled by default on a Standard State Machine, it can be misleading going from a Standard to an Express workflow.&lt;/p&gt;

&lt;p&gt;Here is the snippet of code you need.&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;App&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;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-lib&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;LogGroup&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="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-cdk-lib/aws-logs&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;LogLevel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Pass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;StateMachine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;StateMachineType&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-lib/aws-stepfunctions&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;class&lt;/span&gt; &lt;span class="nc"&gt;ArticleStack&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Stack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expressLogGroup&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;LogGroup&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;ExpressLogs&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;retention&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;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="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StateMachine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ExpressWithLogs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;definition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Pass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ExampleStepForExpressWithLogs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;foo&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;stateMachineType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;StateMachineType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EXPRESS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;expressLogGroup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LogLevel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ALL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;includeExecutionData&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;h2&gt;
  
  
  Standard workflows have logs enabled by default inside AWS Step Functions 💡
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;📙 Read this to better grasp what is happening with these log groups in Step Function or skip to the next section for the tutorial you came for&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;AWS explains it really clearly in its &lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/cw-logs.html" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;, here is a quote describing the default logs behaviors of both Standard and Express types of State Machine.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Standard Workflows&lt;/strong&gt; record execution history &lt;strong&gt;in AWS Step Functions&lt;/strong&gt;, although you can optionally configure logging to Amazon CloudWatch Logs.&lt;/p&gt;

&lt;p&gt;Unlike Standard Workflows, &lt;strong&gt;Express Workflows don't record execution history in AWS Step Functions&lt;/strong&gt;. To see execution history and results for an Express Workflow, you must &lt;strong&gt;configure logging to Amazon CloudWatch Logs&lt;/strong&gt;. Publishing logs doesn't block or slow down executions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The following images show three different State Machines: one &lt;strong&gt;Express configured with logs&lt;/strong&gt;, one &lt;strong&gt;Express configured without logs&lt;/strong&gt; and one &lt;strong&gt;Standard&lt;/strong&gt;. The only one that has a log group associated is the Express one configured with a log group.&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%2Fraw.githubusercontent.com%2FMaximeVivier%2FArticles%2Fmaster%2Fblog-posts%2Fstep-function-learnings%2Fassets%2Fthree-types-of-state-machine.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%2Fraw.githubusercontent.com%2FMaximeVivier%2FArticles%2Fmaster%2Fblog-posts%2Fstep-function-learnings%2Fassets%2Fthree-types-of-state-machine.png" title="Three types of state machine" alt="Three types of state machine"&gt;&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%2Fraw.githubusercontent.com%2FMaximeVivier%2FArticles%2Fmaster%2Fblog-posts%2Fstep-function-learnings%2Fassets%2Fone-log-group.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%2Fraw.githubusercontent.com%2FMaximeVivier%2FArticles%2Fmaster%2Fblog-posts%2Fstep-function-learnings%2Fassets%2Fone-log-group.png" title="One log group associated to one of the Express workflow" alt="One log group associated to one of the Express workflow"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The logs available for the Standard workflow are inside the AWS Step Functions service but there is no log group associated.&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%2Fraw.githubusercontent.com%2FMaximeVivier%2FArticles%2Fmaster%2Fblog-posts%2Fstep-function-learnings%2Fassets%2Flogs-for-standard-workflow.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%2Fraw.githubusercontent.com%2FMaximeVivier%2FArticles%2Fmaster%2Fblog-posts%2Fstep-function-learnings%2Fassets%2Flogs-for-standard-workflow.png" title="Logs of Standard State Machine is in the AWS Step Function service" alt="Logs of Standard State Machine is in the AWS Step Function service"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  CDK is your best friend 💻
&lt;/h2&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%2Fraw.githubusercontent.com%2FMaximeVivier%2FArticles%2Fmaster%2Fblog-posts%2Fstep-function-learnings%2Fassets%2FyouDaRealMVP.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FMaximeVivier%2FArticles%2Fmaster%2Fblog-posts%2Fstep-function-learnings%2Fassets%2FyouDaRealMVP.jpeg" title="CDK is da real MVP" alt="CDK is da real MVP"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All you need is to create a &lt;strong&gt;log group&lt;/strong&gt;, configure it and then &lt;strong&gt;attach it to the State Machine&lt;/strong&gt; via the logs prop. If you want to jump directly to the snippet here is a shortcut.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;CDK handles all the work&lt;/strong&gt; of creating the role for the State Machine to write inside CloudWatch.&lt;/p&gt;

&lt;p&gt;The LogLevel allows you to select what kind of information you want to have. The four levels are: &lt;code&gt;OFF&lt;/code&gt;, &lt;code&gt;ALL&lt;/code&gt;, &lt;code&gt;ERROR&lt;/code&gt; and &lt;code&gt;FATAL&lt;/code&gt;. It is recommended by AWS to choose &lt;code&gt;ALL&lt;/code&gt; because to have logs of all executions and not only the failed ones in this &lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/diff-standard-express-exec-details-ui.html#exp-wf-exec-limitation-details-log-dependent-test" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But the &lt;code&gt;includeExecutionData&lt;/code&gt; property of the CDK construct makes all the difference. Thanks to this property set to &lt;code&gt;true&lt;/code&gt;, you have all step transition data displayed in the Step Functions console.&lt;/p&gt;

&lt;p&gt;Express with NO &lt;em&gt;logs execution data&lt;/em&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%2Fraw.githubusercontent.com%2FMaximeVivier%2FArticles%2Fmaster%2Fblog-posts%2Fstep-function-learnings%2Fassets%2Fexpress-with-logs-but-not-includeExecutionData-exec-logs.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%2Fraw.githubusercontent.com%2FMaximeVivier%2FArticles%2Fmaster%2Fblog-posts%2Fstep-function-learnings%2Fassets%2Fexpress-with-logs-but-not-includeExecutionData-exec-logs.png" title="Express with NO logs execution data" alt="Express with NO logs execution data"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Express with &lt;em&gt;logs execution data&lt;/em&gt; because &lt;code&gt;includeExecutionData&lt;/code&gt; is set to &lt;code&gt;true&lt;/code&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%2Fraw.githubusercontent.com%2FMaximeVivier%2FArticles%2Fmaster%2Fblog-posts%2Fstep-function-learnings%2Fassets%2Fexpress-with-logs-exec-logs.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%2Fraw.githubusercontent.com%2FMaximeVivier%2FArticles%2Fmaster%2Fblog-posts%2Fstep-function-learnings%2Fassets%2Fexpress-with-logs-exec-logs.png" title="Express with logs execution data because includeExecutionData is set to true" alt="Express with logs execution data because includeExecutionData is set to true"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How to configure logs in an Express workflow in CDK ✅&lt;a id="tuto-part"&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Here is the &lt;strong&gt;snippet&lt;/strong&gt; of code that you need to &lt;strong&gt;make it happen&lt;/strong&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;App&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;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-lib&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;LogGroup&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="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-cdk-lib/aws-logs&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;LogLevel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Pass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;StateMachine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;StateMachineType&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-lib/aws-stepfunctions&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;class&lt;/span&gt; &lt;span class="nc"&gt;ArticleStack&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Stack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expressLogGroup&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;LogGroup&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;ExpressLogs&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;retention&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;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="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StateMachine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ExpressWithLogs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;definition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Pass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ExampleStepForExpressWithLogs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;foo&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;stateMachineType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;StateMachineType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EXPRESS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;expressLogGroup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LogLevel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ALL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;includeExecutionData&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;For this snippet of code, I used the &lt;strong&gt;V2&lt;/strong&gt; of the &lt;strong&gt;CDK&lt;/strong&gt; (2.56 to be more precise)&lt;/p&gt;

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

&lt;p&gt;You are all set to have logs on all your State Machines in CDK.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>stepfunctions</category>
      <category>serverless</category>
      <category>cdk</category>
    </item>
    <item>
      <title>👷 Serverless event-sourcing with AWS: State of the art data synchronization ⚡</title>
      <dc:creator>Maxime Vivier</dc:creator>
      <pubDate>Thu, 06 Oct 2022 15:03:34 +0000</pubDate>
      <link>https://dev.to/slsbytheodo/serverless-event-sourcing-with-aws-state-of-the-art-data-synchronization-4mog</link>
      <guid>https://dev.to/slsbytheodo/serverless-event-sourcing-with-aws-state-of-the-art-data-synchronization-4mog</guid>
      <description>&lt;p&gt;If you're about to implement a &lt;strong&gt;serverless&lt;/strong&gt; architecture for an event sourcing based application, you are in the right place. This article aims at sharing with you &lt;strong&gt;2 years of learnings&lt;/strong&gt; 😍, saving you the trouble to make the same mistakes we did at Kumo, and providing you with a guidebook to have the &lt;strong&gt;most stable&lt;/strong&gt; and the &lt;strong&gt;least painful&lt;/strong&gt; solution you can go with. 🚀&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;📚 In this article I assume that you have at least a basic knowledge of what is &lt;strong&gt;CQRS&lt;/strong&gt;. &lt;a href="https://docs.microsoft.com/en-us/azure/architecture/patterns/cqrs" rel="noopener noreferrer"&gt;This article is an excellent page to understand more deeply the ins and outs of CQRS pattern&lt;/a&gt; if you feel the need before diving into the interesting things.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  👀 Quick overview of an event sourcing solution implementing a CQRS interface
&lt;/h2&gt;

&lt;p&gt;💸 Bank applications are one type of business requiring to &lt;strong&gt;store a trace of all business events&lt;/strong&gt;. These business events are the transactions made on a bank account such as credit and debit actions.&lt;/p&gt;

&lt;p&gt;Let's take the example of a &lt;strong&gt;banking application&lt;/strong&gt; that would need to display both the balance of the account and the last transaction made.&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%2Fraw.githubusercontent.com%2FMaximeVivier%2FArticles%2Fmaster%2Fblog-posts%2Fevent-sourcing-serveless%2Fassets%2FArchiMissingDataSynchro.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%2Fraw.githubusercontent.com%2FMaximeVivier%2FArticles%2Fmaster%2Fblog-posts%2Fevent-sourcing-serveless%2Fassets%2FArchiMissingDataSynchro.png" title="CQRS interface with AWS serverless technologies" alt="CQRS interface with AWS serverless technologies"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This architecture is an implementation of &lt;strong&gt;CQRS interface&lt;/strong&gt; for the use case of the bank which has to store all credit and debit events. There are two &lt;strong&gt;commands&lt;/strong&gt; writing credit and debit events in an &lt;strong&gt;event ledger&lt;/strong&gt;. There are two &lt;strong&gt;queries&lt;/strong&gt; that each fetch its associated &lt;strong&gt;projection&lt;/strong&gt;, the total balance of the account and the last transaction made.&lt;/p&gt;

&lt;p&gt;As you can see, the missing part of the diagram is the synchronization between the write model and the read model. That's the block this article focus on.&lt;/p&gt;

&lt;h2&gt;
  
  
  📊 First and foremost, in event sourcing, the order of events is essential
&lt;/h2&gt;

&lt;p&gt;All credit and debit events for every user are stored in the event ledger. The access pattern chosen is the &lt;code&gt;userId&lt;/code&gt; as the &lt;strong&gt;partition key&lt;/strong&gt; and the number of the event also called the &lt;code&gt;version&lt;/code&gt; as the &lt;strong&gt;sort key&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FMaximeVivier%2FArticles%2Fmaster%2Fblog-posts%2Fevent-sourcing-serveless%2Fassets%2Fevent-store-schema.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%2Fraw.githubusercontent.com%2FMaximeVivier%2FArticles%2Fmaster%2Fblog-posts%2Fevent-sourcing-serveless%2Fassets%2Fevent-store-schema.png" title="Event sourcing : DynamoDB event ledger" alt="Event sourcing : DynamoDB event ledger"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;❓ Why using a &lt;code&gt;version&lt;/code&gt; field when we can simply take the &lt;code&gt;timestamp&lt;/code&gt; to keep track of the order of the events?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Two commands coming in &lt;strong&gt;concurrently&lt;/strong&gt; will write data based on the &lt;strong&gt;same version of the aggregate&lt;/strong&gt; because they fetched it simultaneously. Let's say a user has 50$ on his bank account and two purchases come in at the same time, one of 40$ and one of 25$. Both event would &lt;strong&gt;both succeed the business validation tests&lt;/strong&gt;. The composite key &lt;code&gt;userId&lt;/code&gt;/&lt;code&gt;timestamp&lt;/code&gt; of both event would be unique because timestamps would be really close but not exactly the same. Hence, both events would be written in the event ledger and you would be &lt;strong&gt;overdrawn&lt;/strong&gt; which is &lt;strong&gt;forbidden&lt;/strong&gt; in our bank.&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%2Fraw.githubusercontent.com%2FMaximeVivier%2FArticles%2Fmaster%2Fblog-posts%2Fevent-sourcing-serveless%2Fassets%2Fconcurrent-events-version-sk-choice-bad.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%2Fraw.githubusercontent.com%2FMaximeVivier%2FArticles%2Fmaster%2Fblog-posts%2Fevent-sourcing-serveless%2Fassets%2Fconcurrent-events-version-sk-choice-bad.png" title="Choice of version as SK in event sourcing" alt="Choice of version as SK in event sourcing"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the other hand, if the &lt;strong&gt;sort key&lt;/strong&gt; is the &lt;code&gt;version&lt;/code&gt; of the event, &lt;strong&gt;one out of both would be invalid&lt;/strong&gt;. Exact simultaneity doesn't exist in computer science, so the first purchase of 40$ would be written and then shortly after the second purchase would be rejected because the event to be written is based on the same version of the last event.&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%2Fraw.githubusercontent.com%2FMaximeVivier%2FArticles%2Fmaster%2Fblog-posts%2Fevent-sourcing-serveless%2Fassets%2Fconcurrent-events-version-sk-choice-good.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%2Fraw.githubusercontent.com%2FMaximeVivier%2FArticles%2Fmaster%2Fblog-posts%2Fevent-sourcing-serveless%2Fassets%2Fconcurrent-events-version-sk-choice-good.png" title="Choice of version as SK in event sourcing" alt="Choice of version as SK in event sourcing"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  👉 👈 How is the synchronization between read and write models ensured ?
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The pain point in CQRS is to have &lt;strong&gt;reliable reading data&lt;/strong&gt; compared to the source of truth. Projections need to be updated according to events written in the event ledger.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  👂 Listening to written events
&lt;/h3&gt;

&lt;p&gt;The objective here is to &lt;strong&gt;react in real time to events&lt;/strong&gt; written inside the event ledger in order to compute the projections. For that a lambda as a &lt;strong&gt;fanout&lt;/strong&gt; can be used by plugging it to the &lt;strong&gt;DynamoDB streams&lt;/strong&gt; of the &lt;strong&gt;event ledger&lt;/strong&gt;. &lt;code&gt;INSERT&lt;/code&gt; actions have to be filtered with the filter patterns so that only theses actions trigger the fanout. DynamoDB batches its streams of events in arrays, so for each of them an event is published in Amazon EventBridge. These EventBridge events are those triggering the lambda that project on the read models.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;❓ You may wonder why not &lt;strong&gt;directly&lt;/strong&gt; plug the &lt;strong&gt;projector&lt;/strong&gt; to the streams&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For 3 reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stream events that trigger a failing lambda will retry indefinitely until the lambda succeeds. That makes it &lt;strong&gt;impossible&lt;/strong&gt; to have a &lt;strong&gt;custom retry policy&lt;/strong&gt; for events triggering a projector.&lt;/li&gt;
&lt;li&gt;You can only plug &lt;strong&gt;2 lambda maximum&lt;/strong&gt; to the streams, and generally application contain more than 2 sets of data to display therefore more than 2 projectors.&lt;/li&gt;
&lt;li&gt;The correct tool to plug multiple listeners to messages is an Event Bus.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fanout delivers events to one or multiple recipients. Each projector, which is a recipient of those events, only needs to listen to events altering the data it's associated to. For example a projector writing user individual information such as the name family name and address doesn't need to be triggered by a credit event.&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%2Fraw.githubusercontent.com%2FMaximeVivier%2FArticles%2Fmaster%2Fblog-posts%2Fevent-sourcing-serveless%2Fassets%2FarchiWoDispatchAggregate.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%2Fraw.githubusercontent.com%2FMaximeVivier%2FArticles%2Fmaster%2Fblog-posts%2Fevent-sourcing-serveless%2Fassets%2FarchiWoDispatchAggregate.png" title="Data synchronization for CQRS with AWS serverless technologies, 1st version" alt="Data synchronization for CQRS with AWS serverless technologies"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  💪 From a dev reliable architecture to a prod-safe architecture
&lt;/h2&gt;

&lt;p&gt;This works fine in dev but &lt;strong&gt;not in production yet&lt;/strong&gt;. This data synchronization architecture will face bugs later on. Let me explain :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When traffic starts to grow, more and more events will flow and one thing &lt;strong&gt;EventBridge doesn't ensure&lt;/strong&gt; is the &lt;strong&gt;order of the events&lt;/strong&gt; sent in the bus event. In order not to overwrite an information with an older one, the last version of event that updated the projection must be kept track of.&lt;/li&gt;
&lt;li&gt;EventBridge ensures that an &lt;strong&gt;event&lt;/strong&gt; is sent at least once, but it can still be &lt;strong&gt;received more than once&lt;/strong&gt;. So projections need to be idempotent.&lt;/li&gt;
&lt;li&gt;In dev, when a &lt;strong&gt;projection&lt;/strong&gt; needs to be &lt;strong&gt;updated&lt;/strong&gt; or a new one needs to be created, wiping the databases and creating new data is the easy way to go to test this new feature. But in production that is not an option. Projections are made from the succession of all events that happened, that's why the solution is to find a way to &lt;strong&gt;replay&lt;/strong&gt; all events to recreate the projections. Projectors must be idempotent and disorder-proof, thus they can handle smoothly a replay of all events.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  😒 EventBridge Archive, the bogus good idea
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 EventBridge offers the possibility to replay all events that went through its service. This feature is called &lt;strong&gt;EventBridge Archive&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There are 3 main issues with that solution:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Replaying all events implies a &lt;strong&gt;high traffic&lt;/strong&gt; when having a high number of events.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Some events&lt;/strong&gt; that are replayed can be &lt;strong&gt;useless&lt;/strong&gt;, so that's useless traffic and thus useless run time.&lt;/li&gt;
&lt;li&gt;This leaves &lt;strong&gt;two sources of truth&lt;/strong&gt;. And Archive data can't be edited, it is immutable. It can be an issue.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🪄 A trick to make data synchronization and replay easier
&lt;/h2&gt;

&lt;p&gt;The best solution would be that &lt;strong&gt;every projector has access to the current state&lt;/strong&gt; of the data every time they need to update their projection. They would all have to fetch all events to compute the aggregate every time an update of the projection would be needed. The same aggregate would be computed across multiple projectors at the same time, which makes this solution not very efficient.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;😎 Unless... !&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The trick we came up with at Kumo is implementing &lt;strong&gt;state carried events&lt;/strong&gt;. Instead of computing the same aggregate in every projector and giving them all access to the event ledger, the &lt;strong&gt;aggregate&lt;/strong&gt; is &lt;strong&gt;computed once&lt;/strong&gt; and it is &lt;strong&gt;attached to the dispatched events&lt;/strong&gt;. It takes the form of a new Lambda function that takes only one event in argument, computes the aggregate and attaches it to the event before republishing it. We named it &lt;code&gt;dispatchAggregate&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The fanout still transforms an array of streams to published EventBridge events triggering the dispatchAggregate Lambda. It also has still very &lt;strong&gt;low risk of failing&lt;/strong&gt; by not adding the action of computing the aggregate out of all DynamoDB events.&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%2Fraw.githubusercontent.com%2FMaximeVivier%2FArticles%2Fmaster%2Fblog-posts%2Fevent-sourcing-serveless%2Fassets%2FarchiWithDispatchAggregate.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%2Fraw.githubusercontent.com%2FMaximeVivier%2FArticles%2Fmaster%2Fblog-posts%2Fevent-sourcing-serveless%2Fassets%2FarchiWithDispatchAggregate.png" title="Data synchronization for CQRS with AWS serverless technologies with aggregate dispatcher" alt="Data synchronization for CQRS with AWS serverless technologies with aggregate dispatcher"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the EventBridge event such as it is dispatched after the fanout and such as it was listened by projectors before the trick.&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;"userId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"163082d2-b005-75d7-5eb6-aa1f83efd55d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"eventType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Debit"&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;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&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;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&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;And this is the shape of the event triggering the projectors that contain the aggregate.&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;"aggregate"&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;"lastKnownVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"totalBalance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lastMonthBalance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;-20&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;"lastEvent"&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;"userId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"163082d2-b005-75d7-5eb6-aa1f83efd55d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"eventType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Debit"&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;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&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;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&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;h3&gt;
  
  
  ✅ The job of the projectors become trivial
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;aggregate&lt;/strong&gt; represents the &lt;strong&gt;current state&lt;/strong&gt; of the data at the &lt;strong&gt;version&lt;/strong&gt; of the &lt;strong&gt;latest event&lt;/strong&gt; taken into account. Furthermore, there is no rule about the shape of the aggregate and it can be customized in the most exploitable way. For this use case, it can be built with keys being all the projections.&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;"lastKnownVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"totalBalance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lastMonthBalance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;-20&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;With an access to the aggregate, projectors have at their disposal the &lt;strong&gt;exact value of the projection&lt;/strong&gt; they each handle and the last version of this data. So it's not necessary to update the projection based on the previous value anymore. The projector can &lt;strong&gt;simply&lt;/strong&gt; use the &lt;strong&gt;PutItem&lt;/strong&gt; command of DynamoDB to &lt;strong&gt;overwrite the previous&lt;/strong&gt; value only if the version incoming is strictly superior to current one stored. The projections become idempotent and they allow more flexibility in the way they are triggered.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔂 The replay is then extremely simple 👌
&lt;/h3&gt;

&lt;p&gt;It's as simple as writing a &lt;code&gt;replay&lt;/code&gt; type event in the ledger. All projectors need to listen to the corresponding EventBridge event. Then every time a &lt;code&gt;replay&lt;/code&gt; type event is written in the ledger, all projectors update their projection with their latest version.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🔖 Do not forget to ignore this &lt;code&gt;replay&lt;/code&gt; type event in the reducer when computing the aggregate&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  📣 A possible drawback of the dispatchAggregate lambda
&lt;/h2&gt;

&lt;p&gt;The computation time of the dispatchAggregate lambda adds run time to the process. But it is acceptable after checking the results we have on our lambda.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;run time&lt;/strong&gt; of the dispatchAggregate lambda with a &lt;strong&gt;cold start&lt;/strong&gt; is &lt;strong&gt;250ms&lt;/strong&gt; on average and pretty much consistently.&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%2Fraw.githubusercontent.com%2FMaximeVivier%2FArticles%2Fmaster%2Fblog-posts%2Fevent-sourcing-serveless%2Fassets%2FdispatchAggregate-cold-start.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%2Fraw.githubusercontent.com%2FMaximeVivier%2FArticles%2Fmaster%2Fblog-posts%2Fevent-sourcing-serveless%2Fassets%2FdispatchAggregate-cold-start.png" title="Aggregate dispatcher cold start" alt="Aggregate dispatcher cold start"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But the &lt;strong&gt;run time&lt;/strong&gt; of the same lambda when it's already hot is between &lt;strong&gt;30ms&lt;/strong&gt; and &lt;strong&gt;80ms&lt;/strong&gt; for the slowest runs, depending on the &lt;code&gt;userId&lt;/code&gt; in question.&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%2Fraw.githubusercontent.com%2FMaximeVivier%2FArticles%2Fmaster%2Fblog-posts%2Fevent-sourcing-serveless%2Fassets%2FdispatchAggregate-30ms.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%2Fraw.githubusercontent.com%2FMaximeVivier%2FArticles%2Fmaster%2Fblog-posts%2Fevent-sourcing-serveless%2Fassets%2FdispatchAggregate-30ms.png" title="Aggregate dispatcher run time" alt="Aggregate dispatcher run time"&gt;&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%2Fraw.githubusercontent.com%2FMaximeVivier%2FArticles%2Fmaster%2Fblog-posts%2Fevent-sourcing-serveless%2Fassets%2FdispatchAggregate-80ms.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%2Fraw.githubusercontent.com%2FMaximeVivier%2FArticles%2Fmaster%2Fblog-posts%2Fevent-sourcing-serveless%2Fassets%2FdispatchAggregate-80ms.png" title="Aggregate dispatcher run time" alt="Aggregate dispatcher run time"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The latency is very low in general because there is only one gateway through which the events go, therefore the lambda is hot most of the time.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The run time topic may become an issue when the &lt;strong&gt;number of events&lt;/strong&gt; becomes &lt;strong&gt;enormous&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For computing the aggregate, &lt;strong&gt;all events&lt;/strong&gt; must be &lt;strong&gt;fetched&lt;/strong&gt; and reduced within the lambda and it might be really long to handle that much events. But here again, there is a solution. In fact, &lt;strong&gt;snapshots of the aggregate&lt;/strong&gt; can be stored in the event ledger. A snapshot representing the aggregate of version N allow &lt;strong&gt;not to worry about events&lt;/strong&gt; of versions strictly inferior to N and start from this one when computing the aggregate.&lt;/p&gt;




&lt;h2&gt;
  
  
  📈 What's left for us to do to implement the most effective solution with the latest releases
&lt;/h2&gt;

&lt;p&gt;Both the issues I've mentioned about the retry policy of the lambda plugged to the streams and the number of lambda you can plug to a stream is no longer true since this summer 2022.&lt;/p&gt;

&lt;p&gt;In terms of efficiency, we &lt;strong&gt;still need a single lambda&lt;/strong&gt; to compute the &lt;strong&gt;aggregate&lt;/strong&gt; since it's time-consuming operation, so the rule of plugging multiple lambda to the streams is not a rule that will change this architecture.&lt;/p&gt;

&lt;p&gt;The opportunity here is that we can have the lambda dispatching the events to the projectors failing without worrying about and infinite retry or loosing data. The &lt;strong&gt;error handling&lt;/strong&gt; allows the configuration of &lt;strong&gt;destination for failed-events&lt;/strong&gt; and for a &lt;strong&gt;retry policy of my choice&lt;/strong&gt;. Having both a fanout that must not fail and a dispatchAggregate to compute and attach the aggregate to the event becomes useless. A step can now be skipped by having the &lt;strong&gt;dispatchAggregate receiving the DynamoDB streams&lt;/strong&gt;, computing and attaching the aggregate to the events before publishing them in EventBridge. A &lt;strong&gt;retry policy&lt;/strong&gt; and a &lt;strong&gt;destination for failed-events&lt;/strong&gt; must be configured on this lambda.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FMaximeVivier%2FArticles%2Fmaster%2Fblog-posts%2Fevent-sourcing-serveless%2Fassets%2FfinalArchiWoFanout.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%2Fraw.githubusercontent.com%2FMaximeVivier%2FArticles%2Fmaster%2Fblog-posts%2Fevent-sourcing-serveless%2Fassets%2FfinalArchiWoFanout.png" title="Data synchronization for CQRS with AWS serverless technologies with aggregate dispatcher" alt="Data synchronization for CQRS with AWS serverless technologies with aggregate dispatcher"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This should make it the &lt;strong&gt;most efficient&lt;/strong&gt; and &lt;strong&gt;up to date solution&lt;/strong&gt; for &lt;strong&gt;synchronizing data&lt;/strong&gt; between an event ledger and projections in an event sourcing application.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;🎁 We have learned a lot about event-sourcing at Kumo in many projects. We centralized all these learnings in a library: &lt;a href="https://github.com/castore-dev/castore" rel="noopener noreferrer"&gt;Castore&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>serverless</category>
      <category>eventsourcing</category>
      <category>aws</category>
      <category>cqrs</category>
    </item>
  </channel>
</rss>
