<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Frédéric Barthelet</title>
    <description>The latest articles on DEV Community by Frédéric Barthelet (@fredericbarthelet).</description>
    <link>https://dev.to/fredericbarthelet</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%2F500791%2Fe5005ac6-abbe-435f-997a-292a60834e90.jpeg</url>
      <title>DEV Community: Frédéric Barthelet</title>
      <link>https://dev.to/fredericbarthelet</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fredericbarthelet"/>
    <language>en</language>
    <item>
      <title>Definitely a must read for anyone designing CLI for the agent era!</title>
      <dc:creator>Frédéric Barthelet</dc:creator>
      <pubDate>Wed, 15 Apr 2026 11:31:18 +0000</pubDate>
      <link>https://dev.to/fredericbarthelet/definitely-a-must-read-for-anyone-designing-cli-for-the-agent-era-20l6</link>
      <guid>https://dev.to/fredericbarthelet/definitely-a-must-read-for-anyone-designing-cli-for-the-agent-era-20l6</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/alpic/designing-a-cli-for-both-humans-and-agents-4069" class="crayons-story__hidden-navigation-link"&gt;Designing a CLI for Both Humans and Agents&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;
          &lt;a class="crayons-logo crayons-logo--l" href="/alpic"&gt;
            &lt;img alt="Alpic logo" 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%2Forganization%2Fprofile_image%2F11088%2Fd212e0c5-c450-44b8-b527-2dd28112a60f.png" class="crayons-logo__image" width="100" height="100"&gt;
          &lt;/a&gt;

          &lt;a href="/julienvallini" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&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%2Fuser%2Fprofile_image%2F115763%2Fdfeb0700-66f1-48bf-ae58-339d24acd0f5.jpeg" alt="julienvallini profile" class="crayons-avatar__image" width="800" height="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/julienvallini" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Julien Vallini
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Julien Vallini
                
              
              &lt;div id="story-author-preview-content-3504056" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/julienvallini" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F115763%2Fdfeb0700-66f1-48bf-ae58-339d24acd0f5.jpeg" class="crayons-avatar__image" alt="" width="800" height="800"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Julien Vallini&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

            &lt;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/alpic" class="crayons-story__secondary fw-medium"&gt;Alpic&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/alpic/designing-a-cli-for-both-humans-and-agents-4069" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Apr 15&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/alpic/designing-a-cli-for-both-humans-and-agents-4069" id="article-link-3504056"&gt;
          Designing a CLI for Both Humans and Agents
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/cli"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;cli&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devtools"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devtools&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/opensource"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;opensource&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/alpic/designing-a-cli-for-both-humans-and-agents-4069" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;7&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/alpic/designing-a-cli-for-both-humans-and-agents-4069#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              1&lt;span class="hidden s:inline"&gt; comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            5 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>Better MCP tools/call Error Responses: Help Your AI Recover Gracefully</title>
      <dc:creator>Frédéric Barthelet</dc:creator>
      <pubDate>Mon, 28 Jul 2025 07:41:02 +0000</pubDate>
      <link>https://dev.to/alpic/better-mcp-toolscall-error-responses-help-your-ai-recover-gracefully-15c7</link>
      <guid>https://dev.to/alpic/better-mcp-toolscall-error-responses-help-your-ai-recover-gracefully-15c7</guid>
      <description>&lt;p&gt;When building MCP servers, we often focus on the happy path: what happens when tools execute successfully. But what about when things go wrong? The quality of your error responses can make the difference between a frustrated user and an AI that recovers gracefully on its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding MCP Error Types: Protocol vs Tool Errors
&lt;/h2&gt;

&lt;p&gt;Before diving into error response strategies, it's crucial to understand the distinction between two types of errors in MCP:&lt;/p&gt;

&lt;h3&gt;
  
  
  MCP Protocol-Level Errors
&lt;/h3&gt;

&lt;p&gt;These are errors in the MCP communication itself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Connection closed or request timeout&lt;/li&gt;
&lt;li&gt;Tool not found&lt;/li&gt;
&lt;li&gt;Malformed requests or protocol violations&lt;/li&gt;
&lt;li&gt;Internal server errors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These errors trigger standard JSON-RPC error responses and typically indicate something is fundamentally broken with the request or the server.&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;"jsonrpc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error"&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;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;-32001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Request Timeout"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Tools/call Errors (The Focus of This Article)
&lt;/h3&gt;

&lt;p&gt;These are errors that occur during tool execution. The tool was found and called, but something went wrong during the processing. These should &lt;strong&gt;not&lt;/strong&gt; be returned as MCP protocol errors, but as successful MCP JSON-RPC responses with &lt;code&gt;isError: true&lt;/code&gt; in the result payload.&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;"jsonrpc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&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;"content"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"An error occurred."&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;"isError"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="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;h2&gt;
  
  
  Tools/call Error Responses Are Context, Not Dead Ends
&lt;/h2&gt;

&lt;p&gt;Why bother sharing so many details about the difference between these two error formats? They're both still errors, right? Nothing that needs much attention.&lt;/p&gt;

&lt;p&gt;Wrong! MCP protocol-level errors are captured by the MCP client, eventually surfaced in the UI (like a notification in Claude), and discarded. On the other hand, &lt;strong&gt;tools/call errors are injected back into the LLM context window, just like successful responses&lt;/strong&gt;. Smart error messages can be leveraged by the model as much as any other prompt, giving it a chance to recover from the error without human intervention.&lt;/p&gt;

&lt;p&gt;Most open-source MCP implementations I've seen return generic tool error messages that leave the AI (and users) in the dark. Let's look at what it takes to rework error messages and increase your server's overall quality.&lt;/p&gt;

&lt;h2&gt;
  
  
  3 Use-Cases of Better Error Responses
&lt;/h2&gt;

&lt;p&gt;Here are examples of elevated error messages that improve model task completion rate (the north star metric used to evaluate MCP server quality).&lt;/p&gt;

&lt;h3&gt;
  
  
  Tool Ordering Guidance
&lt;/h3&gt;

&lt;p&gt;If the application's state prevents the model from using a tool for a given resource, provide instructions on how to update that state to make the tool usable. For example, if you're a famous three-letter infrastructure company exposing a tool to terminate an instance, but this tool can only be called when the instance is in a stopped state, say so in the error message.&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;"content"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"You can't terminate an instance in the running state. Use the stop_instance tool first on this instance."&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;"isError"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="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;
  
  
  Refined Validation Messages
&lt;/h3&gt;

&lt;p&gt;When tool input validation criteria aren't fully representable in JSON schema, use tool error messages to give the model additional context. If you're a travel company exposing a booking tool on your MCP server and the model accidentally misinterprets the current year for your booking request, you can correct it:&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;"content"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The requested travel date cannot be set in the past. You requested travel on July 31st, 2024, but the current date is July 25th, 2025. Did you mean to plan for travel on July 31st, 2025 instead?"&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;"isError"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="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;
  
  
  Smart Unknown Error Handling
&lt;/h3&gt;

&lt;p&gt;Even when you can't provide precise details about an error, give the model instructions on retry strategy and fallback actions to direct the user to:&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;"content"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"An unknown error happened. Try again immediately. If it's the 3rd time you're encountering this issue, provide the user with a link to https://mydashboard.example.com/manual-task to perform the task manually."&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;"isError"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="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;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Error handling in MCP isn't just about graceful failures—it's about creating collaborative experiences where AI can self-correct and recover. By treating error responses as contextual guidance rather than terminal states, you transform frustrating dead ends into stepping stones toward success.&lt;/p&gt;

