<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Michael Walmsley</title>
    <description>The latest articles on Forem by Michael Walmsley (@walmsles).</description>
    <link>https://forem.com/walmsles</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%2F720235%2F297f4637-f800-4669-ac9c-4ab63edd2f1e.jpg</url>
      <title>Forem: Michael Walmsley</title>
      <link>https://forem.com/walmsles</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/walmsles"/>
    <language>en</language>
    <item>
      <title>Lambda Durable Functions - Keeping your Payloads Secure</title>
      <dc:creator>Michael Walmsley</dc:creator>
      <pubDate>Thu, 22 Jan 2026 02:12:48 +0000</pubDate>
      <link>https://forem.com/aws-heroes/lambda-durable-functions-keeping-your-payloads-secure-2bnm</link>
      <guid>https://forem.com/aws-heroes/lambda-durable-functions-keeping-your-payloads-secure-2bnm</guid>
      <description>&lt;p&gt;AWS Lambda Durable Functions represent a significant evolution in how we build serverless workflows. Instead of managing complex state machines or external orchestrators, developers can write straightforward sequential code that spans minutes, hours, or even days. The framework handles checkpointing, replay, and recovery automatically. However, this convenience introduces a security consideration that deserves careful attention: your workflow data gets persisted in checkpoints, and that data may contain sensitive information.&lt;/p&gt;

&lt;p&gt;In this article we will explore the deeper parts of the Lambda Durable SDK and explore how you can go about securing your precious payload data.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are Durable Functions?
&lt;/h2&gt;

&lt;p&gt;Lambda Durable Functions enable you to write long-running workflows as regular Lambda code. The durable execution SDK provides primitives like steps, waits, and callbacks that automatically create checkpoints of your function's progress. When your function pauses for a wait operation or gets interrupted by a failure, Lambda can resume exactly where it left off by replaying the checkpointed operations.&lt;/p&gt;

&lt;p&gt;The checkpoint and replay mechanism works by storing the results of each durable operation. When your function executes a step that calls an external API, the SDK captures that step's return value in a checkpoint before continuing. If your function later pauses or fails, the next invocation replays from the beginning but uses the stored checkpoint results instead of re-executing completed steps. This approach allows your code to remain deterministic while spanning execution times far beyond Lambda's 15-minute invocation limit.&lt;/p&gt;

&lt;p&gt;For a comprehensive introduction to durable functions, I recommend reading the &lt;a href="https://aws.amazon.com/blogs/aws/build-multi-step-applications-and-ai-workflows-with-aws-lambda-durable-functions/" rel="noopener noreferrer"&gt;AWS Lambda durable functions announcement&lt;/a&gt; and reviewing the &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Eric Johnson has a great series of articles covering Lambda Durable Functions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/aws/developing-aws-lambda-durable-functions-with-aws-sam-ga9"&gt;Developing AWS Lambda Durable Functions with AWS SAM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/aws/aws-lambda-durable-functions-build-workflows-that-last-3ac7"&gt;AWS Lambda Durable Functions: Build Workflows That Last&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/aws/is-this-code-deterministic-29l9"&gt;Is this code deterministic?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Where Checkpoint Data Lives and the Security Model
&lt;/h2&gt;

&lt;p&gt;Understanding where your checkpoint data gets stored is essential for implementing proper security controls. Lambda stores durable execution state in an AWS-managed service infrastructure. When you enable durable execution on a Lambda function, AWS manages all the checkpoint storage behind the scenes. Your function's execution role requires permissions for &lt;code&gt;lambda:CheckpointDurableExecution&lt;/code&gt; and &lt;code&gt;lambda:GetDurableExecutionState&lt;/code&gt; to interact with this storage, and a function execution is only able to access its own checkpoint data. The actual data lives in AWS-managed infrastructure, not in resources within your AWS account.&lt;/p&gt;

&lt;p&gt;Each checkpoint contains the operation type, operation name, input parameters, output values, and metadata like timestamps and retry counts. For a payment processing workflow, this means the checkpoint storage contains customer payment details, credit card information, or other personally identifiable information depending on what data flows through your durable operations.&lt;/p&gt;

&lt;p&gt;Lambda automatically encrypts checkpoint data at rest using AWS-owned encryption keys and in transit using TLS when Lambda reads or writes checkpoints. However, AWS does not currently support using customer-managed KMS keys (CMK) for checkpoint storage. You cannot control key rotation, access policies, or audit logging for the storage encryption itself. For many applications, this level of security is perfectly adequate. The data remains encrypted at rest and in transit, and access is controlled through IAM permissions on the checkpoint APIs.&lt;/p&gt;

&lt;p&gt;However, organizations with strict compliance requirements around encryption key management need additional controls beyond what the default checkpoint storage provides. Healthcare applications subject to HIPAA, financial services handling payment card data under PCI-DSS, or government systems with data sovereignty requirements often mandate customer-controlled encryption keys. This is where custom serialization becomes essential and the Lambda Durable Functions team have provided SDK hooks for custom serialisation and deserialisation (SerDes) for payload checkpointing.&lt;/p&gt;

&lt;p&gt;The AWS documentation explicitly states that when you implement custom encryption through serializers and deserializers, you lose visibility of operation results in the Lambda console and API responses. Checkpoint data appears encrypted in execution history and cannot be inspected without decryption. This trade-off between security and operational visibility is unavoidable with the current architecture. You must decide which is more important for your use case.&lt;/p&gt;

&lt;h2&gt;
  
  
  Full Payload Encryption
&lt;/h2&gt;

&lt;p&gt;The recommendation is implementing custom encryption using the SDK's SerDes mechanism when you need additional security controls beyond the default encryption (&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/durable-security.html" rel="noopener noreferrer"&gt;source&lt;/a&gt;). This approach encrypts the entire step result before it reaches the checkpoint storage. You maintain full control over the encryption keys, can use customer-managed KMS keys, and can implement any encryption algorithm your compliance requirements demand.&lt;/p&gt;

&lt;p&gt;The implementation involves creating a custom serializer class that encrypts data during checkpoint creation and decrypts it during replay. &lt;/p&gt;

&lt;h3&gt;
  
  
  TypeScript SerDes Implementation
&lt;/h3&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;Serdes&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/durable-execution-sdk-js&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;DecryptCommand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;EncryptCommand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;KMSClient&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-kms&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;kmsClient&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;KMSClient&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;KMS_KEY_ID&lt;/span&gt; &lt;span class="o"&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;KMS_KEY_ID&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// SerdesContext interface from the SDK (not exported in v1.0.1)&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;SerdesContext&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;entityId&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;durableExecutionArn&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;class&lt;/span&gt; &lt;span class="nc"&gt;KmsSerDer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;Serdes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&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;async&lt;/span&gt; &lt;span class="nf"&gt;serialize&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;T&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SerdesContext&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="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&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;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;KMS_KEY_ID&lt;/span&gt;&lt;span class="p"&gt;)&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;KMS_KEY_ID environment variable is not set&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;plaintext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&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;encoder&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;TextEncoder&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;plaintextBytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;encoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;plaintext&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;EncryptCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;KeyId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;KMS_KEY_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;Plaintext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;plaintextBytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;EncryptionContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;entityId&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;entityId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;durableExecutionArn&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;durableExecutionArn&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;kmsClient&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CiphertextBlob&lt;/span&gt;&lt;span class="p"&gt;)&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;Encryption failed: no ciphertext returned&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;// Convert to base64 for storage&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CiphertextBlob&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&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;async&lt;/span&gt; &lt;span class="nf"&gt;deserialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="na"&gt;encryptedData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SerdesContext&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="nx"&gt;T&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&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;encryptedData&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Convert from base64&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ciphertextBlob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;encryptedData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&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;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;DecryptCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;CiphertextBlob&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ciphertextBlob&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;EncryptionContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;entityId&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;entityId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;durableExecutionArn&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;durableExecutionArn&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;kmsClient&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Plaintext&lt;/span&gt;&lt;span class="p"&gt;)&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;Decryption failed: no plaintext returned&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;decoder&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;TextDecoder&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;plaintext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;decoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Plaintext&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;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;plaintext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;T&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;h3&gt;
  
  
  Applying SerDes to a Durable Step
&lt;/h3&gt;

&lt;p&gt;Once you have created a class implementing the SerDes interface (shown above) you can apply that to each step where you want custom serialisation to be used.&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;// Use KMS encryption for the order step&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;order&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;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;&amp;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-order&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;return&lt;/span&gt; &lt;span class="nf"&gt;createOrder&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="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;serdes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;kmsSerDes&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 example implementation makes direct use of KMS keys which have an upper limit of 4KB on payload size for encryption, which may not be suitable for all payload types.  Where you need to encrypt more than 4KB of data you will need to make use of the AWS encryption SDK and use envelope encryption. &lt;/p&gt;

&lt;p&gt;Envelope encryption is where your data is encrypted with a unique data encryption key (DEK), and that DEK is then encrypted with your KMS master key. This approach means you can encrypt large amounts of data quickly with symmetric encryption while KMS only needs to encrypt/decrypt the small DEK, and the encrypted message contains both the encrypted data and the encrypted DEK together in one portable blob.&lt;/p&gt;

&lt;p&gt;In the github link at the end of this article I have provided examples of both implementations - direct KMS and AWS Envelope encryption.&lt;/p&gt;

&lt;h3&gt;
  
  
  What This Looks Like in Production
&lt;/h3&gt;

&lt;p&gt;When you view the durable execution in the Lambda console, the checkpoint data appears as an encrypted blob. The execution timeline shows that steps completed successfully, but you cannot see the actual data without decrypting it which reduces operational visibility and increases support friction.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wthout Encryption
&lt;/h3&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;"EventId"&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;"EventTimestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-01-21T12:53:09.678Z"&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;"StepSucceeded"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"c4ca4238a0b92382"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"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;"create-order"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"StepSucceededDetails"&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;"RetryDetails"&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;"CurrentAttempt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&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;"Result"&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;"Truncated"&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;"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="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;customer&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;customer_id&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;123456789&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;John Doe&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;email&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;john@example.com&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;ssn&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;123-45-6789&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;address&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;123 Main St&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;},&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;payment&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;method&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;credit_card&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;creditCard&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;4111-1111-1111-1111&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;amount&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:100},&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;items&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:[{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;123&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;quantity&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:2},{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;456&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;quantity&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:2}]}"&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;"SubType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Step"&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;
  
  
  With Encryption
&lt;/h3&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;"EventId"&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;"EventTimestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-01-21T13:46:04.182Z"&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;"StepSucceeded"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"c4ca4238a0b92382"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"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;"create-order"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"StepSucceededDetails"&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;"RetryDetails"&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;"CurrentAttempt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&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;"Result"&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;"Truncated"&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;"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;"AQICAHjFMg8rj2ThqXWiaabEbpqoKCF9xVaTOZP6HOCD17VYhwHddxCX5VW6qI5ehwq4zFGyAAABADCB/QYJKoZIhvcNAQcGoIHvMIHsAgEAMIHmBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDOc8WQxUXh32Isp/sQIBEICBuJWXZ3ST7HPxVuZH3epnhKwcyr2llFzKlUQrzxHxbGkqvNc9o2uhsbzGCq0Lnxq5uAa8aVRYR3Q7ijoGWctSa0NdAIv1FxiUlk9lh7fOALzZuifPxrmcQL4LZTl/ZQBje9GUfNZ9bhI4UbdayCqD8tY6CdYzEWzI2xIoBlAPHdwSX4Y9MGVGFJD8kyhb/oSdJBQmqieqT1DcGsxAAOYEKEtsL/V9YOC4NbxKgnyR7j4YGg+Y+2ImvdU="&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;"SubType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Step"&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;You can see that your workflow progressed through its steps, but the sensitive data remains encrypted and un-readable. This provides strong security guarantees at the cost of operational visibility. When troubleshooting a failed workflow, you cannot simply look at the console to see what data was being processed. You must decrypt the checkpoint data programmatically, which requires appropriate KMS permissions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Field-Level Encryption: Preserving Structure for Operations
&lt;/h2&gt;

&lt;p&gt;Full payload encryption solves the security problem but introduces a significant operational challenge. Once encrypted, your checkpoint data becomes completely opaque. You cannot determine which customer's order failed. You cannot identify patterns in failures across different payment amounts.  The lack of visibility makes production operations substantially harder.&lt;/p&gt;

&lt;p&gt;A more sophisticated approach involves encrypting only the sensitive fields while preserving the overall structure of your data. This technique provides the security benefits of encryption while maintaining the operational visibility needed for production systems. The key insight is that not all data in your checkpoints requires encryption. Order IDs, timestamps, workflow states, and similar metadata can remain in plaintext for operational purposes.&lt;/p&gt;

&lt;p&gt;Field-level encryption works by replacing sensitive fields with placeholder objects and encrypting all the sensitive values together in a single operation. The encrypted blob and the modified structure both get stored in the checkpoint. When the workflow resumes, the SDK automatically decrypts the blob and restores the original field values. Despite encrypting multiple fields, this approach makes only a single KMS call, maintaining the performance benefit of batch encryption.&lt;/p&gt;

&lt;h3&gt;
  
  
  TypeScript Implementation