&lt;p&gt;Remember: every error response is an opportunity to teach the AI how to do better next time.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;What patterns have you found effective for MCP error handling? Share your experiences in the comments below.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>mcp</category>
    </item>
    <item>
      <title>9 Surprises using AWS EventBridge Scheduler</title>
      <dc:creator>Frédéric Barthelet</dc:creator>
      <pubDate>Thu, 01 Dec 2022 17:56:30 +0000</pubDate>
      <link>https://dev.to/slsbytheodo/9-surprises-using-aws-eventbridge-scheduler-13b6</link>
      <guid>https://dev.to/slsbytheodo/9-surprises-using-aws-eventbridge-scheduler-13b6</guid>
      <description>&lt;p&gt;AWS released its news AWS EventBridge Scheduler service, dedicated to planing tasks in your application. The service is available on all regions using the SDK, the CDK, the CLI and the web management console.&lt;/p&gt;

&lt;p&gt;Since the service was released, I've been thoroughly migrating existing workloads that leveraged homemade scheduling features using  DynamoDB TTL or any other contraption. This article serves as a discovery report, describing the good, the bad and my general recommendations when it comes to using this new serverless scheduling managed service, in an effort to save someone else troubles understanding the use cases and limits of the service.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the Scheduler
&lt;/h2&gt;

&lt;p&gt;The Scheduler is an AWS managed service, dedicated to scheduling one-time or recurring triggers targeting AWS services actions.&lt;/p&gt;

&lt;p&gt;There are 3 types of schedules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One-time schedules - &lt;em&gt;December 21st at 7AM UTC&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Rate-based schedules, allowing recurring tasks using frequency rate - &lt;em&gt;every 2 hours&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Cron-based schedules, allowing recurring tasks using a cron expression - &lt;em&gt;every Friday at 4PM&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Schedules can be grouped in Schedule Groups. Both Schedules and Schedule Groups can be provisioned using a CRUD API on the service.&lt;/p&gt;

&lt;p&gt;Rate-based and cron-based schedules can be triggered in a specific timeframe using a start date and an end date. One-time and cron-based schedules are sensible to an optional timezone parameter in which the schedule expression should be evaluated.&lt;/p&gt;

&lt;p&gt;Each schedule can trigger one target. There are 3 types of targets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Templated Isomorphic targets&lt;/li&gt;
&lt;li&gt;Templated Service-specific targets&lt;/li&gt;
&lt;li&gt;Universal targets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unlike Universal targets that require the Scheduler to bootstrap an execution environment to use the AWS SDK, both Templated Isomorphic and Service-specific targets are most likely leveraging EventBridge API Destination features to interact with the destination AWS services using their HTTP interface.&lt;/p&gt;

&lt;h3&gt;
  
  
  Templated Isomorphic targets
&lt;/h3&gt;

&lt;p&gt;You can trigger the following services APIs using the same generic target definition at the creation of your schedule:&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%2F7cso01m80njgwm9edz48.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%2F7cso01m80njgwm9edz48.png" alt="List of AWS services and corresponding actions that can be triggered using templated isomorphic targets" width="800" height="111"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The target definition only requires an &lt;code&gt;arn&lt;/code&gt; (like the one of the Lambda function to be invoked or the Step Functions state machine to be started). It allows the use of an optional &lt;code&gt;input&lt;/code&gt; field. &lt;/p&gt;

&lt;h3&gt;
  
  
  Templated Service-specific targets
&lt;/h3&gt;

&lt;p&gt;You can trigger the following services APIs using additional service-specific options at the creation of your schedule:&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%2Fn26t9rwr1lljww4xt5ut.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%2Fn26t9rwr1lljww4xt5ut.png" alt="List of AWS services and corresponding actions that can be triggered using templated service-specific targets" width="800" height="173"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Like for templated isomorphic targets, the schedule definition only requires an &lt;code&gt;arn&lt;/code&gt;. In addition to the optional &lt;code&gt;input&lt;/code&gt; field, one service specific attribute can be added, named &lt;code&gt;${service}Parameters&lt;/code&gt; to the schedule target definition in order to further configure the action. For exemple, you can provide a &lt;code&gt;SqsParameters&lt;/code&gt; parameter in order to specify &lt;code&gt;MessageGroupId&lt;/code&gt; value for the SQS &lt;code&gt;SendMessage&lt;/code&gt; target.&lt;/p&gt;

&lt;h3&gt;
  
  
  Universal targets
&lt;/h3&gt;

&lt;p&gt;Universal targets allows schedules to target any AWS service and any corresponding action using an abstracted compute environment with the SDK capabilities. Universal target are defined using the magic string &lt;code&gt;arn:aws:scheduler:::aws-sdk:${service}:${apiAction}&lt;/code&gt; as an &lt;code&gt;arn&lt;/code&gt;. This feature is similar to the AWS Step Functions SDK services integration released in September 2021.&lt;/p&gt;

&lt;h2&gt;
  
  
  The good surprises 🎉
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Scheduler is right on time !
&lt;/h3&gt;

&lt;p&gt;It's not clearly written in the documentation, but &lt;a href="https://aws.amazon.com/fr/blogs/compute/introducing-amazon-eventbridge-scheduler/" rel="noopener noreferrer"&gt;Marcia Villalba release post&lt;/a&gt; mentions a granularity of one-minute. We can safely assume schedules precision is within a 60 seconds margin when flexible time window is disabled. In practice, running tests over 10.000 data points, using both Universal and Templated Targets aiming at invoking the same Lambda function, the results show both modes successfully trigger within 50 seconds.&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%2F1vxkdwlnnqyppp4lshzt.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%2F1vxkdwlnnqyppp4lshzt.png" alt="Average, min, max and p90 delay between schedule time and execution time for Universal and Templated targets aiming at invoking the same Lambda function" width="800" height="81"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In addition, delta between scheduled time and invocation time has a roughly unified repartition from 0 to 50 seconds&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%2F5w4vwe3lj8ytwhdbz2s6.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%2F5w4vwe3lj8ytwhdbz2s6.png" alt="Delta repartition graph for Universal and Templated targets" width="800" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Being right on time with a precision of a minute is a huge step forward compared to the 48 hours guarantee on DynamoDB TTL expiration. Of course this DynamoDB garbage collection feature was never intended for precise scheduling, but &lt;a href="https://theburningmonk.com/2019/03/dynamodb-ttl-as-an-ad-hoc-scheduling-mechanism/" rel="noopener noreferrer"&gt;measured results were encouraging&lt;/a&gt; and a lot of application still relied on this mechanism. Lately, delta has considerably increased and the Scheduler release felt like a blessing!&lt;/p&gt;

&lt;p&gt;If you require a scheduling mechanism with a precision to the exact second, have a look at the &lt;a href="https://github.com/guiyom-e/cdk-scheduler" rel="noopener noreferrer"&gt;CDK Scheduler&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Authorization has a per-Schedule granularity
&lt;/h3&gt;

&lt;p&gt;The EventBridge Scheduler allows use of a different role for each Schedule, similar to EventBridge Rules current behavior. Granularity with minimal policy documents is therefore easily enforceable and misconfiguration can be avoided thanks to thorough permission management. &lt;/p&gt;

&lt;p&gt;This Schedule specific role should include permissions for the targeted service and its corresponding action. This role should also be assumable by the Scheduler service.&lt;/p&gt;

&lt;p&gt;Since one-time Schedule will mostly be provisioned at runtime (like for instance to send a reminder email to a user 10 days after its initial connection), please note that the role assumed by the compute unit in charge of creating the Schedule should:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;allow &lt;code&gt;schedule:CreateSchedule&lt;/code&gt; action&lt;/li&gt;
&lt;li&gt;allow &lt;code&gt;iam:PassRole&lt;/code&gt; action for the role to be used by the Schedule&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Schedules access patterns are relevant
&lt;/h3&gt;

&lt;p&gt;Schedules can be grouped in Schedule Groups (they are by default created in a group conveniently named &lt;code&gt;default&lt;/code&gt;). Schedule ARNs are predictable, no technical IDs are involved in the management of Schedules and Schedule Groups. A Schedule ARN follows this syntax: &lt;code&gt;arn:aws:scheduler:${region}:${accountId}:schedule/${scheduleGroupName}/${scheduleName}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Listing existing Schedules with the &lt;code&gt;ListSchedule&lt;/code&gt; action allows multiple access pattern:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;list all Schedules&lt;/li&gt;
&lt;li&gt;list all Schedules in a specific Schedule Group&lt;/li&gt;
&lt;li&gt;list all Schedules whose name has a specific prefix&lt;/li&gt;
&lt;li&gt;list all Schedules in a specific Schedule Group and whose name has a specific prefix&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This allow for clear business logic separation like in multi-tenant applications, ensuring no collision occurs within code when it comes to handling Schedules dedicated to a single tenant. It is strangely resembling a composite primary key access pattern on a DynamoDB Table, where Schedule Group names officiate as separate partitions and Schedules as distinct items who's name is the sort key.&lt;/p&gt;

&lt;p&gt;Get, Update and Delete actions require however an exact identifier - Name and GroupName (which is equivalent to providing the ARN).&lt;/p&gt;

&lt;h3&gt;
  
  
  Scheduler is protected against recursive calls
&lt;/h3&gt;

&lt;p&gt;Universal targets leverage a dedicated compute unit to execute SDK actions for a specific Schedule. Not all SDK services and actions are included in this environment. For instance, all actions of the Scheduler are excluded, preventing unintended recursive calls that may break the bank! I did however had a lot of fun trying to create a recursive Schedule targeting &lt;code&gt;arn:aws:scheduler:::aws-sdk:schedule:createSchedule&lt;/code&gt; with the same payload.&lt;/p&gt;

&lt;h2&gt;
  
  
  The not so good surprises 🤯
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Schedules remain visible after their job is done
&lt;/h3&gt;

&lt;p&gt;The Scheduler does not distinguish still-relevant and irrelevant Schedules. What I call irrelevant Schedules are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one-time Schedules who's scheduled date has passed and target was successfully invoked&lt;/li&gt;
&lt;li&gt;recurring Schedules who's end date has passed and target was successfully invoked on all occurrences&lt;/li&gt;
&lt;li&gt;deactivated Schedules. Those are the only irrelevant Schedules that can be identified and filtered out of listing operations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those Schedules are indeed irrelevant since there is no remaining tasks associated with them. Except for debugging purpose, they have no remaining impact on the overall application behavior.&lt;/p&gt;

&lt;p&gt;Keeping those irrelevant Schedules around induces various problems. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Irrelevant Schedules count towards the per region quota of 1 million Schedules&lt;/strong&gt;. While this quota can be increased, any limitation impacting an application history (formerly, all Schedules that were ever created in the context of a specific application) is doomed to be a critical problem at some point. Remember disk space storage issues induced by endlessly writing application logs? We're finding ourself in the exact same situation here.&lt;/p&gt;

&lt;p&gt;In addition, &lt;strong&gt;no validation occurs at Schedule creation&lt;/strong&gt; to ensure newly created ones are not already irrelevant at the time of creation.&lt;/p&gt;

&lt;p&gt;Finally, &lt;strong&gt;there is no efficient way to list remaining relevant Schedules&lt;/strong&gt; at any time on a given workload.&lt;/p&gt;

&lt;h3&gt;
  
  
  Templated Targets &lt;code&gt;input&lt;/code&gt; field mapping is highly inconsistent
&lt;/h3&gt;

&lt;p&gt;The optional &lt;code&gt;input&lt;/code&gt; field that you can use on templated targets is highly inconsistent. I had to experiment quite a lot with schedules to be able to produce the following few mappings with API reference documentation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;EventBridge – &lt;em&gt;PutEvents&lt;/em&gt;&lt;/strong&gt; &lt;code&gt;input&lt;/code&gt; will be mapped to &lt;a href="https://docs.aws.amazon.com/eventbridge/latest/APIReference/API_PutEvents.html#API_PutEvents_RequestParameters" rel="noopener noreferrer"&gt;&lt;code&gt;Entries[0].Detail&lt;/code&gt;&lt;/a&gt; field&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kinesis Data Firehose – &lt;em&gt;PutRecord&lt;/em&gt;&lt;/strong&gt; -&amp;gt; &lt;code&gt;input&lt;/code&gt; will be mapped &lt;a href="https://docs.aws.amazon.com/firehose/latest/APIReference/API_PutRecord.html#API_PutRecord_RequestParameters" rel="noopener noreferrer"&gt;&lt;code&gt;Record.Data&lt;/code&gt;&lt;/a&gt; field&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lambda – &lt;em&gt;Invoke&lt;/em&gt;&lt;/strong&gt; -&amp;gt; &lt;code&gt;input&lt;/code&gt; will be mapped to &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/API_Invoke.html#API_Invoke_RequestBody" rel="noopener noreferrer"&gt;&lt;code&gt;Payload&lt;/code&gt;&lt;/a&gt; field&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amazon SNS – &lt;em&gt;Publish&lt;/em&gt;&lt;/strong&gt; &lt;code&gt;input&lt;/code&gt; will be mapped to &lt;a href="https://docs.aws.amazon.com/sns/latest/api/API_Publish.html#API_Publish_RequestParameters" rel="noopener noreferrer"&gt;&lt;code&gt;Message&lt;/code&gt;&lt;/a&gt; field&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amazon SQS – &lt;em&gt;SendMessage&lt;/em&gt;&lt;/strong&gt; &lt;code&gt;input&lt;/code&gt; will be mapped to &lt;a href="https://docs.aws.amazon.com/eventbridge/latest/APIReference/API_PutEvents.html#API_PutEvents_RequestParameters" rel="noopener noreferrer"&gt;&lt;code&gt;MessageBody&lt;/code&gt;&lt;/a&gt; field&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step Functions – &lt;em&gt;StartExecution&lt;/em&gt;&lt;/strong&gt; &lt;code&gt;input&lt;/code&gt; will be mapped to &lt;a href="https://docs.aws.amazon.com/step-functions/latest/apireference/API_StartExecution.html#StepFunctions-StartExecution-request-input" rel="noopener noreferrer"&gt;&lt;code&gt;input&lt;/code&gt;&lt;/a&gt; field&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Scheduler supported action list is inconsistent
&lt;/h3&gt;

&lt;p&gt;Actions relative to Schedules are referenced with &lt;code&gt;schedule:${action}&lt;/code&gt;, while actions relative to Schedule Groups are referenced with &lt;code&gt;scheduler:${action}&lt;/code&gt; in policy documents. Small detail here, but can be really troublesome the first time you write a policy document to use for the Scheduler. You can have a full list of all actions &lt;a href="https://docs.aws.amazon.com/scheduler/latest/UserGuide/security_iam_id-based-policy-examples.html#security_iam_id-based-policies-permissions" rel="noopener noreferrer"&gt;in the Scheduler documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Update action has a replace all strategy
&lt;/h3&gt;

&lt;p&gt;Missing optional fields in an update statement are replaced with their default value. Updates on Schedules has unintended behavior if any value that should remain unchanged are not provided in the payload.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prefix attribute for ListSchedule action regex does not match name attribute regex
&lt;/h3&gt;

&lt;p&gt;Schedule name has the following regexp: &lt;code&gt;^[0-9a-zA-Z-_.]+$&lt;/code&gt;&lt;br&gt;
Listing Schedules using a name prefix filter only accepts an argument following this regexp: &lt;code&gt;^[a-zA-Z][0-9a-zA-Z-_]*$&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Long story short: you can only use name prefix filter access pattern for Schedules who's name starts with an alphabetical character. I initially designed my one-time Schedules name to start with ISO8601 representation of the scheduled date to circumvent irrelevant Schedules issue. This proved to be a wrong design intent since all ISO8601 representations start with a number, and cannot therefore be used as prefix attribute in a ListSchedule operation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should you use the AWS EventBridge Scheduler?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  You're currently using CloudWatch Rules or EventBridge Rules
&lt;/h3&gt;