&lt;/h3&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;Serdes&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/durable-execution-sdk-js&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;buildClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;CommitmentPolicy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;KmsKeyringNode&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-crypto/client-node&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;KMS_KEY_ARN&lt;/span&gt; &lt;span class="o"&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;KMS_KEY_ARN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// SerdesContext interface from the SDK (not exported in v1.0.1)&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;SerdesContext&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;entityId&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;durableExecutionArn&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="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;EncryptedPayload&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;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="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;__encrypted_pii&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="cm"&gt;/**
 * Field-level encryption SerDes using AWS Encryption SDK with envelope encryption
 * This provides better performance for large payloads by using data keys
 */&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;EnvelopeEncryptionSerDes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;Serdes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&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;private&lt;/span&gt; &lt;span class="na"&gt;fieldPaths&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="k"&gt;private&lt;/span&gt; &lt;span class="na"&gt;encryptionContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&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="p"&gt;,&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="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="na"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;ReturnType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;buildClient&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;private&lt;/span&gt; &lt;span class="na"&gt;keyring&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;KmsKeyringNode&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * @param fieldPaths - Array of dot-notation paths to fields that should be encrypted
   *                     e.g., ['customer.ssn', 'payment.creditCard']
   * @param encryptionContext - Additional encryption context
   */&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="na"&gt;fieldPaths&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="na"&gt;encryptionContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&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="p"&gt;,&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;=&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fieldPaths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fieldPaths&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;encryptionContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;encryptionContext&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Build the encryption client with commitment policy&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;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buildClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CommitmentPolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REQUIRE_ENCRYPT_REQUIRE_DECRYPT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;getKeyring&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;KmsKeyringNode&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="o"&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;keyring&lt;/span&gt;&lt;span class="p"&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;KMS_KEY_ARN&lt;/span&gt;&lt;span class="p"&gt;)&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;KMS_KEY_ARN environment variable is not set&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;// Create KMS keyring for envelope encryption&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;keyring&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;KmsKeyringNode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;generatorKeyId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;KMS_KEY_ARN&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;return&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;keyring&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;serialize&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;T&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SerdesContext&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="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&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;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Clone the object to avoid mutating the original&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;clonedValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="c1"&gt;// Extract sensitive fields&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;sensitiveData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&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="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="k"&gt;of&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;fieldPaths&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;fieldValue&lt;/span&gt; &lt;span class="o"&gt;=&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;getNestedValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clonedValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;path&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;fieldValue&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;sensitiveData&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fieldValue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="c1"&gt;// Replace with marker&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;setNestedValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clonedValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;__encrypted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Encrypt the sensitive data if any fields were found&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;encryptedPii&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sensitiveData&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&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;plaintext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sensitiveData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// Build encryption context&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fullContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;entityId&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;entityId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;durableExecutionArn&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;durableExecutionArn&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;encryptionContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;

      &lt;span class="c1"&gt;// Encrypt using AWS Encryption SDK (envelope encryption)&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;result&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&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="nf"&gt;encrypt&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="nf"&gt;getKeyring&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="nx"&gt;plaintext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;encryptionContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fullContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// Convert to base64 for storage&lt;/span&gt;
      &lt;span class="nx"&gt;encryptedPii&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&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;// Build the final payload&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EncryptedPayload&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&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;clonedValue&lt;/span&gt;&lt;span class="p"&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;encryptedPii&lt;/span&gt;&lt;span class="p"&gt;)&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;__encrypted_pii&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;encryptedPii&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;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;deserialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="na"&gt;encryptedData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SerdesContext&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="nx"&gt;T&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&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;encryptedData&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;undefined&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="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EncryptedPayload&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;encryptedData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// If there's encrypted PII, decrypt it&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;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__encrypted_pii&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;ciphertext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&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;__encrypted_pii&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// Decrypt using AWS Encryption SDK&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;plaintext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;messageHeader&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&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="nf"&gt;decrypt&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="nf"&gt;getKeyring&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="nx"&gt;ciphertext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// Verify encryption context matches&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expectedContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;entityId&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;entityId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;durableExecutionArn&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;durableExecutionArn&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;encryptionContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;

      &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expectedContext&lt;/span&gt;&lt;span class="p"&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;messageHeader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;encryptionContext&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&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="s2"&gt;`Encryption context mismatch for key &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: expected &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, got &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;messageHeader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;encryptionContext&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;plaintextString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;plaintext&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&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="na"&gt;sensitiveData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&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="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;plaintextString&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// Restore the sensitive fields&lt;/span&gt;
      &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sensitiveData&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="nf"&gt;setNestedValue&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;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="c1"&gt;// Remove the encryption metadata&lt;/span&gt;
      &lt;span class="k"&gt;delete&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;__encrypted_pii&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;payload&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;getNestedValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;obj&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="na"&gt;path&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;unknown&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;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;part&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&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;current&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&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;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Record&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="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&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;part&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;current&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;setNestedValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;obj&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="na"&gt;path&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="na"&gt;value&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="k"&gt;void&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;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Record&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="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&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;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&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="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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="nx"&gt;i&lt;/span&gt;&lt;span class="o"&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;part&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&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;current&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;part&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="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Record&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="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&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="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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;value&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;h3&gt;
  
  
  Applying this to Workflow Steps
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="c1"&gt;// Create dedicated SerDes using AWS Encryption SDK with envelope encryption&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;orderSerDes&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;EnvelopeEncryptionSerDes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Order&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;customer.ssn&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;payment.creditCard&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;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;order-processing&lt;/span&gt;&lt;span class="dl"&gt;'&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="p"&gt;...&lt;/span&gt;

  &lt;span class="c1"&gt;// Use envelope encryption (AWS Encryption SDK) for the order step&lt;/span&gt;
  &lt;span class="c1"&gt;// Only customer.ssn and payment.creditCard will be encrypted&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;order&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;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;&amp;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-order&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;return&lt;/span&gt; &lt;span class="nf"&gt;createOrder&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="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;serdes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;orderSerDes&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;h3&gt;
  
  
  What Field-Level Encryption Looks Like in the Console
&lt;/h3&gt;

&lt;p&gt;With field-level encryption, the Lambda console displays a much more useful view of your checkpoint data. The execution history shows the workflow structure with sensitive fields clearly marked as encrypted.&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;"EventId"&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;"EventTimestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-01-21T14:29:08.022Z"&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;"StepSucceeded"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"c4ca4238a0b92382"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"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;"create-order"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"StepSucceededDetails"&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;"RetryDetails"&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;"CurrentAttempt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&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;"Result"&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;"Truncated"&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;"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="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;order-1769005747947&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;customerId&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;123456789&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;customer&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;customer_id&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;123456789&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;John Doe&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;email&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;john@example.com&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;ssn&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;__encrypted&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;customer.ssn&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;},&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;address&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;123 Main St&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;},&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;payment&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;method&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;credit_card&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;creditCard&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;__encrypted&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;payment.creditCard&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;},&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;amount&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:100},&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;items&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:[{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;123&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;quantity&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:2},{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;456&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;quantity&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:2}],&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;createdAt&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;2026-01-21T14:29:07.947Z&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;__encrypted_pii&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;AgV4x2uTyXhneXm5Lt2L3qRfCcsJ0WCJZxgt8uLZdSLxp9EBjgAFABVhd3MtY3J5cHRvLXB1YmxpYy1rZXkAREE1aFpLbWdvUk5RYU9PM1h2VThnRjFqRW9raXErZXpBZUlVM2dnVmZxTDBpTHlOYWpNZFlzRXM3dE05YVdkUTl5dz09ABNkdXJhYmxlRXhlY3V0aW9uQXJuANdhcm46YXdzOmxhbWJkYTphcC1zb3V0aGVhc3QtMjo4NTk1Nzg2NTExMDI6ZnVuY3Rpb246TGFtYmRhRHVyYWJsZUZ1bmN0aW9uRXhhbS1FbnZlbG9wZVdvcmtmbG93RnVuY3Rpb24teTNPNndQM1d4bXBkOiRMQVRFU1QvZHVyYWJsZS1leGVjdXRpb24vYzkzNTk5ODgtMDQ4ZS00NTE5LWE0YTctNmU2ZWI2YzYwNjU4LzkwNWVkNjQ3LTc5OTctM2JmNi05Y2M4LWZiYjVkMTBhYjQ0YwAIZW50aXR5SWQAATEAC2Vudmlyb25tZW50AApwcm9kdWN0aW9uAAdzZXJ2aWNlABBvcmRlci1wcm9jZXNzaW5nAAEAB2F3cy1rbXMAUGFybjphd3M6a21zOmFwLXNvdXRoZWFzdC0yOjg1OTU3ODY1MTEwMjprZXkvYzVmNGNkYzMtMWYyMy00NDUyLWEwMDAtZTdjNDgxODM3MTliALgBAgEAeMUyDyuPZOGpdaJppsRumqgoIX3FVpM5k/oc4IPXtViHAQ4ueB4Z5NN/r6oUnaYcKKQAAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAwo28yWtu49aAyYS2ACARCAO825YL1fsuSZUxS1VcxP1CgsTtoIfc+rtwUVUKgSh1+UwvXxZTxN7RVEPu/IK7GIGY9QsDi3zPV6zxUaAgAAEAAebuVheeSBpQIdicZSiFGZliUt3IPJxLhFQ4974/vNuEWWFb7JD1RZNub7/iCA++7/////AAAAAQAAAAAAAAAAAAAAAQAAAEk4GWvzu2BZDrdKhUKFM3zR1A6b2I/0DR/Bfs+gdGWfBsn+m4CCCq/zsYElqc8wGroQS3bd1m3+0+Qea+wwRGv2vPBJD7RldQQPfNr4AwI22ckfiFQYwH65eABoMGYCMQCI+jB0SXbxSQs9Pyvil1sjjM0kkZ+YhWyxcirggWnLzE43xNfB2U2IarmN+k0kE+wCMQDSbQSTleKz4yzJG/AXcyUQ2ohcFFtYZcOzlsImr1a7tRgOfrJ2sRxYhQF64tPtMrw=&lt;/span&gt;&lt;span class="se"&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;span class="nl"&gt;"SubType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Step"&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;h4&gt;
  
  
  JSON formatted Payload
&lt;/h4&gt;

&lt;p&gt;Encrypted fields have been replaced with an enryption placeholder rather than showing the actual data.  The encrypted data is still in the payload but at the end in a single blob, the overall payload structure is maintained.&lt;/p&gt;

&lt;p&gt;Using placeholder text enables robust decryption to occur using the actual object names rather than relying on order of fields which can be haphazard.&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;"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;"order-1769005747947"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"customerId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"123456789"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"customer"&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;"customer_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;"123456789"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"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;"John Doe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"john@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ssn"&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;"__encrypted"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"customer.ssn"&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;"address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"123 Main St"&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;"payment"&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;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"credit_card"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"creditCard"&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;"__encrypted"&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.creditCard"&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;100&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;"items"&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;"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;"123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"quantity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&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;"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;"456"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"quantity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&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;"createdAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-01-21T14:29:07.947Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"__encrypted_pii"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AgV4x2uTyXhneXm5Lt2L3qRfCcsJ0WCJZxgt8uLZdSLxp9EBjgAFABVhd3MtY3J5cHRvLXB1YmxpYy1rZXkAREE1aFpLbWdvUk5RYU9PM1h2VThnRjFqRW9raXErZXpBZUlVM2dnVmZxTDBpTHlOYWpNZFlzRXM3dE05YVdkUTl5dz09ABNkdXJhYmxlRXhlY3V0aW9uQXJuANdhcm46YXdzOmxhbWJkYTphcC1zb3V0aGVhc3QtMjo4NTk1Nzg2NTExMDI6ZnVuY3Rpb246TGFtYmRhRHVyYWJsZUZ1bmN0aW9uRXhhbS1FbnZlbG9wZVdvcmtmbG93RnVuY3Rpb24teTNPNndQM1d4bXBkOiRMQVRFU1QvZHVyYWJsZS1leGVjdXRpb24vYzkzNTk5ODgtMDQ4ZS00NTE5LWE0YTctNmU2ZWI2YzYwNjU4LzkwNWVkNjQ3LTc5OTctM2JmNi05Y2M4LWZiYjVkMTBhYjQ0YwAIZW50aXR5SWQAATEAC2Vudmlyb25tZW50AApwcm9kdWN0aW9uAAdzZXJ2aWNlABBvcmRlci1wcm9jZXNzaW5nAAEAB2F3cy1rbXMAUGFybjphd3M6a21zOmFwLXNvdXRoZWFzdC0yOjg1OTU3ODY1MTEwMjprZXkvYzVmNGNkYzMtMWYyMy00NDUyLWEwMDAtZTdjNDgxODM3MTliALgBAgEAeMUyDyuPZOGpdaJppsRumqgoIX3FVpM5k/oc4IPXtViHAQ4ueB4Z5NN/r6oUnaYcKKQAAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAwo28yWtu49aAyYS2ACARCAO825YL1fsuSZUxS1VcxP1CgsTtoIfc+rtwUVUKgSh1+UwvXxZTxN7RVEPu/IK7GIGY9QsDi3zPV6zxUaAgAAEAAebuVheeSBpQIdicZSiFGZliUt3IPJxLhFQ4974/vNuEWWFb7JD1RZNub7/iCA++7/////AAAAAQAAAAAAAAAAAAAAAQAAAEk4GWvzu2BZDrdKhUKFM3zR1A6b2I/0DR/Bfs+gdGWfBsn+m4CCCq/zsYElqc8wGroQS3bd1m3+0+Qea+wwRGv2vPBJD7RldQQPfNr4AwI22ckfiFQYwH65eABoMGYCMQCI+jB0SXbxSQs9Pyvil1sjjM0kkZ+YhWyxcirggWnLzE43xNfB2U2IarmN+k0kE+wCMQDSbQSTleKz4yzJG/AXcyUQ2ohcFFtYZcOzlsImr1a7tRgOfrJ2sRxYhQF64tPtMrw="&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;__encrypted_pii&lt;/code&gt; field contains the secure JSON data as an object.&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;"customer.ssn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"123-45-6789"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"payment.creditCard"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"4111-1111-1111-1111"&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 JSON is then:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Stringified to plaintext&lt;/li&gt;
&lt;li&gt;Encrypted with KMS (produces binary ciphertext)&lt;/li&gt;
&lt;li&gt;Base64 encoded for JSON storage&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The operational benefits become immediately clear. You can see the order ID, customer name, email, and payment amount in plaintext. When troubleshooting a failed payment, you can identify the specific order and see the payment amount without needing to decrypt anything.  You can identify patterns like failures occurring only for payments above certain amounts. The sensitive fields are clearly marked with the &lt;code&gt;__encrypted&lt;/code&gt; placeholder, making it obvious what data is protected while keeping everything else visible for operational purposes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making This Readily Available
&lt;/h2&gt;

&lt;p&gt;The field-level encryption pattern solves a real problem that many developers face when building production durable functions. Rather than requiring every team to implement their own version, this functionality belongs in a reusable utility. I have submitted an RFC proposal to extend the DataMasking utility in Python to include field level encryption and an RFC for Typescript Powertools to implement and expose the same utility with full feature parity (Data Masking not available in Typescript Powertools).&lt;/p&gt;

&lt;p&gt;If you find this functionality valuable for your durable functions, I encourage you to review and support the RFCs. Your feedback and use cases will help shape the final implementation. Visit the &lt;a href="[https://github.com/aws-powertools/powertools-lambda-python/issues](https://github.com/aws-powertools/powertools-lambda-python/issues/7963)"&gt;Python RFC&lt;/a&gt; and &lt;a href="https://github.com/aws-powertools/powertools-lambda-typescript/issues/4960" rel="noopener noreferrer"&gt;TypeScript RFC&lt;/a&gt; to add your voice to the discussion. A thumbs up reaction on the RFC helps demonstrate community demand and prioritization for the Powertools maintainers.&lt;/p&gt;

&lt;p&gt;The security of your checkpoint data matters. Whether you choose full encryption for maximum security or field-level encryption for operational visibility, having a well-tested, reusable implementation will make securing your durable functions significantly easier. The examples in this article provide working code you can use today, and the Powertools RFCs aim to make this functionality available as a maintained, tested utility for the entire serverless community.&lt;/p&gt;

&lt;p&gt;Full source code is available on GitHub &lt;a href="https://github.com/walmsles/lambda-durable-function-encryption" rel="noopener noreferrer"&gt;https://github.com/walmsles/lambda-durable-function-encryption&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>security</category>
      <category>serverless</category>
    </item>
    <item>
      <title>Run Any MCP Server Securely Without Changing Its Config</title>
      <dc:creator>Michael Walmsley</dc:creator>
      <pubDate>Tue, 06 Jan 2026 12:55:43 +0000</pubDate>
      <link>https://forem.com/walmsles/run-any-mcp-server-securely-without-changing-its-config-426j</link>
      <guid>https://forem.com/walmsles/run-any-mcp-server-securely-without-changing-its-config-426j</guid>
      <description>&lt;p&gt;MCP servers are powerful - they give Claude Desktop or other AI assistants access to your filesystem, databases, APIs, and more. But that power comes with risk. Every MCP server runs with your full user permissions. A bug or malicious MCP package could read your SSH keys, AWS credentials, or browser cookies.&lt;/p&gt;

&lt;p&gt;When you add an MCP server to Claude Desktop, you're running arbitrary code on your machine. That filesystem server? It can see everything your user can see. That database tool? It has access to your entire home directory.  Most of us just... trust it. We copy the config from the README and hope for the best, because we want immediate results.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Traditional Container Approach
&lt;/h2&gt;

&lt;p&gt;Some security-conscious users already run MCP servers in containers using Docker, or other container runtimes. Here's what that looks like:&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;"mcpServers"&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;"sqlite"&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;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"docker"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&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;"run"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"-i"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"--rm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"-v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/home/michael/data:/data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"-v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/home/michael/.cache/uv:/home/mcp/.cache/uv"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"-e"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HOME=/home/mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"--user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1000:1000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ghcr.io/serverlessdna/mcp-python:latest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"uvx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mcp-server-sqlite"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"--db-path"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/data/mydb.sqlite"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's 12 lines of Docker arguments you need to get right and it looks nothing like the example configuration the MCP server provider gives you. Volume mounts, user permissions, environment variables, image tags. Miss one flag and it fails silently or breaks in subtle ways.&lt;/p&gt;

&lt;p&gt;Most people look at that and think "I'll just run it natively".&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What if you could run MCP servers in complete isolation, with access only to what you explicitly allow - and configure them exactly as their READMEs document?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Most MCP server documentation shows something 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;"mcpServers"&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;"sqlite"&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;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"uvx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&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;"mcp-server-sqlite"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"--db-path"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"~/data/mydb.sqlite"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;run-mcp&lt;/code&gt;, your config is almost the same:&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;"mcpServers"&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;"sqlite"&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;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"run-mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&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;"uvx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mcp-server-sqlite"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"--db-path"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/data/mydb.sqlite"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"env"&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;"MCP_MOUNT"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"~/data:/data"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;One word changes.&lt;/strong&gt; Replace &lt;code&gt;uvx&lt;/code&gt; or &lt;code&gt;npx&lt;/code&gt; with &lt;code&gt;run-mcp&lt;/code&gt;, add the original command as the first argument, and declare what the server can access. That's it.&lt;/p&gt;

&lt;p&gt;Three approaches, compared:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Lines of Config&lt;/th&gt;
&lt;th&gt;Container Isolation&lt;/th&gt;
&lt;th&gt;Complexity&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Native (uvx/npx)&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;❌ None&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Raw Docker&lt;/td&gt;
&lt;td&gt;12+&lt;/td&gt;
&lt;td&gt;✅ Full&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;run-mcp&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;✅ Full&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The server runs in a container with zero access to your host - unless you explicitly grant it through a registered mount using the &lt;code&gt;MCP_MOUNT&lt;/code&gt; environment variable as shown in the example.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Changes
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Without run-mcp&lt;/th&gt;
&lt;th&gt;With run-mcp&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Server sees entire home directory&lt;/td&gt;
&lt;td&gt;Server sees only what you mount&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server can read &lt;code&gt;~/.aws&lt;/code&gt;, &lt;code&gt;~/.ssh&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;No access unless you add it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Malicious package = full compromise&lt;/td&gt;
&lt;td&gt;Malicious package = contained&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Servers share your environment&lt;/td&gt;
&lt;td&gt;Each server gets isolated storage&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The security model flips from "access everything by default" to "access nothing by default."&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Replace the command&lt;/strong&gt; - &lt;code&gt;uvx&lt;/code&gt; becomes &lt;code&gt;run-mcp uvx&lt;/code&gt;, &lt;code&gt;npx&lt;/code&gt; becomes &lt;code&gt;run-mcp npx&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mount what you need&lt;/strong&gt; - &lt;code&gt;MCP_MOUNT=~/data:/data&lt;/code&gt; grants explicit access.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Credentials are opt-in&lt;/strong&gt; - Add &lt;code&gt;~/.aws:/home/mcp/.aws:ro&lt;/code&gt; only when the server needs AWS access and provides read-only access.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No Docker knowledge required. No Dockerfile. No docker-compose. Just a single binary that handles everything across all platforms (Windows, macOS, Linux).&lt;/p&gt;

&lt;h2&gt;
  
  
  Real Examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Filesystem Server (Read-Only Access)
&lt;/h3&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;"mcpServers"&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;"filesystem"&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;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"run-mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&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;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@modelcontextprotocol/server-filesystem"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/docs"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"env"&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;"MCP_MOUNT"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"~/Documents:/docs:ro"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server can only read your Documents folder. Nothing else.&lt;/p&gt;

&lt;h3&gt;
  
  
  AWS Server (With Credentials)
&lt;/h3&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;"mcpServers"&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;"aws-api"&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;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"run-mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&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;"uvx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"awslabs.aws-api-mcp-server"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"env"&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;"MCP_MOUNT"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"~/.aws:/home/mcp/.aws:ro"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"AWS_REGION"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server gets read-only access to your AWS credentials. Nothing else on your system is visible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Memory Server (No Host Access)
&lt;/h3&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;"mcpServers"&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;"memory"&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;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"run-mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&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;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@modelcontextprotocol/server-memory"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;MCP_MOUNT&lt;/code&gt; means no host filesystem access at all. The server gets an isolated home directory that persists between runs - but it's completely sandboxed using standard volume features of container runtimes.&lt;/p&gt;

&lt;h2&gt;
  
  
  No Runtime Dependencies
&lt;/h2&gt;

&lt;p&gt;Don't have Node.js 22 installed? Python 3.12? Doesn't matter.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;run-mcp&lt;/code&gt; auto-detects your container runtime - Docker, Podman - and runs the appropriate container. Your machine stays clean. No version conflicts. No global package pollution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Install the binary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Linux (amd64)&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://github.com/serverless-dna/run-mcp/releases/latest/download/run-mcp-linux-amd64 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; ~/.local/bin/run-mcp &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;chmod&lt;/span&gt; +x ~/.local/bin/run-mcp

&lt;span class="c"&gt;# macOS (Apple Silicon)&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://github.com/serverless-dna/run-mcp/releases/latest/download/run-mcp-darwin-arm64 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; ~/.local/bin/run-mcp &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;chmod&lt;/span&gt; +x ~/.local/bin/run-mcp

&lt;span class="c"&gt;# macOS (Intel)&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://github.com/serverless-dna/run-mcp/releases/latest/download/run-mcp-darwin-amd64 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; ~/.local/bin/run-mcp &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;chmod&lt;/span&gt; +x ~/.local/bin/run-mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Windows (PowerShell)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;New-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ItemType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Directory&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Force&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;~\.local\bin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Out-Null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;Invoke-WebRequest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Uri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://github.com/serverless-dna/run-mcp/releases/latest/download/run-mcp-windows-amd64.exe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-OutFile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;~\.local\bin\run-mcp.exe&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Ensure &lt;code&gt;~/.local/bin&lt;/code&gt; is in your PATH, or use the full path to &lt;code&gt;run-mcp&lt;/code&gt; in your Claude Desktop config.&lt;/p&gt;

&lt;p&gt;Update your Claude Desktop config and you are done.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Security Boundary That Should Have Been There
&lt;/h2&gt;

&lt;p&gt;Every MCP server you run is code executing on your machine with your permissions. The MCP protocol is powerful, but it shipped without a security model for the host system.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;run-mcp&lt;/code&gt; adds that missing layer. Same simple configs. Same MCP servers. Now with isolation by default.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/serverless-dna/run-mcp" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/serverless-dna/run-mcp#readme" rel="noopener noreferrer"&gt;Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/orgs/serverless-dna/packages" rel="noopener noreferrer"&gt;Container Images&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Questions or feedback? Open an issue or find me on &lt;a href="https://www.linkedin.com/in/walmsles/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>opensource</category>
      <category>security</category>
    </item>
    <item>
      <title>Web Sockets Don't Pub-Sub</title>
      <dc:creator>Michael Walmsley</dc:creator>
      <pubDate>Sun, 02 Mar 2025 12:26:22 +0000</pubDate>
      <link>https://forem.com/walmsles/web-sockets-dont-pub-sub-jg9</link>
      <guid>https://forem.com/walmsles/web-sockets-dont-pub-sub-jg9</guid>
      <description>&lt;p&gt;Web Sockets have become a fundamental technology in modern web development, but their true function is often misunderstood. The killer use-case for Web Sockets has been in the development and proliferation of chat apps where users jump into a channel and write messages to one another.  This has led to a wide spread understanding that Web Sockets provide publish and subscribe capability for your application - but this is a myth - Web Sockets do not Pub-Sub.  In this article you will learn  about Web Sockets, what they do and how they fit into your application architecture and common uses for Web Sockets in web application development.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are Web Sockets?
&lt;/h2&gt;

&lt;p&gt;Web Sockets represent a communication protocol that creates a persistent connection between a client (typically a web browser) and a server. Unlike traditional HTTP, which follows a request-response pattern, Web Sockets allow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Two-way communication&lt;/strong&gt;: Both server and client can send messages at any time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persistent connection&lt;/strong&gt;: One connection stays open instead of creating new ones for each interaction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Low latency&lt;/strong&gt;: Messages transmit immediately without the overhead of establishing new connections&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before Web Sockets, developers used techniques like HTTP polling, where browsers would repeatedly ask servers "Do you have anything new for me?". This is inefficient and causes delays. Web Sockets solved this problem by creating a direct communication channel that stays open enabling servers to send data directly to clients as soon as it is available which enables powerful real-time bi-directional communication.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Web Sockets Work?
&lt;/h2&gt;

&lt;p&gt;WebSocket connections are an instance of an upgraded standard HTTP connection over TCP.  When connecting to a web server with the “wss” protocol - the server attempts to upgrade the connection from a standard HTTP web connection to a persistent websocket one.&lt;/p&gt;

&lt;p&gt;This process happens in two main phases:&lt;/p&gt;

&lt;h3&gt;
  
  
  The Handshake
&lt;/h3&gt;

&lt;p&gt;When a client wants to establish a WebSocket connection:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Initial Request&lt;/strong&gt;: The client sends an HTTP request that says "I'd like to upgrade this connection to Web Sockets" and ”I support these sub-protocols”.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server Verification&lt;/strong&gt;: The server responds with a special code confirming it accepts the WebSocket connection and also confirms the sub-protocol it prefers the client to use.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connection Established&lt;/strong&gt;: Once the handshake completes successfully, the HTTP connection "upgrades" to a WebSocket connection.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This handshake is designed to be compatible with HTTP infrastructure, so Web Sockets can work on standard web ports (80 and 443).&lt;/p&gt;

&lt;h3&gt;
  
  
  Data Transfer
&lt;/h3&gt;

&lt;p&gt;After the connection is established:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Data is sent in units called "frames".&lt;/li&gt;
&lt;li&gt;Messages can be text (UTF-8) or binary data.&lt;/li&gt;
&lt;li&gt;Either side can send messages at any time and messages must conform to the agreed sub-protocol.&lt;/li&gt;
&lt;li&gt;For security, all client-to-server messages are encrypted over the TSL channel created when connecting.&lt;/li&gt;
&lt;li&gt;The connection stays open until either side decides to close it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is important to understand that with a Web Socket connection both the client and server are able to send messages to the other at anytime.  Any data written to the Web Socket will be transported to the other party.  This is where sub-protocols become important and are a critical component of the design process for an application leveraging this technology.  The way clients and servers communicate over Web Sockets is defined by the sub-protocol.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are SubProtocols?
&lt;/h2&gt;

&lt;p&gt;Subprotocols are agreed-upon conventions for how messages should be formatted and interpreted. Think of them as languages that both client and server agree to speak.  Sub-protocols are important for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Standardization&lt;/strong&gt;: Provide consistent ways to format messages (like JSON, XML, or custom formats)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Messaging Syntax&lt;/strong&gt;: provides the order and meaning of messages and defines any request/response style interactions and how these are tracked.  If you are using web-sockets to transfer request/response interactions these need to be carefully thought out and designed so the client and server can correlate related messages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Versioning&lt;/strong&gt;: Allows protocols to evolve while maintaining backward compatibility&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interoperability&lt;/strong&gt;: Different implementations can work together if they follow the same subprotocol&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;During the handshake, the client can say "I can speak these sub-protocols" and the server responds with "Let's use this one." For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A chat application might use a chat-specific sub-protocol.&lt;/li&gt;
&lt;li&gt;A financial application might use a different sub-protocol optimized for market data.&lt;/li&gt;
&lt;li&gt;A game might use yet another sub-protocol designed for real-time player movements.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By eliminating the constant request-response cycle and providing a direct communication channel, Web Sockets make interactive web applications more responsive and efficient, especially when real-time updates are essential.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where is the Pub-Sub?
&lt;/h2&gt;

&lt;p&gt;The Web Sockets layer itself doesn’t provide any capability or functionality other than handling the client connections and transferring data between the client and server applications that sit at either end of the data pipe connection (Web Socket).&lt;/p&gt;

&lt;p&gt;The connections starts as a normal HTTP connection with DNS Lookup and TLS handshake for a secure connection.  Once the HTTP connection is complete the client will send a standard HTTP get asking to upgrade the connection to a websocket.  It is at this point the client will include a list of sub-protocols it understands.  The server provides the client sub-protocols and asks the backend server to authorize the connection.  The backend server will respond with the sub-protocol is prefers to use from those provided by the client and whether the connection is authorized to continue.&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%2Fddekatubn25td5ashry7.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%2Fddekatubn25td5ashry7.png" alt="Web Socket Sequence diagram showing client web socket server and backend application as actors" width="800" height="558"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is at this point that the connection is now established and either the client or server are able to send data to each other using the selected sub-protocol.  The client is able to send a message conforming to the sub-protocol syntax to the server for processing.  Periodically the Web Socket server may send keep-alive messages which are documented as part of the Web Socket protocol - these "Ping"-"Pong" messages are a way to ensure the connection is healthy. &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%2Fw9gizku9u6rd3f09t7a1.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%2Fw9gizku9u6rd3f09t7a1.png" alt="Web Socket Sequence diagram showing client web socket server and backend application as actors" width="800" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Web Sockets, being bi-directional, enables the server to also send notifications to the client.  These can occur at anytime and the client decides how to handle these.  The sub-protocol determines the structure of data to be sent between the client and server and it also dictates how each actor will respond to messages being sent.&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%2Fp80qucd7ro2u7kw7m4b5.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%2Fp80qucd7ro2u7kw7m4b5.png" alt="Web Socket Sequence diagram showing client web socket server and backend application as actors" width="800" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When the client and server communications are complete, either may request to close down the connection and disconnect.&lt;/p&gt;

&lt;p&gt;Web Sockets do not provide any functionality other than transporting data between client and server applications, it is the backend server implementation which provides the Publish and Subscribe capability.  For this reason it is essential to think about the sub-protocol your applications will use when communicating over web-sockets, without this you will have applications sending garbage to one another and not achieving anything useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Considerations for Architects and Developers
&lt;/h2&gt;

&lt;p&gt;Web Sockets provide the core communications capability for realtime data transfer, they handle the establishment of the connection and transfer of data.  From a client-server application perspective there are a number of challenges that Architects and Developers need to be aware of when designing and building solutions around Web Sockets.&lt;/p&gt;

&lt;h3&gt;
  
  
  Connection Management
&lt;/h3&gt;

&lt;p&gt;Unlike stateless HTTP, WebSocket connections create long-lived connections that must be actively managed.  Networks are often unreliable with interruptions causing connections to drop.  When you build with Web Sockets both the client and server developers must be mindful of the connections and their state - the management required is dependent on the actual features and function of the application sub-protocol being implemented.  You will need to consider the following for managing connections:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Implementing reconnection logic with exponential backoff on both client and server&lt;/li&gt;
&lt;li&gt;Design for graceful handling of unexpected disconnections.&lt;/li&gt;
&lt;li&gt;Consider connection monitoring with Ping/Pong frames to detect "zombie" connections.&lt;/li&gt;
&lt;li&gt;Ensure both client and server can detect "half-open" connections where TCP remains connected but the application level communication has failed.&lt;/li&gt;
&lt;li&gt;Handle abrupt disconnect from clients which can occur through application crashes or browser tab closure or refresh scenarios to ensure you have clean disconnection logic.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Message Ordering and Delivery Guarantees
&lt;/h3&gt;

&lt;p&gt;Web Sockets guarantee in-order delivery on a single connection but lack mechanisms for handling reconnection scenarios.  When clients are reconnecting you need to consider the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Implement application-level sequence numbers for all messages to detect missed updates.&lt;/li&gt;
&lt;li&gt;Build acknowledgment protocols for critical messages with timeout-based retransmission.&lt;/li&gt;
&lt;li&gt;Design idempotency into message handling to safely process potential duplicates during reconnection.&lt;/li&gt;
&lt;li&gt;Consider queuing unacknowledged messages for retransmission after re-establishing the Web Socket connection.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  State Synchronization after Disruption
&lt;/h3&gt;

&lt;p&gt;Web Sockets do not provide delivery guarantees or a way to rebuild state following a disconnection.  When connections drop, there is no built-in way to determine what state was lost.  You will need to consider designing a mechanism to deal with these scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Design efficient delta synchronization protocols to minimize data transfer after reconnection.&lt;/li&gt;
&lt;li&gt;Consider event sourcing approaches where clients can request specific missed events.&lt;/li&gt;
&lt;li&gt;Build "catchup" mechanisms that determine precisely what updates a reconnecting client needs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Resource Management and Throttling
&lt;/h3&gt;

&lt;p&gt;Web Sockets lack built-in flow control beyond TCP's mechanisms.  This places the burden of resource management and throttling on you as a service provider.  You need to consider the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Implement application-level back pressure signals when receivers can't keep up with message volume&lt;/li&gt;
&lt;li&gt;Design rate-limiting systems that account for both message frequency and payload size&lt;/li&gt;
&lt;li&gt;Consider prioritization schemes for different message types during high-load scenarios&lt;/li&gt;
&lt;li&gt;Monitor per-connection memory usage, especially when handling large message fragments&lt;/li&gt;
&lt;li&gt;Implement circuit-breakers that degrade service gracefully under extreme load&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Frame Size and Message Fragmentation
&lt;/h3&gt;

&lt;p&gt;Large messages create special challenges with Web Sockets.  Web Sockets typically handle a smaller message frame size (e.g. 32kb), which means larger messages are broken up into smaller chunks which need to be reconstituted on the receiving end.  You will need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Design protocols that properly handle multi-frame messages, especially during connection instability&lt;/li&gt;
&lt;li&gt;Implement timeout mechanisms for partially received fragmented messages.&lt;/li&gt;
&lt;li&gt;Consider streaming approaches for large data transfers instead of single large messages.&lt;/li&gt;
&lt;li&gt;Test fragmentation edge cases including control frames interleaved with fragmented messages.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Standardized Keep-Alive and Health Monitoring
&lt;/h3&gt;

&lt;p&gt;Since Web Sockets are a bi-directional communication channel, both client and server are responsible for detecting the connection stability and health.  Your client and server applications will need to consider some or all of the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Establish consistent Ping/Pong usage patterns across your system with clear timing parameters.&lt;/li&gt;
&lt;li&gt;Implement application-level heartbeats separate from WebSocket protocol mechanisms.&lt;/li&gt;
&lt;li&gt;Design escalating health check procedures that can distinguish between different failure modes.&lt;/li&gt;
&lt;li&gt;Consider how firewalls and proxies may affect long-lived connections with timeout policies.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Request-Response Correlation
&lt;/h3&gt;

&lt;p&gt;Web Sockets provide a message stream with no built-in request-response syntax.  These are left to the underlying sub-protocol to design for and consider.  In order to ensure your protocol and messaging design is robust you should consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Implement message correlation IDs to match responses with their originating requests.&lt;/li&gt;
&lt;li&gt;Design timeout and retry mechanisms for requests that don't receive timely responses.&lt;/li&gt;
&lt;li&gt;Consider how concurrent operations will be managed without native request multiplexing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Security Implications
&lt;/h3&gt;

&lt;p&gt;Long-lived connections introduce unique security concerns which must be considered in your design.  Think about some or all of the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Implement periodic re-authentication without disrupting connections - tokens expire and you need to refresh them while keeping the connection alive.&lt;/li&gt;
&lt;li&gt;Consider rate limiting to prevent DoS attacks - both connection attempts and message frequency need monitoring.&lt;/li&gt;
&lt;li&gt;Define clear message validation rules in your sub-protocol - validate all incoming messages before processing them.&lt;/li&gt;
&lt;li&gt;Use secure WebSocket connections (wss://) in production to protect data in transit.&lt;/li&gt;
&lt;li&gt;Verify the Origin header to prevent cross-site WebSocket hijacking attempts.&lt;/li&gt;
&lt;li&gt;Implement timeouts for inactive connections to free server resources.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Testing Strategy
&lt;/h3&gt;

&lt;p&gt;Web Sockets require more complex testing approaches due to the challenges already discussed.  Think about and consider the following testing strategies to validate your solution:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simulate connection failures and recoveries to ensure your designs function as expected, particularly consider the failure scenarios which is where problems and edge cases will occur.&lt;/li&gt;
&lt;li&gt;Test partial message delivery and fragmentation to ensure your application is resilient to network interruptions.  Web Socket data occurs in frames which are smaller in size to the overall message - this adds to the testing complexities.&lt;/li&gt;
&lt;li&gt;Verify correct behavior under high message volumes to ensure your sub-protocol design functions correctly.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Protocol Design
&lt;/h3&gt;

&lt;p&gt;The sub-protocol you design will impact on the functioning and reliability of your application.  When thinking about the sub-protocol design you need to consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Message acknowledgment mechanisms for critical updates.  If you need to guarantee a message delivery - this needs to be a specific design choice in your architecture.&lt;/li&gt;
&lt;li&gt;Define clear message structures with versioning support so that you can support backwards compatibility.&lt;/li&gt;
&lt;li&gt;Consider binary formats (Protocol Buffers, MessagePack) for high-throughput applications.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Designing messaging protocols is not easy, and must be a carefully considered part of your solution design. The sub-protocol should consider many of the elements we have discussed throughout this section, some of them are protocol specific which define the semantics of how the client and server communicate. &lt;/p&gt;

&lt;h2&gt;
  
  
  Remember Web Sockets don't Pub-Sub!
&lt;/h2&gt;

&lt;p&gt;Web Sockets provide the communication channel, the responsibility for building reliable, scalable systems on top of this communication channel requires significant additional design and development effort.  In my opinion success or failure of your application design comes down to the underlying sub-protocol.  When implementing Web Sockets as your underlying communication mechanism make sure that you fully consider the impact of HOW your systems will talk - this will determine whether you succeed or fail because Web Sockets don't do pub-sub, your client and server applications do that - Web Sockets is your data pipe between them and everything else you have to build or buy!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>websockets</category>
      <category>ai</category>
    </item>
    <item>
      <title>When Serverless Goes Wrong!!?!?</title>
      <dc:creator>Michael Walmsley</dc:creator>
      <pubDate>Wed, 26 Apr 2023 14:04:35 +0000</pubDate>
      <link>https://forem.com/walmsles/when-serverless-goes-wrong-614</link>
      <guid>https://forem.com/walmsles/when-serverless-goes-wrong-614</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Recently, I spoke at two meetups on &lt;strong&gt;”Unlocking Serverless Observability”&lt;/strong&gt;, which is an important topic, so I have decided to publish an article about the demo project I put together to help illustrate - When Serverless Goes Wrong.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  A Working Sample
&lt;/h1&gt;

&lt;p&gt;Supporting my recent talks and this article is a working (and non-working) example of an Event Driven data integration Architecture that can be &lt;a href="https://github.com/walmsles/serverless-observability" rel="noopener noreferrer"&gt;found on my GitHub page&lt;/a&gt;.  The README is comprehensive and explains how to install and run the performance tests if you want to see it yourself. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WARNING: Executing a performance test even with AWS Lambda may incur charges on your AWS Account.  Given the generous free tier, this is unlikely, but you have been warned.&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  About the Project
&lt;/h1&gt;

&lt;p&gt;Alice works for Widgets Incorporated, a popular online retailer of technology widgets.  Alice keeps the lights on for the e-commerce website running on a large EC2 Instance on the AWS cloud.  Alice’s most important job is ensuring the weekly sales export runs for the sales and marketing team.&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%2Ft1fja056xrlqhxar2nkh.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%2Ft1fja056xrlqhxar2nkh.png" alt="Meet Alice" width="400" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One day, Max from sales and marketing seeks out Alice and demands something to be done about making sales data available more than just weekly!  In the same conversation, he also shares that they are changing delivery providers and they will need to integrate into Ace Courier’s scheduling API to coordinate deliveries.&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%2F73e8pkfj3px8pkc84j5b.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%2F73e8pkfj3px8pkc84j5b.png" alt="Meet Max from Marketing who is very angry and gruff" width="400" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Alice is very worried about adding more processing to the existing e-commerce platform.  It is at capacity, and Widgets Inc. doesn’t have a large budget to do much more about it!  Alice digs through documentation wikis and stack overflow, trying to think how this problem can be resolved.  When she was ready to give up, someone replied to her question about stack overflow.  Widgets Inc.’s e-commerce solution has a lesser-known notification webhook feature that can be turned on through configuration.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5hu0syq7j1yk5nnam9fm.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%2F5hu0syq7j1yk5nnam9fm.png" alt="Alice Searches for a Solution" width="400" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Excitedly she turns to &lt;a href="https://serverlessland.com" rel="noopener noreferrer"&gt;Serverless Land&lt;/a&gt; to learn more about Event Driven Architecture so she can build a serverless event hub to handle live order notifications.  With this simple configuration change, she knows she can deliver live sales data for Max in Sales and Marketing and do a live integration with Ace Courier’s scheduling API.&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%2F4za44ghfngy5r3w3oyrj.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%2F4za44ghfngy5r3w3oyrj.png" alt="Event Driven Architectures on ServerlessLand.com" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On Serverless Land, Alice finds a GitHub reference to an Event Driven pattern which will be useful to solve her exact problem.  The &lt;a href="https://serverlessland.com/patterns/apigw-lambda-eventbridge-sam-java" rel="noopener noreferrer"&gt;pattern she found onServerless Land&lt;/a&gt; is for Java, but that is okay because the existing sam template will accelerate the IaC aspect of building out the serverless event hub in Python.  &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%2Fvzz6t9jrmfjc1el02kho.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%2Fvzz6t9jrmfjc1el02kho.png" alt="Event Driven Architectures on ServerlessLand.com" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Alice has been learning about Serverless with Python and has been reading about &lt;a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html" rel="noopener noreferrer"&gt;SAM CLI&lt;/a&gt; and &lt;a href="https://awslabs.github.io/aws-lambda-powertools-python" rel="noopener noreferrer"&gt;AWS Lambda Powertools for Python&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%2F8z2si7btcf7qouolru7g.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%2F8z2si7btcf7qouolru7g.png" alt="Event Driven Architectures on ServerlessLand.com" width="720" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  How Alice Built it?
&lt;/h1&gt;

&lt;p&gt;Alice used AWS Lambda power tools for logging and tracing and simplifying her code.  For the Delivery API Lambda, she used the &lt;a href="https://pypi.org/project/tenacity/" rel="noopener noreferrer"&gt;tenacity library&lt;/a&gt;, which is great for adding retries to any function in Python to ensure the lambda recovered from any failures.&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%2Fby9xh0l0yzyhonn2278d.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%2Fby9xh0l0yzyhonn2278d.png" alt="Sample code for calling Ace Couriers API using tenacity library for retries" width="800" height="385"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Alice used AWS Lambda Powertools for Python which has an excellent structured logger.  She made good use of this and set the &lt;code&gt;service&lt;/code&gt; name to enable logs to be grouped by each service processing data.  Alice leveraged the &lt;code&gt;correlation_id_path&lt;/code&gt; log setting, which enables json-path extraction of attribute values from the AWS Event being processed to automatically populate the &lt;code&gt;correlation_id&lt;/code&gt; so she could trace transactions across the entire distributed stack.  Alice also listened to my &lt;a href="https://dev.to/strands/observability/getting-started"&gt;Getting Started&lt;/a&gt; tips by making sure the logs tell a story and using the &lt;code&gt;START&lt;/code&gt;, &lt;code&gt;COMPLETE&lt;/code&gt; and &lt;code&gt;FAILED&lt;/code&gt; status values as a &lt;code&gt;status&lt;/code&gt; JOSN attribute for key logs to assist in tracing.&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%2Fofcucuplxomzp7kg436i.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%2Fofcucuplxomzp7kg436i.png" alt="Sample code for calling Ace Couriers API using tenacity library for retries" width="800" height="755"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Alice did quite well and completed the build reasonably quickly - she had good logging and retries for API integration failures which are all great considerations for building a resilient event-driven architecture.&lt;/p&gt;

&lt;h1&gt;
  
  
  So What went Wrong?
&lt;/h1&gt;

&lt;p&gt;One of the key things Alice didn’t consider in her build, which is common for many teams I work with, is understanding the transaction Volumes that the architecture will be processing.  This is critical to understand since Serverless is hyper-scalable and will handle almost any traffic you throw at it!  Alice also didn’t consider the potential limits of the external Courier API she was interacting with - understanding the constraint of ALL your downstream systems is just as critical!  If you know your volumes and downstream limits - you will quickly learn up-front at design time whether any scalability boundaries will be problematic.&lt;/p&gt;

&lt;h1&gt;
  
  
  When Serverless Goes Wrong
&lt;/h1&gt;

&lt;p&gt;To demonstrate the problem when Scalability Boundaries clash, I built Alice’s demo architecture and introduced a deliberate Scalability boundary through a &lt;strong&gt;slow-api&lt;/strong&gt; service representing Ace Couriers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Demo Project Constraints
&lt;/h2&gt;

&lt;p&gt;There are 3 components to this project:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;NotificationFunction&lt;/strong&gt; - this takes the payload from the API, injects a correlation_id and forwards it to EventBridge as a notify-order event.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DeliveryFunction&lt;/strong&gt; - This is a simple function that takes the detail of the event, removes the meta-data from the body and sends this to the configured API endpoint. This lambda uses the  &lt;a href="https://pypi.org/project/tenacity/" rel="noopener noreferrer"&gt;tenacity library&lt;/a&gt;  for retries in the code with a wait time between 3 and 8 seconds on any failure from calling the “slow api”.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SlowAPI&lt;/strong&gt; - This is an Api that has been setup to mimic an unstable real-world API and has been setup with the following constraints:

&lt;ul&gt;
&lt;li&gt;20% of the API Calls will fail immediately.&lt;/li&gt;
&lt;li&gt;The API will take between 0 and 2 seconds to return a response when it runs successfully.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;strong&gt;SlowApi&lt;/strong&gt; lambda is triggered by an API Gateway route with an API Key and usage plan set to restrict the number of transactions through it to 10 per second: BurstLimit = 10, RateLimit = 10. The &lt;strong&gt;NotificationHandler&lt;/strong&gt; is also triggered by an API Gateway with no API Key and no rate limiting.&lt;/p&gt;

&lt;p&gt;There is a configured artillery test file that can be used to run a performance test with the following characteristics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For 1 minute test will hit the Notification API at approx. 2 transactions per second&lt;/li&gt;
&lt;li&gt;For the next 2 minutes, transactions will ramp up to 5 transactions per second&lt;/li&gt;
&lt;li&gt;For the next 10 minutes, transactions will ramp from 4 per second to 15 per second and then remain at this level which is a level that exceeds the SlowAPI transaction limit.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are 2 branches on the demo project - &lt;code&gt;no-flow-control&lt;/code&gt; is what Alice built.  &lt;code&gt;flow-control&lt;/code&gt; is the same architecture but with flow control introduced to ensure the event integration flow is controlled.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Happened During the Performance Test?
&lt;/h2&gt;

&lt;p&gt;The Slow API worked quite well - it had errors, but these were planned errors, approximately 20% of all requests.  So the graph below is a fairly solid representation of a 20% error rate with the green line wavering around the 80% success mark as planned.  Nice to see part of the system worked!&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%2Fy2w9r2j119hxxnplebac.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%2Fy2w9r2j119hxxnplebac.png" alt="Metric graph for the slow-api handler" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see from the &lt;strong&gt;Delivery Lambda Success&lt;/strong&gt; dashboard graph below, the Delivery Lambda was fine for the first seven (7) minutes before the success rate started to drop.  What is impressive from a Lambda perspective is that at the 8-minute mark, we have a success rate greater than 90%, whilst we have errors sitting at nearly 50 in that minute.  This speaks to the automatic resilience that is built into the AWS Lambda asynchronous invocation mechanism.  Sadly as time continues, the success rate bottoms out at approximately 20% at the 17-minute mark with a peak error rate of 1535 failed lambda invocations.  This graph picture is something you will see a LOT on the metrics dashboard for your lambda functions when errors are occurring.  The important thing to look at is the bottom number of the range for the % Success (where it bottoms out) since it may bottom out at 97%, which from an overall system health perspective, is still quite healthy.&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%2Fus53by4440pk1qn5wa3m.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%2Fus53by4440pk1qn5wa3m.png" alt="Delivery handler success without flow control" width="800" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you have scalability boundaries clashing, what you will see is a steady growth in your overall Lambda Concurrency metric, as shown below.  This graph peaks at 788, just shy of my account level limit of 1,000 concurrent invocations.  A common reaction as people see this is to urgently reach out to your AWS Technical Account Manager and very quickly increase the account concurrency so that you have more headroom.  Unfortunately, when you have this reaction, you only create a larger pool of concurrency for the out-of-control lambda function to draw from, which will make the graph peak much larger and effectively make your bill slightly larger.&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%2Fivc0cl0g7chfgqxs3bv9.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%2Fivc0cl0g7chfgqxs3bv9.png" alt="Account Concurrency metric graph showing steady growth over time" width="800" height="411"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you have no flow control built into your system, there is nothing much you can do at this point but ride out the storm or deactivate lambda function triggers to stop your system from creating bigger problems for the downstream systems.  This is the key architectural component missing for this event-driven architecture - &lt;strong&gt;Flow Control&lt;/strong&gt;.  The overall invocation count metric graph tells a very similar story, as expected.&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%2F2ujy98c2uozx1ggeay6k.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%2F2ujy98c2uozx1ggeay6k.png" alt="Invocation count metric graph" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Did this happen?
&lt;/h2&gt;

&lt;p&gt;The scale madness happens because we have a Scalability Boundary between the Delivery API Lambda and the Ace Courier System.  Anytime we have a hyper-scalable system talking to a less scalable system or a system of unknown scale, this is a Scalability Boundary. This is where scale differences occur and cause problems.  As Serverless Architects, we need to be able to identify these boundaries and design our architectures to ensure our system remains resilient and working at all times.  This boundary can also be considered a domain boundary. We must know all interactions beyond our bounded context and manage failure carefully to avoid losing data due to a &lt;strong&gt;Retry Storm&lt;/strong&gt;, which is what causes the graph peaks and high concurrency load.&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%2Fpplpnd86dlm17211t1kv.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%2Fpplpnd86dlm17211t1kv.png" alt="image showing uncontrolled scale in the architecture" width="800" height="243"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to fix
&lt;/h2&gt;

&lt;p&gt;To resolve scalability boundary clashes, we need to add a flow control buffer that can provide a retry mechanism on failure.  The best example is using an SQS queue as the buffer element coupled with maximum concurrency configuration on the Lambda trigger’s Event Source Mapping.  This combination is the most powerful flow control mechanism available on AWS today.&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%2Fqp9s7oeltybf3clk2anu.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%2Fqp9s7oeltybf3clk2anu.png" alt="Architecture diagram showing flow control" width="800" height="223"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How does Flow Control help?
&lt;/h2&gt;

&lt;p&gt;The slow API in our system with flow control is behaving as expected.  The success rate is wavering around the 80% mark by design.  The error rates are much lower - this is because of the introduction of flow control, which controls the number of concurrent calls into the Slow API endpoint.  As you can see, the Slow API (or Ace Couriers System) is much calmer and not spiralling out of control like before.  A graph like this is not ideal because there are errors, but it’s not showing a system that is completely out of control, so there is time to work to correct the problem.  In this case, the error rate is because we coded it in, but in a real system with transient errors like this we need to look at logging to determine the issue and then deploy a fix.&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%2Fzais5lgpe8d5r39iwur4.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%2Fzais5lgpe8d5r39iwur4.png" alt="Slow API success metric graph" width="800" height="423"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Delivery API Success Graph&lt;/strong&gt; shows the switching of success to error and back again - but notice the lowest value on the Y-axis is around the 98% mark.  So although there are errors, it is not disastrous (yet).&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%2F045y6jeji1n99amvul4p.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%2F045y6jeji1n99amvul4p.png" alt="Delivery API Success metric graph" width="800" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With flow control in place, the concurrent lambda execution is way different - it is a very steady flat line which is what we expect to see in a healthy system.  It is hovering around a total concurrency of 20 across the entire system, which is very healthy!&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%2Fbvr9kr7xam6py6oihvvq.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%2Fbvr9kr7xam6py6oihvvq.png" alt="Account Concurreny metric graph" width="800" height="421"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Invocations are also okay, as you can see from the graph.&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%2Fjdfxtc4l92ydcthu6qhn.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%2Fjdfxtc4l92ydcthu6qhn.png" alt="Account Invocation metric graph" width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In these last 2 graphs, you can see 2 anomalous spikes.  Something weird happened with the metrics for the Notification function. At 13:55, cloud watch stopped recording metrics for this function and instead recorded all counts in 2 batches at 14:12 and again at 14:28.  Outside of these 2 anomalies, you can notice the steady, even pace at which the lambda invocations happen through the flow control mechanism provided by the SQS Queue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why is Structured Logging so Important
&lt;/h2&gt;

&lt;p&gt;One of the great things Alice did well was to use AWS Lambda Powertools for the Logging utility, which emitted structured logs in JSON structure.  Part of the logging utility enables capturing of the transaction &lt;strong&gt;correlation_id&lt;/strong&gt; along with function details from the lambda context and a defined &lt;strong&gt;service&lt;/strong&gt; name, like the entry shown below.&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%2F2an7ld43qqwvhhzu5xwv.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%2F2an7ld43qqwvhhzu5xwv.png" alt="Structured logging sample log message" width="800" height="903"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The additional fields enable Alice to search for everything that happened to a single transaction to &lt;strong&gt;Tell the Story&lt;/strong&gt; of what happened.  An example output of a correlation_id story is shown below:&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%2F0upz3b8kebnxklectuxi.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%2F0upz3b8kebnxklectuxi.png" alt="Log query showing a transaction story" width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A feature I get all my teams to do is to emit a &lt;strong&gt;START&lt;/strong&gt;, &lt;strong&gt;COMPLETED&lt;/strong&gt;, and &lt;strong&gt;FAILED&lt;/strong&gt; log message as a status.  This is powerful to query logs over a timeframe to determine whether everything was processed correctly or not.  Often this message is built into the Lambda handler framework itself through middleware components so that developers don’t have to do anything to get this happening.  In this way we can compare the counts of &lt;strong&gt;START&lt;/strong&gt; messages in the &lt;strong&gt;notify-handler&lt;/strong&gt; service to the &lt;strong&gt;COMPLETED&lt;/strong&gt; status messages in the &lt;strong&gt;delivery-handler&lt;/strong&gt; service.&lt;/p&gt;

&lt;p&gt;As you can see, for the No Flow Control test - the actual success rate was terrible. &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%2Fgc6ubv1852chvi5kfvqb.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%2Fgc6ubv1852chvi5kfvqb.png" alt="Actual success rate without flow control" width="800" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Where the &lt;strong&gt;Flow Control&lt;/strong&gt; test had a successful outcome.&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%2F5v37v0jytgel4bcxsuay.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%2F5v37v0jytgel4bcxsuay.png" alt="Actual success rate with flow control" width="800" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;As you can see from this experiment, adding Flow Control between external communication points within each bounded context of your distributed system makes for a more successful event processing flow.  The other points that come to mind that are critical for building Event-Driven Architecture for success are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Understand your expected Volumes up-front and model them.  Understand what your real Transaction rate per second (TPS) is.&lt;/li&gt;
&lt;li&gt;identify the rate limits and constraints of systems you interface to.  It’s important to understand where the scale clashes will occur.&lt;/li&gt;
&lt;li&gt;Add flow control to your Bounded Domains to assist with scalability boundary clashes.&lt;/li&gt;
&lt;li&gt;Use structured logging to enable forensic analysis when things go wrong.&lt;/li&gt;
&lt;li&gt;Emit some simple messages to highlight the beginning of lambda execution and another to highlight whether it was successful.  Use these to create metrics for comparison and to measure the success of your system.&lt;/li&gt;
&lt;li&gt;Think about Business KPI metrics - I keep saying this but do think about them - they are also important, and the absence of a business metric can indicate something not working correctly. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Observability is important, and it’s not that hard to get it right.  With some planning and the useful techniques highlighted here, you can quickly measure your serverless system’s success!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Introducing Serverless DNA - A New Site dedicated to Serverless!</title>
      <dc:creator>Michael Walmsley</dc:creator>
      <pubDate>Sun, 29 Jan 2023 01:13:29 +0000</pubDate>
      <link>https://forem.com/aws-builders/introducing-serverless-dna-a-new-site-dedicated-to-serverless-38mi</link>
      <guid>https://forem.com/aws-builders/introducing-serverless-dna-a-new-site-dedicated-to-serverless-38mi</guid>
      <description>&lt;p&gt;One thing I have learned over the past year while writing blog articles on Serverless to help people learn is that it is not straightforward, and the skills you need are more than just writing software or deploying infrastructure.  You need to understand a broad range of topics to design and build solutions with Serverless successfully.  This makes blog articles that go deep really long and difficult to read and understand because there is so much to cover!&lt;/p&gt;

&lt;p&gt;To be successful with Serverless, you need to have some understanding of the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Infrastructure as Code&lt;/li&gt;
&lt;li&gt;Security&lt;/li&gt;
&lt;li&gt;Event Driven Architectures&lt;/li&gt;
&lt;li&gt;Available Managed Services&lt;/li&gt;
&lt;li&gt;Code Frameworks&lt;/li&gt;
&lt;li&gt;Software Engineering&lt;/li&gt;
&lt;li&gt;and the List goes on ... !&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I have created a new blog site for all my future writing based around smaller, focused articles that are interlinked and weave together to create a complete picture of each topic.  The articles are organised into focused groups and will be linked so people can browse through each topic and learn information in smaller doses which will be easier to read, understand and share.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Serverless DNA?
&lt;/h2&gt;

&lt;p&gt;The "DNA" stands for "Digital Native Architecture", which is how I see Serverless  fitting into the broad world of cloud technology. &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%2F9517se1x8dedce5fni4q.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%2F9517se1x8dedce5fni4q.png" alt="Serverless Digital Native Architecture (DNA) Logo" width="800" height="267"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Serverless DNA takes the form of Small Blog articles, "Mini-blogs" to create knowledge about a specific Strand of Serverless Technology.  Each Strand of knowledge may be linked to one or more other Strands which are related and should also be looked at to help expand and grow your knowledge of Serverless.&lt;/p&gt;

&lt;p&gt;This first release on serverlessdna.com is the MVP of the DNA concept for Serverless knowledge - I want to grow this into more than just my blog site but also enable it as a community resource linking in content from around the globe from other contributors to help categories, link and collate the body of Serverless knowledge out there today.  Find out more by browsing the &lt;a href="https://serverlessdna.com/strands/about" rel="noopener noreferrer"&gt;About Strand&lt;/a&gt; - which is where I will be sharing the public roadmap for future development.&lt;/p&gt;

&lt;p&gt;I hope you find this new site useful and I really look forward to your thoughts and comments on this new beginning for my writing in 2023.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>aws</category>
      <category>architecture</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Being an AWS Community Builder in 2022</title>
      <dc:creator>Michael Walmsley</dc:creator>
      <pubDate>Tue, 20 Dec 2022 22:17:54 +0000</pubDate>
      <link>https://forem.com/aws-builders/aws-community-builder-5082</link>
      <guid>https://forem.com/aws-builders/aws-community-builder-5082</guid>
      <description>&lt;h2&gt;
  
  
  What surprises you most about the AWS Community builders program?
&lt;/h2&gt;

&lt;p&gt;I am always amazed at how "ON" this community is.  There is always something happening and always someone online somewhere to provide you with unconditional support in answering questions.  Everybody here is so caring and willing to share and really just wants to help you to be better at building!&lt;/p&gt;

&lt;p&gt;It's a fabulous AWS Community and the giving feeling here is completely infectious and addictive!&lt;/p&gt;

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

&lt;p&gt;I have been working as an application developer and architect for a while now - I keep telling people I witnessed the birth of the internet as we know it today when the Mosaic browser popularised the World Wide Web (WWW).  Back then, you spent time building your own software frameworks as there was nothing around and open source was only just starting - so when I look back my entire career has been about searching for that perfect software architecture, and working towards providing mechanisms for delivering the perfect digital experience.&lt;/p&gt;

&lt;p&gt;Rolling forward a lot of years I started working with AWS in 2015 working for smaller digital agencies.  I worked to build out solutions using an ecosystem of docker containers which were deployed on EC2 instances.  At this time API gateway was released and I started exploring this "Serverless" thing that had started, and really quickly I started to get hooked!&lt;/p&gt;

&lt;p&gt;I really got to know AWS Serverless deeply around 2018 where I had the opportunity to re-develop and improve the scoring backend for the 2019 Australian Open Digital Experience on &lt;a href="https://ausopen.com" rel="noopener noreferrer"&gt;ausopen.com&lt;/a&gt;.  I designed and built a series of de-coupled, event driven microservices for delivering live scoring updates, stats, and schedules to the mobile and web front-ends - it was a wild ride but what you could get done in a short amount of time was astounding - it was the most immersive 12 weeks of my career and I see this as a real turning point to going all-in on cloud and Serverless.  &lt;/p&gt;

&lt;p&gt;My current claim to fame - "Ever watched the scores on the AusOpen app change?  I did that, that was me, and the tennis ball that indicates who is serving - I wrote that code - I make it move".&lt;/p&gt;

&lt;p&gt;In the past 2 years I have designed and built a number of Event Driven platforms using Serverless and each time I have built a new team and trained them in the Ways of Serverless and continually work to develop and accelerate serverless teams.&lt;/p&gt;

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

&lt;p&gt;For me its the collaboration and community connections the AWS Community Builder program provides coupled with Webinars from AWS service teams and technical experts.  Every webinar is a growth opportunity, being in this program is a growth opportunity - The amount of knowledge on AWS in one place is astounding and its there for immediate consumption - You just need to be sure you get some sleep and look after yourself!&lt;/p&gt;

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

&lt;p&gt;I always joke about loving swag and I think it's true - I LOVE swag!  Jason Dunn and the team do a fabulous job of creating Swag items that are actually useful!  I love and use all the swag I have been gifted so far.&lt;/p&gt;

&lt;p&gt;Personally I have been eyeing off the "Jacket of Epic Builder Endurance" for a while now (Just saying!).&lt;/p&gt;

&lt;h2&gt;
  
  
  What are you eating for dinner today?
&lt;/h2&gt;

&lt;p&gt;Tonight its &lt;a href="https://www.food.com/recipe/jamie-olivers-chicken-cacciatore-441611" rel="noopener noreferrer"&gt;Chicken Cacciatore&lt;/a&gt; - click the link for Jamie Oliver's recipe - I don't really have a recipe to be honest. For me its a whatever I happen to have kind of recipe, but Jamie's is pretty close! &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%2Fgo3a9ifzirwfox80hfju.jpeg" 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%2Fgo3a9ifzirwfox80hfju.jpeg" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Is there anything else you would like to share about the AWS Community Builder program in 2022?
&lt;/h2&gt;

&lt;p&gt;I am incredibly thankful for being accepted into this community and am amazed at how caring the AWS Community leads are and the lengths they have gone to in helping us learn and be continually engaged.&lt;/p&gt;

&lt;p&gt;I can also honestly say that my journey to re:Invent in 2022 was an incredible experience amplified by being a part of this community.  Being looked after by our incredible AWS Community hosts who went out of their way to ensure we engaged with the service teams and had everything we needed, making re:Invent super special, THANKYOU!&lt;/p&gt;

</description>
      <category>cbchristmas2022</category>
      <category>aws</category>
    </item>
    <item>
      <title>Serverless Testing, Part 2: SOLID Architecture for Lambda</title>
      <dc:creator>Michael Walmsley</dc:creator>
      <pubDate>Sun, 10 Apr 2022 08:45:41 +0000</pubDate>
      <link>https://forem.com/aws-builders/serverless-testing-part-2-solid-architecture-for-lambda-4ohk</link>
      <guid>https://forem.com/aws-builders/serverless-testing-part-2-solid-architecture-for-lambda-4ohk</guid>
      <description>&lt;p&gt;&lt;strong&gt;&lt;a href="https://blog.walmsles.io/serverless-testing-part-1-what-i-forgot-at-the-beginning" rel="noopener noreferrer"&gt;Serverless Testing, Part 1: What I forgot at the beginning&lt;/a&gt;&lt;/strong&gt; describes the start of my serverless story where I forget about plain old Software Engineering Principles, and I re-introduced the Dependency Inversion Principle, which is essential when writing software!  I want to explore software architecture for serverless functions in this second instalment.  You will learn about SOLID principles and we will break down Hexagonal Architectures and how these ideas work together to simplify building Serverless code.&lt;/p&gt;

&lt;p&gt;In my experience, many teams who are moving into Serverless development are from a heavy infrastructure background and are less experienced in application development using software architecture principles.  So I like to start by going back to basics and talking about SOLID principles of building software.&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%2F129m9rqtm70eg6ok8mq7.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%2F129m9rqtm70eg6ok8mq7.png" alt="solid_principles.png" width="800" height="497"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Single Responsibility Principle
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;A function should have one reason to change&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Functions should have a single responsibility which is the only reason it should change.  The more responsibilities your code has, the more reasons you will have to change it, and the more complex your functions will become over time.  Keeping functions simple has several key advantages - it lowers code maintenance costs and makes them faster to execute.  However, keeping functions simple is not as easy as it sounds and is part of the distributed design that causes friction in some teams who feel that serverless systems are complex to build or have too many moving parts.  This aspect of serverless is the hardest to deal with and needs a disciplined approach to keeping your solutions simple.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open / Closed Principle
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This software principle means that classes should be open for extension but closed for modification meaning the inner working of a class or function should not need changing when a dependent object or class changes.   You can achieve this through interfaces that extend your code's inner working by introducing a new implementation.  Using interfaces in this way enhances the loose coupling of dependencies.&lt;/p&gt;

&lt;p&gt;I also like to apply this to serverless functions when you need to write code.  I always try and make my functions data-driven and configurable so that the processing relies on only the event triggering the function.  This principle applied to serverless is about not changing your original service to deal with specific behaviour in a specialised context.  Instead, look to make these exceptional cases an option or feature flag and encourage composability to add additional processing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Liskov's Substitution Principle
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Overridden class methods must have the same input parameters as their superclass.  So, if it looks like a duck and behaves like a duck, it is a duck, and if you want a new duck, make sure it quacks the same as all the others.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Liskov's principle states that if you use a subclass in place of its parent class anywhere in your software, nothing will break.   In simple terms, parent classes define a contract that sub-classes should never alter - violating this principle means clients who try to use this sub-class will no longer work as expected without being modified.&lt;/p&gt;

&lt;p&gt;In serverless, a new version of your service should always be able to replace a previous version without breaking anything.  This also puts our design focus on service consumers or customers who do not need to change their code when a new version is deployed, which would be considered a breaking change for the Interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Interface Segregation Principle
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;No code should be forced to depend on methods it does not use&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The Interface Segregation principle keeps your defined interfaces simple and specific to their purpose.  Keeping interfaces clean is essential; otherwise, every implementation is forced to implement functions rarely used, which is a waste of effort and increases software complexity.&lt;/p&gt;

&lt;p&gt;This principle also helps in serverless designs by ensuring that we design simple, single-purpose interfaces for our services.  A service that does too much will expose too many details and may leak internal implementation details to consumers, which is always a mistake for any microservice architecture.  Keeping interfaces simple and to the point is what this principle is about, and we should be strong in applying it to every service boundary we design.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dependency Inversion Principle
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt; High-level modules should not depend on low-level modules.  Both should depend on abstractions.&lt;/li&gt;
&lt;li&gt; Abstractions should not depend on details.  Details should depend on abstractions.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;Dependency Inversion is all about creating functional abstractions between different layers of our code.  This principle enforces the use of interfaces for every dependency, enabling the simple replacement of any component in our code quickly and easily.  To embrace this principle means when designing an interaction between a high-level module and a lower level, one should be created with the interactions in mind, which will dictate the overall interface design.  In this way, the coupling of our software components is reduced, and we can introduce new implementations at any time, which is ideal when we have embraced all the SOLID principles explained here.&lt;/p&gt;

&lt;p&gt;The serverless use-case for this principle is really in building out strong software domain abstractions around cloud services to isolate business logic from the complexity of the cloud services it uses.  This was the main point I introduced in Part 1 of this series. &lt;/p&gt;

&lt;h2&gt;
  
  
  Hexagonal Software Architecture
&lt;/h2&gt;

&lt;p&gt;Hexagonal Architecture is all the rage in the Serverless world and has been written about by many people:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://netflixtechblog.com/ready-for-changes-with-hexagonal-architecture-b315ec967749" rel="noopener noreferrer"&gt;Ready for changes with Hexagonal Architecture&lt;/a&gt; on the Netflix TechBlog by Damir Svrtan and Sergii Makagon&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/blogs/compute/developing-evolutionary-architecture-with-aws-lambda/" rel="noopener noreferrer"&gt;Developing evolutionary architecture with AWS Lambda&lt;/a&gt; on the AWS Blog by Luca Mezzalira&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/serverless-transformation/service-ports-finding-a-loosely-coupled-utopia-with-event-driven-serverless-6964aacd1487" rel="noopener noreferrer"&gt;Service Ports: Finding a Loosely Coupled Utopia with Event-Driven Serverless&lt;/a&gt; on Serverless Transformations by Ben Ellerby&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;These articles describe the benefits and tell you why you should be using hexagonal architecture.  They all talk about Ports and Adapters as the magic you need to isolate your business domain logic from the more specific lower-level code that deals with writing data to databases, file systems or API interfaces.  I don't like to use "hexagonal" or "ports". Instead, I prefer to talk about software patterns. &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%2Fa0m1qecvdo0i75aaw71j.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%2Fa0m1qecvdo0i75aaw71j.png" alt="hex_arch.png" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Building your software using this concept is nothing more than embracing the Dependency Inversion Principle from the SOLID principles we have already discussed.  In hexagonal architectures, Ports are nothing more than well-defined Interfaces clearly defining your business domain interactions with external dependencies.  Adapters are specific implementations of these business domain interfaces (Ports) to enable the de-coupling of your business domain logic from the cloud environment you deploy to.  Adding dependency injection into the mix at this point is the real magic;  Enabling injection of Adapters into your business domain logic allows a lot of freedom in building and testing your code.&lt;/p&gt;

&lt;p&gt;I strongly feel there is a lot of effort focused on developers mocking SDK elements of AWS and other cloud providers.  A lot of time is wasted trying to work out how to mock SDK responses from SDK API calls.  By adopting business domain Interfaces and implementing them with Adapters, you can instantly create mocks using a mock implementation of the Interface for local testing purposes.  This means your business domain developers do not need to know about the AWS SDK dependencies.  This can be locked away within the Adapter implementations, which can be handled by framework developers who can be specialists for your cloud environment.  Combining Adapters with simple dependency injection allows your adapter dependencies to be injected, meaning business domain code does not need to change whether you are unit testing, integration testing, or deploying to the cloud.  You will have adapters for each of these environments and can drive the injection of these dependencies when needed so all your business domain logic can be quickly and simply unit tested.&lt;/p&gt;

&lt;p&gt;A huge benefit of adopting a Hexagonal architecture is to isolate your business logic from your cloud and serverless components that make up your application.  This means you can now have some separation of responsibilities within your team - Experts on your cloud integration and experts on your business domain logic.  With this isolation, you can have less cloud experienced programmers focus on business logic and more deeply cloud experienced developers on Cloud integration - this separation of concerns is powerful.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Road to Hexagonal Applications - Service Oriented Architecture (SOA)
&lt;/h3&gt;

&lt;p&gt;Clean hexagonal architecture comes down to software engineering principles we have discussed throughout this article, and I will throw in one more term - Service Oriented Architecture (SOA).  Wrapping our business domain logic into Service classes creates a complete separation between your cloud environment and application logic; it also becomes a natural break between cloud expert developers and business domain developers - something I have been searching for to create layered teams with specific skill-sets across cloud and the business domain.&lt;/p&gt;

&lt;p&gt;Using Services as the core base also allows direct and straightforward dependency injection of the various dependent interface adapters (Ports) required for the service to function.  The following code snippet shows an example of a base abstract style python class for a Service Interface (Port).  It accepts several other adapters for core functionality required, including Logging, Notifications and Storage.  Through dependency injection for domain adapters - the business domain logic is now wholly isolated via the Dependency Inversion Principle I mentioned earlier.&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%2F7kyfyopikvd1b49we17m.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%2F7kyfyopikvd1b49we17m.png" alt="python-service-sample.png" width="800" height="799"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding Business Functional Adapters (Ports)
&lt;/h3&gt;

&lt;p&gt;In the Service example, several adapters are injected as dependencies.  The following code sample shows how you can structure the notification adapter (Port), which has a business-focused method &lt;strong&gt;notify_new_user&lt;/strong&gt; that is functional.  In this way, we separate the detail of the notification implementation from our domain programmers.  This also means we can pivot the actual implementation without changing business domain code - so from a SOLID perspective, we are now embracing the Open-Closed Principle and the Dependency Inversion Principle - two powerful software engineering techniques.&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%2Fzk0a5ki8i2kot9h36cg8.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%2Fzk0a5ki8i2kot9h36cg8.png" alt="python-notify-adapter-sample.png" width="800" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Bringing it all together
&lt;/h3&gt;

&lt;p&gt;The following example shows how an AWS Lambda handler would apply the techniques discussed in this article.  An adapter class (UserEventMapper) translates the Lambda event into a User model, which can be passed into the Service instance to process the &lt;strong&gt;new_user&lt;/strong&gt;.  The Service response is also mapped by an adapter class (NewUserResponse) to enable an actual AWS Lambda Service response based on the service used to trigger this function.  This creates a very clear Lambda function that anyone can look at and understand what is happening.  The adapter classes used can have their implementations changed to alter how this function is being triggered—all without making changes to the business domain logic.&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%2Fo0pbo0p81dn4kmt148jd.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%2Fo0pbo0p81dn4kmt148jd.png" alt="python - lambda-example.png" width="800" height="668"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We have explored the core of software engineering - SOLID principles, Dependency Inversion, Dependency Injection and Service-Oriented Architecture (SOA).  Combining these techniques can assist in simplifying your development team's journey to Serverless by abstracting away the cloud complexity that Serverless developers are traditionally confronted with.  Using these techniques discussed here, we can reduce the cognitive load developers have to contend with in creating serverless solutions.  This all does feel like it goes against the simplicity of serverless, but we must acknowledge that we are mostly building software solutions at the end of the day.  I say mostly as we should be embracing serverless services where possible, but like Jared Short says - "When you write Serverless code, you need to own it", so engineer it properly and take control.  &lt;/p&gt;

&lt;p&gt;Own it is what I say. Own it with SOLID principles, Interfaces and Adapters and Service-Oriented Architecture.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Serverless Testing, Part 1: What I forgot at the beginning</title>
      <dc:creator>Michael Walmsley</dc:creator>
      <pubDate>Mon, 10 Jan 2022 12:07:04 +0000</pubDate>
      <link>https://forem.com/aws-builders/serverless-testing-part-1-what-i-forgot-at-the-beginning-4c08</link>
      <guid>https://forem.com/aws-builders/serverless-testing-part-1-what-i-forgot-at-the-beginning-4c08</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This is the first in a series of articles about Serverless software architecture and testing. I am collating a summary of what I have learned on my journey to Serverless and am starting with this introduction, highlighting my thoughts on what I forgot when I started learning about serverless. This series will be made up of three parts:&lt;/p&gt;

&lt;p&gt;Part 1: What I forgot at the beginning&lt;/p&gt;

&lt;p&gt;Part 2: SOLID Architecture for Lambda&lt;/p&gt;

&lt;p&gt;Part 3: Simplify Testing with SOLID Architecture&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Serverless Testing has been a hot topic in recent times. Many of the articles focus on general approaches, tooling to automate the creation of temporary stacks, and tell us to stop emulating the cloud locally and test in the cloud! In this article, I won't be covering these tools and approaches. Instead, I want to focus on what I forgot on my Serverless journey, and hopefully, it will help you not make the same mistake.&lt;/p&gt;

&lt;p&gt;I started my journey with Serverless over four years ago when Lambda was relatively new, and we were all trying to work out just how this new cloud technology worked, scaled and could be used to create real, working, resilient solutions. There are many things to learn about at first - Lambda, SQS, SNS, DynamoDB, and the list goes on. So many new things that we all get wrapped up in learning how these products work.&lt;/p&gt;

&lt;p&gt;When I started building serverless, I started with simple code; they were just functions, after all. Then, I quickly moved to local emulation of SQS, SNS, etc., to do what we have always been encouraged as software engineers - do end-to-end local testing. With mixed success, I moved back to unit testing with event fixtures to test my core business logic and continued to refine my testing knowledge and skills. There are always complexities involved with testing lambda code;  There are interactions with cloud managed services such as S3, SNS, SQS or DynamoDB, which have AWS SDK clients to manipulate data being stored or processed. Now I spent a lot of time working out how to mock these AWS dependencies in my unit tests and make sure the SDK calls expected were made. Every new service seemed to introduce a new challenge; I spent more time writing mocks and getting unit tests working in this foreign land of AWS dependencies than building solutions and started to feel less productive. I have spent a lot of time the past few months reflecting on my journey since it began and how I have changed my thinking in many areas, particularly around testing serverless solutions. &lt;/p&gt;

&lt;p&gt;I strongly feel I got wrapped up in the details of all the new services I had to deal with and coming to terms with how they all worked. The cognitive load on putting this all together at first is a lot! Combining this additional learning and the understanding that we are just writing functions means we expect to be writing fewer lines and simpler code. I was doing just this when I first started and looking back. I can see what I forgot about! I forgot all about plain old Software Engineering!&lt;/p&gt;

&lt;p&gt;As a professional Software Engineer with a lot of experience, this is quite a confronting revelation. However, looking at conversations within the community over the past year, I feel I am not alone here. So what do I mean when I say I forgot about plain old Software Engineering? Three small words come to mind - Dependency Inversion Principle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dependency Inversion Principle
&lt;/h2&gt;

&lt;p&gt;This core principle of Object-Oriented design sounds scarier and more confusing than it is. The following describes this design principle defined by Robert C. Martin:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;High-level modules should not import anything from low-level modules. Both should depend on abstractions (e.g., interfaces).&lt;/li&gt;
&lt;li&gt;Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Dependency Inversion is all about creating functional abstractions between different layers of our code. In a Serverless function context, this technique allows us to focus on more meaningful, descriptive functions to perform actions for our code rather than getting bogged down in the detail of an AWS SDK client for DynamoDB and its associated API calls. Creating these abstractions also removes the implementation detail and complexity of the cloud service mocking of the past. It allows quick and clean testing of each Interface since we can introduce a test stand-in implementation for any abstraction.&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%2F6mpivxiteeuo7l0dic6m.jpeg" 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%2F6mpivxiteeuo7l0dic6m.jpeg" alt="Image showing Dependency Inversion" width="799" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Class Diagram - Dependency inversion applied to Lambda handler&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So what we need to look at is creating functional abstractions for any call from our core business logic out to the cloud. For example, in the diagram above, you can see the &lt;strong&gt;EventHandler&lt;/strong&gt; depends on the &lt;strong&gt;DomainServiceInterface&lt;/strong&gt; and the &lt;strong&gt;DomainLogicImplementation&lt;/strong&gt;, which implements the DomainServiceInterface, depends on the &lt;strong&gt;StorageInterface&lt;/strong&gt;. This allows the replacement of any green implementation blocks with Testing Stubs enabling all the components to be tested without creating complex mocks and test assertions. This simple software engineering principle is what I forgot about during the start of my Serverless journey. With the state of confusion and commentary around Serverless testing recently, I felt compelled to share my thoughts and what I forgot about when I first started. Hopefully, this will help you find your way quicker in this often-confusing space.&lt;/p&gt;

&lt;p&gt;This article is the first in a new series I am writing on Serverless Software Architecture and Testing. These concepts go hand in hand as good architecture and code organisation naturally lead to better testing. My next article will be about SOLID Architectures for Lambda, taking the Dependency Inversion principle mentioned above further and deeper.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>programming</category>
      <category>testing</category>
    </item>
    <item>
      <title>AWS Lambda Powertools: Idempotency, A Deeper Dive</title>
      <dc:creator>Michael Walmsley</dc:creator>
      <pubDate>Sat, 23 Oct 2021 22:50:48 +0000</pubDate>
      <link>https://forem.com/aws-builders/aws-lambda-powertools-idempotency-a-deeper-dive-32kc</link>
      <guid>https://forem.com/aws-builders/aws-lambda-powertools-idempotency-a-deeper-dive-32kc</guid>
      <description>&lt;p&gt;In an earlier article, I wrote about &lt;a href="https://blog.walmsles.io/making-all-your-apis-idempotent" rel="noopener noreferrer"&gt;Making all your APIs Idempotent&lt;/a&gt;.  I shared why idempotency is essential and introduced the AWS Lambda Powertools for Python, which provides a utility for adding idempotency to your Python built lambdas in a quick and easy to use way.  My article focused on an API as the reason for requiring idempotency and, while accurate, it is not the only use-case for the Powertools Idempotency utility.  In this article, I dive deeper into the Powertools utility and highlight more features and use cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Idempotency Works
&lt;/h2&gt;

&lt;p&gt;The idempotency utility is quite a complex feature wrapped up into a simple package to make it easy and approachable to use.  The idempotent utility follows these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Calculate a unique idempotent token using the request payload and check the idempotent store to see if the transaction has been completed or is currently in flight.&lt;/li&gt;
&lt;li&gt;If completed, the utility will return the stored result from the first execution; If in-flight, the utility will raise an &lt;em&gt;&lt;strong&gt;IdempotencyAlreadyInProgressError&lt;/strong&gt;&lt;/em&gt; exception signalling to the caller it is safe to retry the operation.&lt;/li&gt;
&lt;li&gt;If no transaction exists, the utility will save an in-flight record into the store and call the function to process the transaction.  When the function completes, the utility will keep a copy of the response so that future invocations can receive the same result.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At its core, the idempotent implementation for power tools derives a hash value (md5 by default) for the idempotent token of the transaction. Therefore, calculating this unique token is the most critical part that we need to understand to ensure idempotency works correctly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Calculating the Idempotent Token
&lt;/h2&gt;

&lt;p&gt;AWS Powertools has your back in this department and will take on board sane, consistent defaults that will work in more straightforward use cases by calculating the idempotent token using the entire message body. However, if your use case is not simple, you must understand how your data changes between invocations.  When processing an AWS Lambda event, many of the fields within the event will contain data that changes between each request, for example, "requestId" and "requestTime" in the AWS API gateway Proxy event, which vary for each API invocation.&lt;/p&gt;

&lt;p&gt;For non-default hash keys, you will want to use the &lt;strong&gt;IdempotencyConfig&lt;/strong&gt; object and ensure you set the &lt;strong&gt;event_key_jmespath&lt;/strong&gt; string to allow powertools to extract the unique idempotency token from the Lambda event for the hash key.&lt;br&gt;
Understanding how JMESPath and the &lt;a href="https://awslabs.github.io/aws-lambda-powertools-python/latest/utilities/jmespath_functions/" rel="noopener noreferrer"&gt;Powertools JMESPath&lt;/a&gt; &lt;strong&gt;powertools_json&lt;/strong&gt; functions decode strings to JSON objects is essential for the idempotency utility.&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%2F19lcezqyelxwtk46waeo.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%2F19lcezqyelxwtk46waeo.png" alt="image2.png" width="800" height="129"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Making a JSON REST API Idempotent
&lt;/h2&gt;

&lt;p&gt;An excellent example is using the utility with API gateway proxy events for a JSON REST-based API. Unfortunately, the AWS API Gateway Event's body attribute is a string value that is not ideal when trying to assert the idempotency of your JSON Payload.&lt;br&gt;
By definition, attributes in a JSON Payload for a REST API have no order associated with them, meaning the following examples are considered identical in terms of an API transaction:&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;"transaction_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;"bd784218-31cc-46be-b66a-a9c38b9a2fe5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"user_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;"10000233220"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"me@email.com"&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;is the same as,&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;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"me@email.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"user_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;"10000233220"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"transaction_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;"bd784218-31cc-46be-b66a-a9c38b9a2fe5"&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 Powertools idempotency implementation considers this detail, but only if you ensure the body is decoded into a JSON object using the &lt;strong&gt;powertools_json&lt;/strong&gt; built-in JMESPath function. To achieve this outcome for the above example, you would need to set up the IdempotencyConfig as follows:&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;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;IdempotencyConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_key_jmespath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;powertools_json(body)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the &lt;strong&gt;powertools_json&lt;/strong&gt; built-in function, the data given to the Idempotent Utility's hash generation function will be transformed into a python Dictionary object, allowing the hash generator to convert the data into a sorted JSON serialized string.  In this way, your API will remain idempotent regardless of the attribute ordering in the API JSON body.&lt;/p&gt;

&lt;p&gt;When choosing the parts of your message payload for the unique idempotency key, it is critical to realize that a string value can contain whitespace and newlines, affecting the hash outcome.&lt;br&gt;
I strongly recommend reviewing the &lt;a href="https://jmespath.org/tutorial.html" rel="noopener noreferrer"&gt;JMESPath tutorial page&lt;/a&gt; to understand how the JMESPath utility works; it is a powerful tool in your python arsenal and is essential in ensuring the Powertools idempotency utility works for you correctly.&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%2Focdbnw24xsgrn5tdm9iu.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%2Focdbnw24xsgrn5tdm9iu.png" alt="image1.png" width="800" height="349"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Idempotent Decorators for Everything!
&lt;/h2&gt;

&lt;p&gt;The core of the idempotency utility are the python function decorators (yes, there is more than one!).  A recent addition to AWS Lambda Powertools for Python is the &lt;a href="https://awslabs.github.io/aws-lambda-powertools-python/latest/utilities/idempotency/#idempotent_function-decorator" rel="noopener noreferrer"&gt;idempotent_function&lt;/a&gt; decorator, which provides idempotency to any synchronous Python function. So now you can add idempotency to absolutely anything, not just Lambda handlers!&lt;/p&gt;

&lt;h2&gt;
  
  
  Making any function Idempotent
&lt;/h2&gt;

&lt;p&gt;Using the standard idempotent_function decorator, you can tell Powertools how to calculate the idempotency key using any of the function parameters.  The IdempotencyConfig is also available for customizing how the utility will work utilizing the event_key_jmespath as mentioned above.  &lt;/p&gt;

&lt;p&gt;The following code example showcases applying the idempotent_function decorator to the SQS batch processor function making your SQS queue processing completely safe from processing the same message multiple times.&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%2F8x0l824f5fziu15g4ncj.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%2F8x0l824f5fziu15g4ncj.png" alt="image3.png" width="800" height="258"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Payload Validation
&lt;/h2&gt;

&lt;p&gt;The AWS Builder's Library article on &lt;a href="https://aws.amazon.com/builders-library/making-retries-safe-with-idempotent-APIs/" rel="noopener noreferrer"&gt;Making  retries safe with Idempotent APIs&lt;/a&gt; covers the need for payload validation when data that is not part of the idempotent token changes between requests.  You need to consider these scenarios carefully so you do not confuse your API consumers; Powertools has a &lt;a href="https://awslabs.github.io/aws-lambda-powertools-python/latest/utilities/idempotency/#payload-validation" rel="noopener noreferrer"&gt;payload validation feature&lt;/a&gt; you can leverage to cover this situation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing Your Solution
&lt;/h2&gt;

&lt;p&gt;Once you have designed and implemented idempotency in your solution you will want to test it thoroughly before making it live.  You can test your solution using a local dynamo DB or mocks to save on cloud costs; Powertools covers code testing scenarios completely in their &lt;a href="https://awslabs.github.io/aws-lambda-powertools-python/latest/utilities/idempotency/#testing-your-code" rel="noopener noreferrer"&gt;online documentation&lt;/a&gt;. &lt;/p&gt;

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

&lt;p&gt;My everyday work is building and training Serverless teams and making sure they build reliably, with resilience and taking note of the AWS Well-Architected Principles documented in the Serverless Lens.  I always recommend developing on AWS Lambda with the Python runtime since I can rely on teams meeting the Well-Architected principles through the AWS Lambda Powertools. For groups who insist on Typescript or .NET, I strongly urge them to use Python since it has Powertools, the swiss army knife that AWS Lambda developers need for every project!&lt;/p&gt;

&lt;p&gt;If switching your development to Python for AWS Lambda Powertools is not for you, I recommend heading over to the AWS Powertools Roadmap and supporting the &lt;a href="https://github.com/awslabs/aws-lambda-powertools-roadmap/issues/26" rel="noopener noreferrer"&gt;Typescript&lt;/a&gt; and &lt;a href="https://github.com/awslabs/aws-lambda-powertools-roadmap/issues/17" rel="noopener noreferrer"&gt;.NET&lt;/a&gt; feature requests by adding a 👍.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Correlating Transactions with Zero Code</title>
      <dc:creator>Michael Walmsley</dc:creator>
      <pubDate>Tue, 12 Oct 2021 13:10:34 +0000</pubDate>
      <link>https://forem.com/aws-builders/correlating-transactions-with-zero-code-4n9i</link>
      <guid>https://forem.com/aws-builders/correlating-transactions-with-zero-code-4n9i</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;I take pride in writing detailed, technical articles filled with solid code examples and links to Github so you can review and learn from my serverless journey. Today, I think this will be my shortest ever article, and it lacks all these things since starting on this Zero Code adventure.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm8nxlvx158f1f18moeo4.jpg" 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%2Fm8nxlvx158f1f18moeo4.jpg" alt="End of the road sign" width="450" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are a lot of articles out there on correlating transactions across distributed systems and plenty of technical frameworks and solutions for providing transaction traceability. However, correlating transactions as the data passes through our services is essential to observe our system behaviour. So, as developers, we reach for what we know best our development tools, software frameworks and technical solutions. &lt;/p&gt;

&lt;p&gt;Adding Correlation Ids, tracing codes and segment identifiers takes effort and time.&lt;/p&gt;

&lt;p&gt;Usually, we place these identifiers in HTTP Headers and other unique Attribute locations for Cloud-Native services so we can hide these away and provide them as transparent travellers across our distributed systems. In an AWS Managed service sense, this means we need to know how to pack and unpack these identifiers at each step through our serverless systems - across SQS boundaries, through EventBridge events and via SNS topics. These cloud-native services each have a different mechanism to save, transport, and unpack these keys to observability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But what if there was a different way without all this complexity and code?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I want to share with you my thinking on this and what I have arrived at to resolve this technical problem in a way that transcends the AWS service complexities I just mentioned. Nowadays, I believe in using the language of integration, and for tracing transactions through distributed services, I create an internal integration language encapsulating these observability identifiers.&lt;/p&gt;

&lt;p&gt;Here is an example of a message structure from a system I am working on right now:&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;"correlation_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;"b58e7f98-2b58-11ec-8d3d-0242ac130003"&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_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;"bde284dc-2b58-11ec-8d3d-0242ac130003"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"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;"3965a72e-2b59-11ec-8d3d-0242ac130003"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"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;"7e6c7638-218c-4947-8d84-a20a9cf68acd"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"firstname"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"michael"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"lastname"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"walmsley"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"michael@myemail.com"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;data&lt;/strong&gt; represents the original payload received from the initial caller of the service, usually via an API endpoint.   The API entry point is responsible for validating the data to ensure it is correct before transforming it into the internal message format containing the tracing identifiers (correlation_id, message_id, tracing_id). Using a standard messaging structure like this means we no longer worry about changing how we interact with each of the Managed Services we are using since our correlation data is now directly embedded within our message data. All of our messages now have correlation identifiers that pass through data services like SQS, Eventbridge, SNS, Kafka, Kinesis, etc., without the need for any custom code to embed meta-data or other custom markers for correlating our transactions. &lt;/p&gt;

&lt;p&gt;This simple approach of defining an internal service messaging format allows your transaction data and correlation data to always be visible within every component of your distributed Serverless Architecture. Furthermore, we achieve Transaction observability within our Serverless data processing systems using nothing more than our already prepared Cloudwatch Logging utilities and Zero additional code!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>zerocode</category>
    </item>
    <item>
      <title>Serverless Integration, Zero Code</title>
      <dc:creator>Michael Walmsley</dc:creator>
      <pubDate>Sat, 09 Oct 2021 10:03:40 +0000</pubDate>
      <link>https://forem.com/aws-builders/serverless-integration-zero-code-3i39</link>
      <guid>https://forem.com/aws-builders/serverless-integration-zero-code-3i39</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;The words of Farah Campbell and Ben Kehoe on "The Serverless Mindset" has inspired me to level up my thinking and approach to Serverless integration. &lt;/p&gt;

&lt;p&gt;So let me share my journey to zero code integration with you.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For my first foray into zero code integration, I decided to keep things simple and play with the classic integration pattern of sending data via an API into an SQS queue for downstream processing.  Most serverless websites will have you build an architecture like this, an API gateway triggering a Lambda that pushes to the SQS queue for downstream processing.&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%2Fupguplizspa6ubpucpxi.jpg" 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%2Fupguplizspa6ubpucpxi.jpg" alt="Simple Classic Lambda Integration Architecture (with code)" width="399" height="114"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This pattern is easy to build, deploy and get working with any of the main Serverless frameworks; my weapon of choice is the Serverless Framework from Serverless Inc.  The Serverless Framework focuses on deploying Lambda functions and makes this task quick and easy. For example, to build the Lambda processor pushing to SQS looks something like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;sqs&lt;/span&gt;

&lt;span class="nx"&gt;frameworkVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;aws&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;python3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;
  &lt;span class="nx"&gt;lambdaHashingVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20201221&lt;/span&gt;

&lt;span class="nx"&gt;functions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="nx"&gt;apiPushSQS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api_push_sqs&lt;/span&gt;
    &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;POST&lt;/span&gt;
          &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;
    &lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;THE_QUEUE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;Ref&lt;/span&gt; &lt;span class="nx"&gt;downstreamSQS&lt;/span&gt;

&lt;span class="nx"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="nx"&gt;Resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

    &lt;span class="nx"&gt;downstreamSQS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AWS&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nx"&gt;SQS&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nx"&gt;Queue&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Building out the pattern is simple, and the framework focuses on Lambda functions and their events which makes building and deploying quick and easy.  I managed to do this in minutes and felt productive; the instant win had my endorphins flowing, and I was conquering it!  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But is this the ideal serverless solution?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I am sure Ben Kehoe would say, "If you came along to my talk today to hear about Lambda, you are in the wrong place", and these words are echoing in my head right now as I write this and think how I can do this without code. &lt;/p&gt;

&lt;p&gt;We all know the AWS API Gateway supports direct, native integrations with many services, and I have designed many systems around these patterns. Still, I have never actually built one using the Serverless framework; until now.  &lt;/p&gt;

&lt;p&gt;To achieve direct integrations from API gateway to other Services involves understanding how to configure the Integration request, and there are choices as to how we can go about this.  I started by configuring the API via the console and followed one of the few blog articles I could find on this topic to set it up, which you can look at &lt;a href="https://medium.com/@pranaysankpal/aws-api-gateway-proxy-for-sqs-simple-queue-service-5b08fe18ce50" rel="noopener noreferrer"&gt;here&lt;/a&gt;.  &lt;/p&gt;

&lt;p&gt;This article allowed me to see the steps I needed to complete:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create the SQS Queue&lt;/li&gt;
&lt;li&gt;Create the IAM Policy allowing API Gateway to use the sqs:SendMessage action on my SQS Queue.&lt;/li&gt;
&lt;li&gt;Create the role to attach the policy to and enable API gateway to assume the role.&lt;/li&gt;
&lt;li&gt;Create the API and define a POST method that we can use to map the incoming request through to the SQS queue.&lt;/li&gt;
&lt;li&gt;Integrate the API resource with our SQS Queue and create an integration mapping template to transform the incoming payload for our forwarding request to the SQS service.&lt;/li&gt;
&lt;li&gt;Deploy and Test the API&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Continue with me on my journey to complete these steps and end up with the following architecture, which uses zero-code.&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%2Fjbdv7d8fs489cyb533us.jpg" 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%2Fjbdv7d8fs489cyb533us.jpg" alt="Zero-Code Serverless.yml" width="239" height="114"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To achieve a zero-code solution, we need to learn about the native AWS language of Velocity Templates and understand how data flows through the API Gateway service.  As Ben and Farrah would say - writing code is easy, but being genuinely serverless takes work, and it's not always the work we want to do or is fun to do, but we need to challenge ourselves to leverage the cloud services and write less code!&lt;/p&gt;

&lt;h2&gt;
  
  
  How the API Gateway Data flow works
&lt;/h2&gt;

&lt;p&gt;A quick high-level look at an API gateway request for a v1 REST API 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%2F401fkrejoihzh0dibyvv.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%2F401fkrejoihzh0dibyvv.png" alt="AWS API Gateway Data Flow" width="800" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each client request goes through the following stages for a simple un-authenticated request:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Method Request &lt;/li&gt;
&lt;li&gt;Integration Request - where transformations occur for the integration request to SQS&lt;/li&gt;
&lt;li&gt;Backend Service (Simple Queue Service in this case)&lt;/li&gt;
&lt;li&gt;Integration Response - where transformations occur for the integration response from SQS&lt;/li&gt;
&lt;li&gt;Method Response - Mapping of integration Response to Method response for the client.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now that we know the steps for the API request through the gateway, we can start setting this up using the Serverless framework because that's my current go-to for deploying serverless solutions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create the SQS Queue
&lt;/h2&gt;

&lt;p&gt;Creating the SQS queue is the easiest part of this solution. First, add the following to the resources section of the serverless YAML file.  I prefer not to provide the QueueName when setting up SQS; this allows the serverless framework to name the resources using the usual naming standard of serviceName-stage-resourceName-randomString.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="nx"&gt;sqsQueue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AWS&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nx"&gt;SQS&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nx"&gt;Queue&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create the IAM Policy and Role
&lt;/h2&gt;

&lt;p&gt;To create the IAM Role and Policy, we cannot use the standard IAM methods for the Serverless Framework since these focus on setting up IAM Roles and Policies for functions in your solution.  This time there are no functions or Lambdas at all!&lt;br&gt;
Add the role and policy details into the Serverless YAML file in the resources section.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzc45qsy3wgn8pm7qweqh.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%2Fzc45qsy3wgn8pm7qweqh.png" alt="IAM Role and Policy" width="800" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lines 23 - 34&lt;/strong&gt; enable API gateway to assume the role &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lines 35 - 44&lt;/strong&gt; define the actions allowed by the policy&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Line 44&lt;/strong&gt; uses Cloudformation intrinsic function !GetAtt to get the Arn of the SQS Queue we create in the stack.&lt;/p&gt;
&lt;h2&gt;
  
  
  Create the Rest API
&lt;/h2&gt;

&lt;p&gt;To create the Rest API, we need to create an AWS::Apigateway::Rest resource.  Usually, the Serverless Framework would make this for you under the covers as it creates functions triggered by HTTP events.  In the configuration for the RestApi, I have used &lt;code&gt;${self:custom.resourcePrefix}&lt;/code&gt; for the "Name", which is a variable I set up to use for the naming of Services throughout my Serverless YAML.&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%2F6pcsliy8zt4bmbo8229v.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%2F6pcsliy8zt4bmbo8229v.png" alt="Create AWS RestApi" width="800" height="259"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Create the API Resources and Integrate to our SQS
&lt;/h2&gt;

&lt;p&gt;We create the  API method using the "AWS::ApiGateway::Method" resource, which needs to look 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%2Fmmoe5x1iwlng85mvowst.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%2Fmmoe5x1iwlng85mvowst.png" alt="AWS API Method" width="800" height="693"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Creating the API  method and integration is where all the work is in building out our zero-code solution and will break this down into the following steps for clarity:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create the API Method and Responses (lines 60 - 70)&lt;/li&gt;
&lt;li&gt;Create the integration - the easy bits (lines 71 - 79)&lt;/li&gt;
&lt;li&gt;Create the Integration Request (lines 80 - 84)&lt;/li&gt;
&lt;li&gt;Create the Integration Response (lines 85 - 99)&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Create the API Method
&lt;/h3&gt;

&lt;p&gt;Creating the Method resource is the easy bit.  We set up the API Method on line 60 and Method responses on lines 64 - 69.  The HTTP status codes returned by the Integration response must be configured in the Method responses so that the API gateway will not throw an error and return an "HTTP 500 internal server error". &lt;/p&gt;

&lt;p&gt;On &lt;strong&gt;line 61&lt;/strong&gt;, we use "!GetAtt apiGw.RootResourceId" to obtain the actual Id for the RestApi.&lt;/p&gt;

&lt;p&gt;On &lt;strong&gt;line 62&lt;/strong&gt;, we use "!Ref apiGw" to retrieve the RestApiId for our REST API. &lt;/p&gt;

&lt;p&gt;We have to use these Cloudformation intrinsic functions here since we want the values after AWS has created them during the stack deployment.&lt;/p&gt;
&lt;h3&gt;
  
  
  Create the integration - the easy bits
&lt;/h3&gt;

&lt;p&gt;I will break this down by the lines to make it easier to follow and explain each step.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Line 71&lt;/strong&gt; defines the HTTP Method API gateway will use when calling the Integration endpoint; in this case, we want to use POST since we are sending data to SQS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Line 72&lt;/strong&gt; defines the type of integration; in this case, we are doing a direct service integration, so will use "AWS".  Using AWS means using an internal integration that allows us to transform the payload rather than "AWS_PROXY", which will proxy the request directly to the integration service leaving the body untouched.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Line 73&lt;/strong&gt; defines the IAM Role the API Gateway will assume while performing the integration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Line 74&lt;/strong&gt; defines the URI of the action we want to invoke.  The Uri action we need to invoke will look like "arn:aws:API gateway:us-east-1:sqs:path/1234567890/the_queue_name", so we are using the Cloudformation intrinsic function "Join" to create this for us.&lt;/p&gt;

&lt;p&gt;Once we have these attributes configured, we have defined the core details on the service we are integrating, the "How".&lt;/p&gt;
&lt;h3&gt;
  
  
  Create The Integration Request
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Line 80&lt;/strong&gt; defines the PassthroughBehaviour - I like to use "NEVER" so that if we receive an unexpected request "Content-Type", then API gateway will respond with an &lt;code&gt;HTTP 415 error - Unsupported Media Type&lt;/code&gt;, which I think is desirable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Line 81 - 82&lt;/strong&gt; defines the request parameters we send to SQS; we will map our request into &lt;code&gt;x-www-form-urlencoded&lt;/code&gt; values when sending our POST to SQS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lines 83 - 84&lt;/strong&gt; define how we will transform the incoming body and send it to SQS; this is a simple VTL template setting the &lt;code&gt;Action=SendMessage&lt;/code&gt; and &lt;code&gt;MessageBody=$input.JSON($)&lt;/code&gt;, which is the JSON body of the request to be sent to the SQS queue. &lt;/p&gt;
&lt;h3&gt;
  
  
  Create the Integration Response
&lt;/h3&gt;

&lt;p&gt;So far, we have created the API, formed the integration request and sent it to SQS; now, we have to deal with the SQS service response and map a response back to our caller using the "IntegrationResponses" attribute.  The value expected here is a MAP of HTTP status codes and VTL template snippets to transform the HTTP response we get from SQS.  In this demo, I have mapped the following response codes - 200, 404 to responses that will look something like the following:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HTTP 200 Response&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We use a VTL template to transform the SQS response into a payload that we want to return rather than the entire integration payload.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;request_id&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;ea87ccb1-92d3-51ff-b9f0-956ad19844d7&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;message_id&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;173689a1-6b40-492e-9e6c-338c13b70f16&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;accepted&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;ok&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;&lt;strong&gt;HTTP 404 Response&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error_code&lt;/span&gt;&lt;span class="dl"&gt;"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error_message&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;Unable to complete Request&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;h2&gt;
  
  
  Deploy and Test the API
&lt;/h2&gt;

&lt;p&gt;After creating the SQS Queue and a new REST API, we need to deploy our API to a stage so our clients can call the API.&lt;br&gt;
Lines 101 - 108 define the Stage Deployment of our API and will make the API available.&lt;br&gt;
Important Note: This resource will only work for the first deployment; any change to your REST API will not deploy as part of a stack update.  I searched far and wide for a solution to this behaviour and landed on the need to run a post-stack update API deployment using the console or AWS CLI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;$&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt; &lt;span class="nx"&gt;apigateway&lt;/span&gt; &lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;deployment&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;\&lt;/span&gt;
    &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;rest&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;\&lt;/span&gt;
    &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Wrap-Up
&lt;/h2&gt;

&lt;p&gt;Writing a Zero-Code Serverless integration has been an evolving journey, and I have shared the details of where I ended up in this article.  I have learned a lot about AWS along the way and encourage you to explore and try code-less integrations in your solutions as a way of levelling up your journey to serverless nirvana.  What I have shared today is not production-ready - I am still exploring these patterns to make them more resilient and reliable for live use.&lt;/p&gt;

&lt;p&gt;Check out the real working Serverless Solution (No code included) on my GitHub repository &lt;a href="https://github.com/walmsles/zero-code-api-sqs" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>aws</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