&lt;p&gt;Rules using cron-based and rate-based schedules should be migrated to EventBridge Scheduler. You'll be able to reach more target types than with existing EventBridge target catalog. You'll also be able to remove a few Lambda functions who's sole purpose was to use the SDK to reach a service not integrated with EventBridge. Finally, the Scheduler has 14 million Schedules included in its free tier each month, your application may not use as much and you'll remain free of charge after migrating to this new service.&lt;/p&gt;

&lt;h3&gt;
  
  
  You're currently using DynamoDB TTL
&lt;/h3&gt;

&lt;p&gt;Using DynamoDB TTL to schedule one-time tasks can now be definitely deprecated. The pricing impact of removing DynamoDB and Lambda altogether from the &lt;a href="https://www.serverless.com/blog/guest-post-developing-a-serverless-scheduler-using-dynamodb-ttl-and-filtered-streams" rel="noopener noreferrer"&gt;required infrastructure to implement such scheduling mechanism&lt;/a&gt; is worth it. Even if you're currently fine with the 48 hours window of DynamoDB TTL, you should rely on the Scheduler with the corresponding flexible time window parameter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key take-aways
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Always use Universal Targets
&lt;/h3&gt;

&lt;p&gt;Indeed, universal targets have quite a few advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;📚 Targets catalog&lt;/strong&gt; All templated targets can be achieved with universal targets. Universal targets cover almost all AWS services and actions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;👨‍💻 Developer Experience&lt;/strong&gt; You can safely rely on targeted service actions documentation instead of hoping you're aiming for the correct field using &lt;code&gt;input&lt;/code&gt; shortcut provided by templated targets. Your schedule definition will be consistent and self-explanatory since they won't be relying on EventBridge Scheduler specific shorthand syntax. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;⚙️ Configurability&lt;/strong&gt; You can use all allowed configuration options for the action you want to trigger.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;💶 Cost&lt;/strong&gt; There is no additional charges to use universal targets, it costs the same while doing much more!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🏃 Schedule precision&lt;/strong&gt; Unlike what I initially assumed, universal targets are slightly closer to the requested scheduled time (considering p90 delta).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Provision at the right time
&lt;/h3&gt;

&lt;p&gt;Schedules and Schedule Groups can be created at deploy time, using any IaC framework, or at runtime, using the SDK. A few recommendations regarding when to provision which:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Almost always, Schedule Groups should be provision at deploy time.&lt;/li&gt;
&lt;li&gt;Schedule Groups used for tenancy segregation in multi-tenant applications are the only groups that should be provisioned at runtime, at the time of tenant creation.&lt;/li&gt;
&lt;li&gt;Recurring Schedules without start and end dates are relevant throughout the entire lifespan of an application, they should be provisioned using IaC at deploy time.&lt;/li&gt;
&lt;li&gt;One-time Schedules and recurring Schedules with a given timeframe should be created at runtime, resulting from a user action, within their respective previously provisioned Schedule Groups&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  If you need to use UpdateSchedule actions, always use GetSchedule beforehand as starting point for your command payload
&lt;/h3&gt;

&lt;p&gt;Indeed, update actions in EventBridge Scheduler use a replace all attributes strategy. If you omit a value that was previously given (at creation or previous update), the Schedule will use the default value for the corresponding missing attributes. This can lead to unexpected behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prefer the use of UTC
&lt;/h3&gt;

&lt;p&gt;Timezone management is a pain, always. The Scheduler tries to compensate with an optional timezone parameter and implements daylight savings time shift.&lt;/p&gt;

&lt;p&gt;In most cases, if you want to avoid timezone strange behavior, prefer relying on a date management library to convert one-time Schedules scheduled time in UTC before creating it.&lt;/p&gt;

&lt;p&gt;Cron-based Schedules are the only relevant Schedules that might benefit from timezone sensitive settings.&lt;/p&gt;

&lt;p&gt;Rate-based Schedules are unaffected by this setting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Always use a DLQ on your Schedules
&lt;/h3&gt;

&lt;p&gt;If it can fail, it will fail at some point. CloudWatch metrics available for the Scheduler cannot distinguished failed target invocation on a per-Schedule basis. Provisioning a dead-letter queue and referencing it as destination for all your schedules is a must have!&lt;/p&gt;

&lt;h3&gt;
  
  
  Implement a regular cleanup process for irrelevant Schedules
&lt;/h3&gt;

&lt;p&gt;Regular cleaning of now irrelevant Schedules should be implemented to keep the total number of Schedules under control and avoid reaching the 1 million quota for the service. You can rely on a rate-based Schedule to regularly invoke a Lambda function dedicated to listing and deleting irrelevant Schedules. You can adjust the retention period, for debugging purpose, for which you still want to keep a Schedule around by changing your programmatic filtering parameters.&lt;/p&gt;

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

&lt;p&gt;All things considered, the new AWS EventBridge Scheduler service feels like a blessing, especially for one-time Schedules where there were no robust alternatives on AWS. Google Cloud Platform had &lt;a href="https://cloud.google.com/tasks" rel="noopener noreferrer"&gt;Cloud Task&lt;/a&gt; since 2018 for this specific purpose. It's nice to see AWS matching the offer and providing, almost for free, a dedicated managed service with precise scheduling mechanism.&lt;/p&gt;

&lt;p&gt;At the time of publishing this discovery report, some questions remain unanswered. Among the various subjects I'll dig into, but save the findings for a separate article, you'll find:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;why and when to use the &lt;strong&gt;flexible time window&lt;/strong&gt; parameter&lt;/li&gt;
&lt;li&gt;why and when to use the &lt;strong&gt;client token&lt;/strong&gt;. How can you ensure &lt;strong&gt;idempotency&lt;/strong&gt; when you interact with the Scheduler&lt;/li&gt;
&lt;li&gt;what kind of &lt;strong&gt;L2/L3 CDK Construct&lt;/strong&gt; can and should be implemented to ease up integration of this service&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>serverless</category>
      <category>scheduler</category>
      <category>aws</category>
      <category>task</category>
    </item>
    <item>
      <title>Serverless Framework ️&lt;3 AWS CDK</title>
      <dc:creator>Frédéric Barthelet</dc:creator>
      <pubDate>Wed, 26 Jan 2022 10:25:21 +0000</pubDate>
      <link>https://dev.to/slsbytheodo/serverless-framework-aws-cdk-1dnf</link>
      <guid>https://dev.to/slsbytheodo/serverless-framework-aws-cdk-1dnf</guid>
      <description>&lt;p&gt;There are plenty of articles out there comparing major Infrastructure as Code frameworks when it comes to building and deploying an AWS serverless stack. A serverless newcomer can easily be overwhelmed by the wide variety of solutions available: Serverless Framework, AWS CDK, Pulumi, Terraform, SST, Architect...&lt;br&gt;
Rather than pitting those solutions against each other, this article focus on leveraging two of them, &lt;a href="https://github.com/serverless/serverless" rel="noopener noreferrer"&gt;Serverless Framework&lt;/a&gt; and &lt;a href="https://github.com/aws/aws-cdk/" rel="noopener noreferrer"&gt;AWS CDK&lt;/a&gt;, for what they are best at and making them work together seamlessly.&lt;/p&gt;

&lt;p&gt;This solution does not require any additional dependency, plugin or CLI tool. It works right out of the box, using native CDK API and Serverless Framework service definition properties.&lt;/p&gt;
&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;The following service file definition provides a simple way to deploy an application relying on Lamba-centric resources as well as other type of resources, leveraging respectively Serverless Framework and CDK, all using a single CloudFormation deployment. This solution improves greatly the developer experience by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;leveraging the simple configuration format of the Serverless Framework&lt;/li&gt;
&lt;li&gt;leveraging AWS CDK constructs rather than vanilla CloudFormation for resources outside of the scope of the Serverless Framework&lt;/li&gt;
&lt;li&gt;using a single language to define the entire stack
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// serverless.ts&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AWS&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;@serverless/typescript&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;App&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;DefaultStackSynthesizer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Stack&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-cdk-lib&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AttributeType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Table&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-cdk-lib/aws-dynamodb&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;app&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;App&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Stack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="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;// Override default synthetizer to prevent check related to CDK bootstrap&lt;/span&gt;
  &lt;span class="na"&gt;synthesizer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DefaultStackSynthesizer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;generateBootstrapVersionRule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="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;table&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;Table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MyDynamoDBTable&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;partitionKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PK&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AttributeType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STRING&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;serverlessConfiguration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AWS&lt;/span&gt; &lt;span class="o"&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;serverless-framework-loves-aws-cdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nodejs14.x&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;iam&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;statements&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;Effect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Allow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;Action&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;dynamodb:PutItem&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="na"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tableArn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;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;functions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;saveUser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;saveUser.main&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;TABLE_NAME&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;events&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;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST /user&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="na"&gt;package&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;individually&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;synth&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getStackByName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stackName&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kr"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;serverlessConfiguration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

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

&lt;p&gt;Let's deep dive, step by step, in this service file, and understand how Serverless Framework and AWS CDK work together seamlessly at provisioning a state of the art serverless application.&lt;/p&gt;
&lt;h3&gt;
  
  
  Serverless Framework TypeScript service file
&lt;/h3&gt;

&lt;p&gt;Since v1.72.0, the &lt;a href="https://www.serverless.com/framework/docs/providers/aws/guide/intro#services" rel="noopener noreferrer"&gt;Serverless framework accepts &lt;code&gt;serverless.ts&lt;/code&gt; as a valid service file&lt;/a&gt; in addition to the more commonly-known &lt;code&gt;serverless.yml&lt;/code&gt;, &lt;code&gt;serverless.json&lt;/code&gt; and &lt;code&gt;serverless.js&lt;/code&gt; file formats. Using &lt;code&gt;serverless.js&lt;/code&gt; or &lt;code&gt;serverless.ts&lt;/code&gt; service definition is a requirement to implement this solution. Both those formats allow programmatic execution using Node.js in order to build the output service definition. In addition, you benefit from TypeScript definitions exported by &lt;a href="https://github.com/serverless/typescript" rel="noopener noreferrer"&gt;&lt;code&gt;@serverless/typescript&lt;/code&gt; package&lt;/a&gt; to help you build your Serverless Framework service definition properly.&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;// serverless.ts&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AWS&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;@serverless/typescript&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;serverlessConfiguration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AWS&lt;/span&gt; &lt;span class="o"&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;serverless-framework-loves-aws-cdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nodejs14.x&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="kr"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;serverlessConfiguration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  AWS CDK pure resources
&lt;/h3&gt;

&lt;p&gt;Within the same &lt;code&gt;serverless.ts&lt;/code&gt; file, or in any other file, you can bootstrap your CDK application, creating a new &lt;code&gt;App&lt;/code&gt; and &lt;code&gt;Stack&lt;/code&gt;. You can then start to add resources not natively handled by the Serverless Framework, based on your app's requirements, referencing the newly created stack as the resource scope.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Stack&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-cdk-lib&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AttributeType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Table&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-cdk-lib/aws-dynamodb&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;app&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;App&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Stack&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;app&lt;/span&gt;&lt;span class="p"&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="c1"&gt;// Override default synthetizer to prevent check related to CDK bootstrap&lt;/span&gt;
  &lt;span class="na"&gt;synthesizer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DefaultStackSynthesizer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;generateBootstrapVersionRule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;table&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;Table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MyDynamoDBTable&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;partitionKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PK&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AttributeType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STRING&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Shipping AWS CDK resources within Serverless Framework generated CloudFormation
&lt;/h3&gt;

&lt;p&gt;In order to bridge AWS CDK and Serverless Framework, you can use native features from both frameworks.&lt;/p&gt;

&lt;p&gt;On one side, Serverless Framework provides an option to &lt;a href="https://www.serverless.com/framework/docs/providers/aws/guide/resources" rel="noopener noreferrer"&gt;include custom CloudFormation&lt;/a&gt;, using the &lt;code&gt;resources&lt;/code&gt; property from the service definition. This property is usually used to inject vanilla CloudFormation, but we'll leverage AWS CDK to avoid doing so.&lt;/p&gt;

&lt;p&gt;On the other, AWS CDK provides an API to programmatically generate a Cloud Assembly using the &lt;code&gt;synth&lt;/code&gt; method of any &lt;code&gt;App&lt;/code&gt; instance. A Cloud Assembly instance is a CDK internal class used to represent a deployable cloud application. Each CDK App can contain multiple CDK Stack, but once again, the CDK provides an API to select a specific stack representation from a Cloud Assembly instance. Finally, each stack of a Cloud Assembly instance exposes the underlying CloudFormation template, as generated by AWS CDK CLI operations.&lt;/p&gt;

&lt;p&gt;Using AWS CDK API to generate desired template, and injecting it within Serverless Framework service definition &lt;code&gt;resources&lt;/code&gt; property does the trick bridging both frameworks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;serverlessConfiguration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AWS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;synth&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getStackByName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stackName&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Adding references to AWS CDK resources within Serverless Framework service definition
&lt;/h3&gt;

&lt;p&gt;There's no good bridging both frameworks if they can't interact with each other. Serverless Framework Lambda handlers usually need resource specific identifiers in order to interact with those resources.&lt;/p&gt;

&lt;h4&gt;
  
  
  Exporting CDK-generated DynamoDB table name to Serverless function
&lt;/h4&gt;

&lt;p&gt;In order to insert new items within AWS CDK provisioned DynamoDB table, let's create a new function using Serverless Framework service definition. This function will implement AWS SDK &lt;code&gt;@aws-sdk/lib-dynamodb&lt;/code&gt; to execute a &lt;code&gt;PutItem&lt;/code&gt; command. This method requires the DynamoDB table name in order to insert the new item in the correct table. One way to pass the variable representing the DynamoDB table name is to use the &lt;code&gt;environment&lt;/code&gt; property of the function definition.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;serverlessConfiguration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AWS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="na"&gt;functions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;saveUser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;saveUser.main&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;TABLE_NAME&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Injecting DynamoDB table name here&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;events&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;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST /user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The actual value for the DynamoDB table, provisioned using AWS CDK, is not known before deployment. One could solve this using &lt;a href="https://docs.aws.amazon.com/cdk/api/v1/docs/@aws-cdk_aws-dynamodb.Table.html#tablename" rel="noopener noreferrer"&gt;&lt;code&gt;tableName&lt;/code&gt; props from CDK Table construct&lt;/a&gt;, however it is good practice &lt;strong&gt;NOT&lt;/strong&gt; constraining its name value as conflict may occur if two tables share the same name within the same AWS account and region (which will happen if you deploy the same Serverless service twice with different stage value). This goes for many resources where unicity constraints are imposed within the same AWS account, or even globally (like S3 bucket name)!&lt;/p&gt;

&lt;p&gt;In order to resolve at deployment the actual table name, one can use CloudFormation intrinsic functions. In the case of a DynamoDB table name, the &lt;code&gt;Ref&lt;/code&gt; intrinsic functions will do the trick following &lt;a href="https://docs.aws.amazon.com/fr_fr/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html#aws-resource-dynamodb-table-return-values" rel="noopener noreferrer"&gt;DynamoDB CloudFormation documentation&lt;/a&gt;. With AWS CDK, there is no need to bother with intrinsic function syntax. Each construct exposes a set of property representing the resource return values, that will ultimately resolves to intrinsic functions under the hood. For DynamoDB &lt;code&gt;Table&lt;/code&gt; construct, &lt;code&gt;table.tableName&lt;/code&gt; resolves to the actual table name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;TABLE_NAME&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above statement will however translate to a completely unexpected value in the generated CloudFormation template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;TABLE_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${Token[TOKEN.183]}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AWS CDK actually resolves some value at a later stage of the app's lifecycle and uses &lt;a href="https://docs.aws.amazon.com/cdk/v2/guide/tokens.html" rel="noopener noreferrer"&gt;tokens&lt;/a&gt; as placeholder in the meantime. In order to get the underlying value behind this CDK generated token, we can use &lt;code&gt;resolve&lt;/code&gt; method from the CDK Stack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;TABLE_NAME&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This completes proper setup of the Serverless Framework function in order to perform operations with the CDK generated DynamoDB table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;serverlessConfiguration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AWS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="na"&gt;functions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;saveUser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;saveUser.main&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;TABLE_NAME&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;events&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;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST /user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Granting Serverless function permissions to interact with CDK-generated DynamoDB table
&lt;/h4&gt;

&lt;p&gt;Similarly, Serverless Framework service definition will require additional configuration to ensure the &lt;code&gt;saveUser&lt;/code&gt; function is actually allowed to insert new items in the DynamoDB table.&lt;/p&gt;

&lt;p&gt;This is usually done using the &lt;code&gt;provider.iam.role.statements&lt;/code&gt; configuration, adding a new IAM Policy statement which allows DynamoDB PutItem operation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;serverlessConfiguration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AWS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;iam&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;statements&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;Effect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Allow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;Action&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;dynamodb:PutItem&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="na"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="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="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While opening up IAM Policy statement effect to all DynamoDB table is tempting and very easy to implement, it is not recommended, is considered a way too broad permission and may raise security concerns.&lt;/p&gt;

&lt;p&gt;You can however overcome this issue following the same principal than before: CDK &lt;code&gt;Table&lt;/code&gt; construct exposes a &lt;code&gt;tableArn&lt;/code&gt; property than can be used to narrow down resources with whom &lt;code&gt;PutItem&lt;/code&gt; is actually permitted.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tableArn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;Using &lt;code&gt;synth&lt;/code&gt; API from AWS CDK to access the underlying Cloud Assembly instance and its CloudFormation template has its limits: no asset can be uploaded on AWS as part of the normal deployment cycle of AWS CDK. AWS CDK relies on a &lt;a href="https://docs.aws.amazon.com/cdk/v2/guide/bootstrapping.html" rel="noopener noreferrer"&gt;bootstrapping separate stack&lt;/a&gt;, that needs to be provisioned independently, whenever one of the resource leverages CDK Assets. For example, deploying &lt;code&gt;Function&lt;/code&gt; constructs as part of your CDK stack requires file and/or Docker images to be uploaded to your AWS account, and therefore requires the bootstrapping stack to perform such upload.&lt;/p&gt;

&lt;p&gt;Using AWS CDK to complement the Serverless Framework often mean that you won't actually ever rely on &lt;code&gt;Function&lt;/code&gt; construct, Lambda provisioning being Serverless Framework scope, but it's worth mentioning as a limitation in case another resource type, not handled by Serverless Framework, also relies on assets being uploaded.&lt;/p&gt;

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

&lt;p&gt;This code snippet and detailed explanations is of course only demonstration of what can be achieved using both Serverless Framework and AWS CDK to deploy a serverless application, using best of both worlds to improve consequently the overall developer experience.&lt;/p&gt;

&lt;p&gt;I strongly advise to structure and split your CDK resources in a dedicated project directory, in order to keep &lt;code&gt;serverless.ts&lt;/code&gt; as minimal as possible and to implement CDK recommendations in terms of construct architecture.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>cdk</category>
      <category>typescript</category>
      <category>aws</category>
    </item>
    <item>
      <title>Serverless can benefit from not being so Lambda-centric</title>
      <dc:creator>Frédéric Barthelet</dc:creator>
      <pubDate>Wed, 12 Jan 2022 12:59:30 +0000</pubDate>
      <link>https://dev.to/slsbytheodo/serverless-can-benefit-from-not-being-so-lambda-centric-1c4h</link>
      <guid>https://dev.to/slsbytheodo/serverless-can-benefit-from-not-being-so-lambda-centric-1c4h</guid>
      <description>&lt;p&gt;&lt;em&gt;Stop writing unnecessary code&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As a developer, I need to write code to develop specific features for a project. However, each line that I write is a potential futur bug and increases my project technical debt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Less code is better&lt;/strong&gt;. Serverless services have ways to enable you to remove more code than traditional architectures. The use of managed services reduces the amount of boiler plate code that does not directly adds value to your application while retaining the possibility to write full custom code where it's needed most.&lt;/p&gt;

&lt;p&gt;One can argue that full-fledged frameworks achieve the same result of reducing boilerplate code. However, it requires the development team to continuously update this code dependency to its latest revision and ship frequent dependency updates in production. With managed services, this burden is passed down to the cloud provider.&lt;/p&gt;

&lt;p&gt;Most software engineers relates serverless to its per-use ephemeral compute environment - also known as FaaS - (Lambda/ECS for AWS, Cloud Functions for GCP or Azure Functions for Azure), often missing on a large opportunity offered by all the other serverless services a cloud provider has to offer. &lt;strong&gt;Serverless real value is not within compute services&lt;/strong&gt;, but rather in side-car managed services, handling task usually handled by non-ephemeral compute environment.&lt;/p&gt;

&lt;p&gt;This article focus on missed opportunities to write less code and leverage managed serverless services, where those can handle efficiently and flawlessly a large number of request without the need to write or depend on a single runtime-executed line of code. I'll dive on a specific exemple, using AWS, to explain the use case and benefits of using non-compute services and actually have them interact in order to develop a specific feature - hereby named &lt;strong&gt;functionless&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you can do with AWS Lambda
&lt;/h2&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%2Fessvu6thzhdpbx8ostrg.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%2Fessvu6thzhdpbx8ostrg.png" alt="One Lambda to rule them all" width="800" height="528"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Lambda can be invoked from a variety of services, allowing the development of event-driven architectures. With the variety of triggers offered to request the execution of your code, the possibilities are endless. You can easily react to a change in your database, to a new file being upload on your bucket, to a new user being registered on your application.&lt;/p&gt;

&lt;p&gt;The versatility of writing your own code and execute it in reaction to those changes is however often in contrast with what I actually see people implementing in their code base.&lt;/p&gt;

&lt;p&gt;Countless times I've seen the magic triptych: API Gateway - Lambda - DynamoDB&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%2Fcpoihiofu0wptrdar9r8.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%2Fcpoihiofu0wptrdar9r8.png" alt="API Gateway - Lambda - DynamoDB triptych in action" width="800" height="218"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This triptych is often used to develop HTTP endpoints, be it a simple CRUD REST API used as a backend or an incoming webhook endpoint receiving notifications from 3rd parties.&lt;/p&gt;

&lt;p&gt;Why do we see this architecture so often ?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.serverless.com/" rel="noopener noreferrer"&gt;Serverless Framework&lt;/a&gt; is lambda-centric and encourages new adopters to trigger lambdas everywhere&lt;/li&gt;
&lt;li&gt;Imperative code is familiar to developer, while AWS services configuration is much less common knowledge&lt;/li&gt;
&lt;li&gt;The developer experience setting up an alternative architecture not relying on Lambda is considerably worse (you can jump to the end of this article for more information)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's take the exemple of writing-reading data to DynamoDB.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you should do with AWS Lambda
&lt;/h2&gt;

&lt;p&gt;In order to identify precisely what you should do within your Lambda handlers' code, it is important to understand what NOT to do. As with our previous exemple, almost everything can be done directly within API Gateway.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Authorization&lt;/strong&gt;: using a Cognito user pool or an API key&lt;br&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%2Fhie7r9jp5jqmyd69qzq0.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%2Fhie7r9jp5jqmyd69qzq0.png" alt="Authorization" width="800" height="372"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Input validation&lt;/strong&gt;: using built-in JSON schema validation configuration&lt;br&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%2Fu9ehjrjgx30tqr0rmbst.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%2Fu9ehjrjgx30tqr0rmbst.png" alt="Input validation" width="800" height="352"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Routing&lt;/strong&gt;: describing specific routes in API Gateway rather than use a &lt;em&gt;catch all {proxy+}&lt;/em&gt; integration&lt;br&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%2Fsoeaocpnprqgrdtrujwn.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%2Fsoeaocpnprqgrdtrujwn.png" alt="Routing" width="800" height="476"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Content transformation&lt;/strong&gt;: transform HTTP request content and map to the corresponding attributes as expected in the database layer&lt;br&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%2F6ilr7ii26nafyimnf23f.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%2F6ilr7ii26nafyimnf23f.png" alt="Content transformation" width="800" height="310"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At the end of the day, for such "simple" operations, you can almost always by-pass lambda altogether and rely on the following &lt;strong&gt;functionless architecture&lt;/strong&gt;.&lt;br&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%2Fejfsztmeshr3jw6n46oa.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%2Fejfsztmeshr3jw6n46oa.png" alt="Target functionless architecture for CRUD operations" width="800" height="710"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Why should I opt-out of Lambda for such use cases ?
&lt;/h2&gt;

&lt;p&gt;As I explained at the beginning of this article, writing less code has benefits of its own for a project code base in the long run. However, that's not the only advantages brought by Functionless.&lt;/p&gt;
&lt;h3&gt;
  
  
  💰 Cost
&lt;/h3&gt;

&lt;p&gt;API Gateway incurred cost in the Functionless architecture are always the same, whether you use it for validation, routing, authorization and content transformation or not.&lt;/p&gt;

&lt;p&gt;The cost repartition in the original triptych architecture are as follow (per million executions, in &lt;code&gt;us-east-1&lt;/code&gt; region):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;3,50$ for API Gateway&lt;/li&gt;
&lt;li&gt;0,70$ for Lambda (including invocation + 30ms warm execution)&lt;/li&gt;
&lt;li&gt;1,25$ for DynamoDB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Total: 6,45$ per million executions&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Removing Lambda from this architecture simply removes Lambda incurred costs from the bills, without increasing other services respective costs, actually &lt;strong&gt;cutting off our costs by 13%&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  🚀 Performance
&lt;/h3&gt;

&lt;p&gt;The serverless triptych stack &lt;em&gt;- in green on graph below -&lt;/em&gt; and functionless stack &lt;em&gt;- in blue on graph below -&lt;/em&gt; performs almost identically. The results shown below represents API Gateway latency (not taking networking latency into account) for both stacks with the same feature: a POST request with a payload containing a single attribute resulting with an item being persisted in DynamoDB.&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%2Frscd6xyxna0pu9ns777s.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%2Frscd6xyxna0pu9ns777s.png" alt="Comparaison of response time distribution over 300 requests for serverless triptych (in green) and functionless (in blue)" width="800" height="563"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The serverless triptych stack is much more consistent over time with almost all warm responses latency ranging between 30ms and 40ms.&lt;/p&gt;

&lt;p&gt;The functionless stack is less performant most of the time - around 50ms - but much faster on some occasions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;6% of the requests have a latency in the 10ms range&lt;/li&gt;
&lt;li&gt;16% in the 20ms range&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, in the event of a &lt;em&gt;cold&lt;/em&gt; serverless triptych stack, results are considerably worse.&lt;/p&gt;
&lt;h4&gt;
  
  
  Lambda's cold start issue
&lt;/h4&gt;

&lt;p&gt;Lambda is fast once it's &lt;em&gt;warm&lt;/em&gt;. The first request requiring compute from Lambda service is much slower than the consecutive requests due to cold start issues. Lambda actually requires to provision a dedicated environment to run your code. You can overcome this issue using provisioned Lambda instances, but what's the point of going serverless then? Provisioned instances significantly impacts the overall cost of the stack, making it much less competitive.&lt;/p&gt;

&lt;p&gt;In this instance, the serverless triptych stack averages at 600ms latency when Lambda is cold, which is 10x more than the warm latency.&lt;/p&gt;

&lt;p&gt;Regarding cost, while init duration is not included in billed duration, the overall cost of the execution is much larger due to a larger amount of time required to process the event. In practice, the average billed duration for cold lambda is 188ms, which represents 3,40$ per million execution. Removing this dependency to Lambda &lt;strong&gt;cuts off our costs by 42%&lt;/strong&gt; in worst case scenario (or best case scenario, depending on the way you look at it) - considering all invocations are cold invocations.&lt;/p&gt;
&lt;h3&gt;
  
  
  🗜 Throttling
&lt;/h3&gt;

&lt;p&gt;Using a functionless stack removes limitations from Lambda service:&lt;br&gt;
Lambda concurrent execution quota (1000 by default) limits the total quantity of lambda being invoked at some point in time in a single AWS account. While this quota can be increased, not using Lambda removes any worry related to concurrency limits. I'm not saying other services used in the functionless stack aren't limited as well: API Gateway has a 10.000 RPS throttling limit as well as a 5000 requests burst limit, while DynamoDB, in its on-demand provisioned capacity, has a 1000 WCU limit per second. You should pay special attention to those services configuration to handle larger RPS on your application. You just have one less service - Lambda - to worry about in a functionless stack.&lt;/p&gt;
&lt;h2&gt;
  
  
  How to deploy your first functionless feature ?
&lt;/h2&gt;

&lt;p&gt;Now it's time to address the elephant in the room: developer experience. My first interaction with AWS service direct integrations like API Gateway to DynamoDB was a disaster. I was guided in this experimentation following &lt;a href="https://aws.amazon.com/fr/blogs/compute/using-amazon-api-gateway-as-a-proxy-for-dynamodb/" rel="noopener noreferrer"&gt;Andrew Baird's guide on using API Gateway as a proxy to DynamoDB&lt;/a&gt; from 2016. If you haven't already face the challenge of correctly configuring such direct integration in API Gateway, I strongly suggest you try following this guide. You lose all benefits from using a more performant and less expensive stack if nobody from your team wants to touch API Gateway functionless configuration with a ten-foot pole.&lt;/p&gt;

&lt;p&gt;The process of implementing such functionless stack, as described in Andrew's article, will be used as our experience baseline. Moving forward, I'll detail an alternative that can be used to make the overall experience much more enjoyable and practicable.&lt;/p&gt;
&lt;h3&gt;
  
  
  Integration Uri
&lt;/h3&gt;

&lt;p&gt;In order to forward HTTP requests made to API Gateway to DynamoDB endpoint, you must setup an AWS service integration. The instructions to setup one using the web console requires knowledge on what to fill in the following fields.&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%2Ffvr8mfao7izaf2n9xkjk.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%2Ffvr8mfao7izaf2n9xkjk.png" alt="AWS service integration setup in web console" width="800" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Some of those fields can easily be guessed, like region and action, as they are commonly used in other IaC scenarios (like IAM role statements definition).&lt;/p&gt;

&lt;p&gt;Others require factual knowledge: all AWS services, including DynamoDB, expose an HTTP API that is used by AWS CLI, SDK, web console. All routes on those API use &lt;code&gt;POST&lt;/code&gt; http verb. While this could make sense for our exemple at hand here, calling &lt;code&gt;PutItem&lt;/code&gt; operation, it is much less obvious if what you're trying to do is a &lt;code&gt;GetItem&lt;/code&gt; or &lt;code&gt;Query&lt;/code&gt; operation and you're familiar with SaaS exposing RESTful APIs.&lt;/p&gt;

&lt;p&gt;The service you want to use is another one of those parameter that requires factual knowledge: while using &lt;code&gt;dynamodb&lt;/code&gt; for the DynamoDB integration is quite straight forward, some other services are less obvious, such as &lt;code&gt;events&lt;/code&gt; instead of &lt;code&gt;eventbridge&lt;/code&gt; for an EventBridge integration.&lt;/p&gt;

&lt;p&gt;The HTTP method - &lt;code&gt;POST&lt;/code&gt; - as well as the service to be used - &lt;code&gt;dynamodb&lt;/code&gt; - are used in combinaison with the region to form the resource Uri. While using the web console is a great first step in using direct AWS services integration feature, teams often rely on versioned IaC to provision an application, where such helper is not always available. In order to setup such integration using CloudFormation, you'd need to provision an &lt;code&gt;AWS::ApiGateway::Method&lt;/code&gt; resource, which requires you to provide the full integration Uri all by yourself. This Uri is a source of frustration for most developers I worked with as it greatly differs from one service to another. In addition, it requires deep understanding of &lt;a href="https://docs.aws.amazon.com/apigateway/api-reference/resource/integration/#uri" rel="noopener noreferrer"&gt;a 15-lines long documentation&lt;/a&gt; in order to build it correctly. The resulting CloudFormation templates ressembles something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;MyAPIGatewayMethod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::ApiGateway::Method&lt;/span&gt;
  &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Integration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;IntegrationHttpMethod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POST&lt;/span&gt;
      &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS&lt;/span&gt;
      &lt;span class="na"&gt;Uri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Fn::Join&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:"&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;arn"&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Partition&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;apigateway:eu-west-1:dynamodb:action/PutItem&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hopefully, the CDK provides a way to benefit back from the web console helper to build such Uri, using the &lt;a href="https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-apigateway.AwsIntegration.html" rel="noopener noreferrer"&gt;&lt;code&gt;AwsIntegration&lt;/code&gt; class&lt;/a&gt; you only need to provide &lt;code&gt;region&lt;/code&gt;, &lt;code&gt;action&lt;/code&gt;, &lt;code&gt;service&lt;/code&gt; to achieve the same provisioning.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;putItemIntegration&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;apigateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AwsIntegration&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;dynamodb&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PutItem&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eu-west-1&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;h3&gt;
  
  
  Integration mapping template
&lt;/h3&gt;

&lt;p&gt;Choosing a service and an action for API Gateway to forward request to is the first step at building an AWS service integration. The next step is to transform the payload of the HTTP request made to API Gateway into what the integrated AWS service expects.&lt;/p&gt;

&lt;p&gt;This is done using &lt;a href="https://velocity.apache.org/engine/devel/vtl-reference.html" rel="noopener noreferrer"&gt;Velocity Template Language&lt;/a&gt;. Here after is an exemple of such a template:&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;"TableName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MyTable"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Item"&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;"PK"&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;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Dog"&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;"SK"&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;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$context.requestId"&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;"name"&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;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$input.path('$.name')"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This template shapes the payload of the DynamoDB action. It includes API Gateway custom method to hydrate &lt;code&gt;SK&lt;/code&gt; and &lt;code&gt;name&lt;/code&gt; attribute's value:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;$context.requestId&lt;/code&gt; uses a &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html#context-variable-reference" rel="noopener noreferrer"&gt;context variable&lt;/a&gt; to generate a unique id for the PutItem action&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;$input.path('$.name')&lt;/code&gt; uses an &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html#input-variable-reference" rel="noopener noreferrer"&gt;input variable&lt;/a&gt; to retrieve a specific value from the HTTP body of the request made to API Gateway&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Relying on VTL is cumbersome: it requires additional knowledge of the syntax and it is not easily testable (some librairies gave it a go like &lt;a href="https://www.npmjs.com/package/api-gateway-template-tester" rel="noopener noreferrer"&gt;API Gateway Template Tester&lt;/a&gt; but are currently unmaintained).&lt;/p&gt;

&lt;h3&gt;
  
  
  Easier integration with aws-apigateway-integrations CDK construct
&lt;/h3&gt;

&lt;p&gt;One way to improve this is to allow the use of imperative language and tools people are used to in the &lt;em&gt;triptych serverless stack&lt;/em&gt; to generate such template. With this goal in mind, I started a CDK Construct library, called &lt;a href="https://github.com/fredericbarthelet/aws-apigateway-integrations" rel="noopener noreferrer"&gt;aws-apigateway-integrations&lt;/a&gt;, aiming at simplifying AWS service integration definition, following &lt;a href="https://docs.aws.amazon.com/cdk/api/v1/docs/aws-apigatewayv2-integrations-readme.html" rel="noopener noreferrer"&gt;@aws-cdk/aws-apigatewayv2-integrations module&lt;/a&gt; footsteps.&lt;/p&gt;

&lt;p&gt;Using the new constructs shipped with this library, you can rely on familiar &lt;a href="https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-dynamodb/index.html" rel="noopener noreferrer"&gt;AWS SDK v3 DynamoDB Commands interfaces&lt;/a&gt; in order to build your VTL, simplifying previous template down to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PutCommandInput&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-sdk/lib-dynamodb&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;DynamoDBActions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;DynamoDBIntegration&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aws-apigateway-integrations&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;putItemCommand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Omit&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;PutCommandInput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TableName&lt;/span&gt;&lt;span class="dl"&gt;"&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="na"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;PK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Dog&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;SK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$context.requestId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$input.path('$.name')&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;putItemIntegration&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;DynamoDBIntegration&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DynamoDBActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PutItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;putItemCommand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;//...&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Integration response and method response
&lt;/h3&gt;

&lt;p&gt;Request authorization, validation and transformation is only half of the functionless architecture. Both integration response, handling content transformation from DynamoDB HTTP response and method response, detailing API Gateway response typologies, must be provisioned for the overall architecture to work properly.&lt;/p&gt;

&lt;p&gt;Integration response similarly relies on VTL to unmarshall content returned by DynamoDB.&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;"$input.path('$.Item.SK.S')"&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;"$input.path('$.Item.name.S')"&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;Such VTL can easily be generated using &lt;code&gt;DynamoDBIntegration&lt;/code&gt;, removing the burden from actually defining it yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next in the functionless ecosystem ?
&lt;/h2&gt;

&lt;p&gt;While &lt;code&gt;aws-apigateway-integrations&lt;/code&gt; is currently a work in progress, I have great hope such integration would greatly facilitate adoption of functionless patterns by greatly improving the overall developer experience. With this in mind, the current development roadmap for this CDK Constructs library includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;adding JSON schema generation and configuration for API Gateway validation within DynamoDBIntegration&lt;/li&gt;
&lt;li&gt;adding IAM role generation within the scope of the construct to allow API Gateway to execute DynamoDB actions&lt;/li&gt;
&lt;li&gt;providing better API to wrap &lt;code&gt;$input&lt;/code&gt; variable&lt;/li&gt;
&lt;li&gt;implementing other AWS services integration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All feedbacks or even contributions are welcome !&lt;/p&gt;

&lt;p&gt;Such implementation opens up a lot of new possibilities: a functionless RESTful API where the only code is configuration-only IaC is not out of reach anymore and can help team achieve cheaper, more robust code base with minimal efforts. Such implementation is currently under discussion within &lt;a href="https://github.com/getlift/lift/discussions/127" rel="noopener noreferrer"&gt;Lift project&lt;/a&gt; where your feedbacks on the implementation are highly valued.&lt;/p&gt;

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