<?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: Mahmut Canga</title>
    <description>The latest articles on DEV Community by Mahmut Canga (@mahmutcanga).</description>
    <link>https://dev.to/mahmutcanga</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%2F160934%2F4479cd27-b7bf-4a59-9c03-e059fbf39a04.jpg</url>
      <title>DEV Community: Mahmut Canga</title>
      <link>https://dev.to/mahmutcanga</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mahmutcanga"/>
    <language>en</language>
    <item>
      <title>Value Created by Testing Infrastructure Code</title>
      <dc:creator>Mahmut Canga</dc:creator>
      <pubDate>Sun, 30 Jan 2022 15:42:24 +0000</pubDate>
      <link>https://dev.to/mahmutcanga/value-created-by-testing-infrastructure-code-2lkp</link>
      <guid>https://dev.to/mahmutcanga/value-created-by-testing-infrastructure-code-2lkp</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2KEJO5jH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sufidev.files.wordpress.com/2022/01/image-1.png%3Fw%3D1024" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2KEJO5jH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sufidev.files.wordpress.com/2022/01/image-1.png%3Fw%3D1024" alt="" width="880" height="358"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The simplest answer is &lt;strong&gt;Customer Trust&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;As you can see from above screenshot, I’m able to get a quick feedback, as fast as 5 seconds, from my Infrastructure Code, &lt;a href="https://aws.amazon.com/cdk/"&gt;CDK Application&lt;/a&gt; if I’m going to be earning more Customer Trust or not after the deployment…&lt;/p&gt;

&lt;p&gt;In this tiny setup, I have an important &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/WorkingWithTables.html"&gt;DynamoDB Table&lt;/a&gt; that stores important Customer data. This data needs to be available all the time and if I get higher demand, I should be able to quickly scale up the table at 80% utilisation. All of these are possible with DynamoDB but none of this could work if you don’t invest on it. The investment can be manually configuring these on AWS Console or even worse, with no investment, you can configure these when your system slows down or you get customer complaints. Of course, no business prefers their customers complaining about their service. So, you would be able to proactively handle it by investing in automation and Infrastructure as Code using CDK.&lt;/p&gt;

&lt;p&gt;However, even in this setup, you may face issues. When you would like to go faster, errors could happen and you would like to get a fast feedback instead of long and painful customer feedback in production. In order to achieve this, TDD and CI/CD are used reasonably and responsibly. CDK in this context is no foreigner to these concepts and provides great tooling and support to test your Infrastructure as you create it via unit tests.&lt;/p&gt;

&lt;p&gt;The biggest value of these tests is being able to reflect Operational Excellence (OpEx) concerns as early as at the unit test stage. By writing unit tests according to your OpEx concerns in your CDK application, it will provide a higher confidence when running the service in production. This confidence will yield more Customer trust as you will be able to reliably run a service and fortunately find out issues before Customer does.&lt;/p&gt;

&lt;p&gt;In the above example screenshot, my OpEx concerns are well covered with unit tests. Now, every time I deploy my application, I can simply run &lt;code&gt;npm run test&lt;/code&gt; on my infrastructure code and make sure that there is no drift in my OpEx concern in production. You can take this to another level by scanning existing repositories in your organisation and alert service owners if company wide OpEx concerns are not covered at code level using some Bots. Also, these metrics could be extremely useful to reflect on service dashboards.&lt;/p&gt;

&lt;p&gt;Speaking of these OpEx concerns, I can easily say almost 90%+ incidents I was part of in the past had an action item or two about alerting, monitoring and more observability as a result of the incident report. These now can be part of the code and forever guard the concerns. Also, it would be much easier to close the loop on an incident report by simply reflecting these on a dashboard and sharing the code change (PR/MR/CR whatever your organisation refers to) into the report. Long term learnings of an incident from OpEx point of view can be internalised and automated with this simple methodology.&lt;/p&gt;

&lt;p&gt;While talking about the business value of this, let’s look at the code and connect the dots. Next section quickly summarise the code setup and hopefully will influence you write more tests for your infrastructure code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sample Setup
&lt;/h2&gt;

&lt;p&gt;I have a very simple CDK application stack with 2 &lt;a href="https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.NestedStack.html"&gt;NestedStack&lt;/a&gt; constructs. One stack is for Monitoring (alert topic, sns, dashboards) and the other one is the Database. I also use &lt;a href="https://docs.aws.amazon.com/cdk/v2/guide/migrating-v2.html"&gt;CDK v2&lt;/a&gt; for these sample codes.&lt;/p&gt;

&lt;p&gt;From AWS Docs, NestedStacks are:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The &lt;a href="https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.NestedStack.html"&gt;NestedStack&lt;/a&gt; construct offers a way around the AWS CloudFormation 500-resource limit for stacks. A nested stack counts as only one resource in the stack that contains it, but can itself contain up to 500 resources, including additional nested stacks.&lt;/p&gt;

&lt;p&gt;The scope of a nested stack must be a &lt;code&gt;Stack&lt;/code&gt; or &lt;code&gt;NestedStack&lt;/code&gt; construct. The nested stack needn’t be declared lexically inside its parent stack; it is necessary only to pass the parent stack as the first parameter (&lt;code&gt;scope&lt;/code&gt;) when instantiating the nested stack. Aside from this restriction, defining constructs in a nested stack works exactly the same as in an ordinary stack.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Monitoring Stack
&lt;/h3&gt;

&lt;p&gt;Below creates a dedicated &lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Dashboards.html"&gt;CloudWatch Dashboard&lt;/a&gt; and exports the dashboard to be used in other stacks. This way, you will be able to put all related metrics and alerts of your service into a central dashboard.&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt; &lt;span class="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;Construct&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;constructs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;cloudwatch&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-cloudwatch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;sns&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-sns&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;subscriptions&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-sns-subscriptions&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;MonitoringStack&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NestedStack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;dashboard&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cloudwatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Dashboard&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;alarmTopic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Topic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Construct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MonitoringStackProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dashboard&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;cloudwatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Dashboard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{});&lt;/span&gt;

        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;alarmTopic&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;sns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Topic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AlarmTopic&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;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;My Service Alarms&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;emailSubscription&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;subscriptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EmailSubscription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dev@mycompany.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;alarmTopic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addSubscription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;emailSubscription&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Database Stack
&lt;/h3&gt;

&lt;p&gt;Below creates a &lt;a href="https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_dynamodb-readme.html"&gt;DynamoDB Table&lt;/a&gt;, sets Read and Write capacity to &lt;code&gt;5&lt;/code&gt; while enabling &lt;code&gt;point in time recovery&lt;/code&gt;. It also enables to auto-scale to &lt;strong&gt;5x&lt;/strong&gt; of the table capacity at &lt;strong&gt;80%&lt;/strong&gt; utilisation. It also adds Read and Write capacity alarms and metrics on CloudWatch Dashboard created in Monitoring Stack.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt; &lt;span class="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;Construct&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;constructs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;dynamodb&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="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;cloudwatch&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-cloudwatch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;sns&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-sns&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;cloudwatchActions&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-cloudwatch-actions&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;DatabaseStackProps&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NestedStackProps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;dashboard&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cloudwatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Dashboard&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;alarmTopic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Topic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;DatabaseStack&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NestedStack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;customerOrdersTable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Construct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DatabaseStackProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customerOrdersTable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setupTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CustomerOrdersTable&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;setupTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Construct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DatabaseStackProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;dynamodb&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="c1"&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="nx"&gt;dynamodb&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;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="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;customerId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AttributeType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STRING&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;sortKey&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;orderId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AttributeType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STRING&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;billingMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BillingMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PROVISIONED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;readCapacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;writeCapacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;encryption&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TableEncryption&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DEFAULT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;pointInTimeRecovery&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StreamViewType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEW_AND_OLD_IMAGES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setupAutoScaling&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setupDashboard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&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;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;setupAutoScaling&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Construct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Table&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;tableReadScaling&lt;/span&gt; &lt;span class="o"&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;autoScaleReadCapacity&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;minCapacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;maxCapacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;tableReadScaling&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scaleOnUtilization&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;targetUtilizationPercent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;80&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;tableWriteScaling&lt;/span&gt; &lt;span class="o"&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;autoScaleWriteCapacity&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;minCapacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;maxCapacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;tableWriteScaling&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scaleOnUtilization&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;targetUtilizationPercent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;



  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;setupDashboard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Construct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DatabaseStackProps&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;dynamodb&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;readCapacityAlarm&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;cloudwatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Alarm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;ReadCapacity`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;metric&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metricConsumedReadCapacityUnits&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;period&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;statistic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sum&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;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;alarmDescription&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; Table Read Capacity`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;evaluationPeriods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;readCapacityAlarm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addAlarmAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cloudwatchActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SnsAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;alarmTopic&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;readCapacityAlarmWidget&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;cloudwatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AlarmWidget&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; Table Read Capacity`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;alarm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;readCapacityAlarm&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;writeCapacityAlarm&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;cloudwatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Alarm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;WriteCapacity`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;metric&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metricConsumedWriteCapacityUnits&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;period&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;statistic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sum&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;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;alarmDescription&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; Table Write Capacity`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;evaluationPeriods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;writeCapacityAlarm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addAlarmAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cloudwatchActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SnsAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;alarmTopic&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;writeCapacityAlarmWidget&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;cloudwatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AlarmWidget&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; Table Write Capacity`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;alarm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;writeCapacityAlarm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dashboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addWidgets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;readCapacityAlarmWidget&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;writeCapacityAlarmWidget&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Now that my stack is setup, this is how I test them. I didn’t share the full test suite but the below is showing the important OpEx concerns under cover.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing Database OpEx Concerns
&lt;/h3&gt;

&lt;p&gt;In this simple &lt;a href="https://jestjs.io"&gt;Jest&lt;/a&gt; setup, I’m creating a new test stack and add the stack under test to it. By generating a &lt;a href="https://aws.amazon.com/cloudformation/resources/templates/"&gt;CloudFormation Template&lt;/a&gt;, I’m able to unit test if my CDK setup is covering my concerns or not.&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt; &lt;span class="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;Template&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/assertions&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;MonitoringStack&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;../lib/stacks/monitoring-stack&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;DatabaseStack&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;../lib/stacks/database-stack&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;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="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Test Database Stack&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Template&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nx"&gt;beforeEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;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;testStack&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;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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;TestStack&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;monitoringStack&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;MonitoringStack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;testStack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;TestMonitoringStack&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{});&lt;/span&gt;

        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;DatabaseStack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;testStack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;TestDatabaseStack&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;dashboard&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;monitoringStack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dashboard&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;alarmTopic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;monitoringStack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;alarmTopic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fromStack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;testStack&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;afterEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Tables have point in time recovery&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;hasResourceProperties&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::DynamoDB::Table&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;PointInTimeRecoverySpecification&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;PointInTimeRecoveryEnabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="na"&gt;ProvisionedThroughput&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;ReadCapacityUnits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;WriteCapacityUnits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Tables have READ capacity alarms&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;hasResourceProperties&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::CloudWatch::Alarm&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;MetricName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ConsumedReadCapacityUnits&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;Namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AWS/DynamoDB&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Tables have WRITE capacity alarms&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;hasResourceProperties&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::CloudWatch::Alarm&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;MetricName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ConsumedWriteCapacityUnits&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;Namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AWS/DynamoDB&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Tables have READ capacity scalable target&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;hasResourceProperties&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::ApplicationAutoScaling::ScalableTarget&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;ScalableDimension&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:table:ReadCapacityUnits&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;ServiceNamespace&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="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Tables have WRITE capacity scalable target&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;hasResourceProperties&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::ApplicationAutoScaling::ScalableTarget&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;ScalableDimension&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:table:WriteCapacityUnits&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;ServiceNamespace&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="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Tables have READ auto-scaling policy at 80% utilization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;hasResourceProperties&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::ApplicationAutoScaling::ScalingPolicy&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;TargetTrackingScalingPolicyConfiguration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;PredefinedMetricSpecification&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="na"&gt;PredefinedMetricType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DynamoDBReadCapacityUtilization&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;TargetValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Tables have WRITE auto-scaling policy at 80% utilization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;hasResourceProperties&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::ApplicationAutoScaling::ScalingPolicy&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;TargetTrackingScalingPolicyConfiguration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;PredefinedMetricSpecification&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="na"&gt;PredefinedMetricType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DynamoDBWriteCapacityUtilization&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;TargetValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Closing Notes
&lt;/h2&gt;

&lt;p&gt;I hope the above sample codes and overall context could influence you write more unit tests to your infrastructure. To my knowledge, Terraform doesn’t support such setup or even if it is supported, I couldn’t find the productivity and tooling support in my recent attempts like running a simple &lt;code&gt;npm run test&lt;/code&gt; in WebStorm or VSCode. This is why I believe CDK is a game changer for developer confidence and productivity when it comes to building and running services that delight customers on AWS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where to Next?
&lt;/h3&gt;

&lt;p&gt;For further details on why OpEx is important, you can dive into AWS Well Architected white paper.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/architecture/well-architected"&gt;AWS Well Architected&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also, there is great tutorials on CDK documentation to get started:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html"&gt;CDK Getting Started&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CDK documentation itself teaches how to use services in great detail&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_dynamodb-readme.html"&gt;DynamoDB Module – CDK&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>cdk</category>
      <category>devops</category>
    </item>
    <item>
      <title>To 5XX or Not to 5XX?</title>
      <dc:creator>Mahmut Canga</dc:creator>
      <pubDate>Thu, 30 Dec 2021 07:48:38 +0000</pubDate>
      <link>https://dev.to/mahmutcanga/to-5xx-or-not-to-5xx-3bb8</link>
      <guid>https://dev.to/mahmutcanga/to-5xx-or-not-to-5xx-3bb8</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xdv9b_w---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sufidev.files.wordpress.com/2021/12/the-blowup-un4paddppau-unsplash.jpg%3Fw%3D1024" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xdv9b_w---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sufidev.files.wordpress.com/2021/12/the-blowup-un4paddppau-unsplash.jpg%3Fw%3D1024" alt="" width="880" height="561"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Speaking of &lt;code&gt;5XX&lt;/code&gt; errors, I have a strong opinion from a good API design perspective. I developed these opinions by working at both client level and at server level in different projects or even at the same project. Also, seeing how HTTP Clients of popular frameworks in NodeJS, Javascript, Java and even .Net, &lt;code&gt;5XX&lt;/code&gt; is something we should never return in a programmatic way. This is maybe in the realm of checked vs unchecked exceptions but I’m more at the good API design realm.&lt;/p&gt;

&lt;h2&gt;
  
  
  Perceptions
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Error Code&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;API&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Client&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2XX&lt;/td&gt;
&lt;td&gt;I have handled all you requested and here is the result of this operation&lt;/td&gt;
&lt;td&gt;Show the result of the requested operation, happy days!&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4XX&lt;/td&gt;
&lt;td&gt;I have handled an exception in my API and I know what it is, here is a bad request response. I might fail fast and return you this bad request response in milliseconds and may not even try any downstream services at all to avoid downfall spiral…&lt;/td&gt;
&lt;td&gt;I know I sent a valid request but for some reason(s) API didn’t process my request. I’ll go retry or show an updated result (error screen, retry with new input etc). However, I’ll need to make it harder every time so I will leave some space to API to recover. I’ll also note down this in case needed for future resolutions…&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5XX&lt;/td&gt;
&lt;td&gt;There is something that stops my program flow and it is an exceptional case, my engineers need to look at this and later on either fix this issue or catch it and give a meaningful result (4XX) to my clients if this exceptional case is an expected outcome due to service design… Or there is some big meltdown happening and my API is not even receiving anything about it as the infrastructure or whole service is down…&lt;/td&gt;
&lt;td&gt;Something exceptional happening in the API. API doesn’t know what it is, I’m not even sure if my request reached to API, I’ll not retry but rather show something to user that there is something going on that engineers are fixing. I’ll note this down but will not retry after a meaningful time or budget. It is not great but everything fails all the time…&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Leaky Abstractions is your enemy!
&lt;/h2&gt;

&lt;p&gt;In distributed systems, everything fails all the time. Good API design captures failures and handles them gracefully. Returning &lt;code&gt;5XX&lt;/code&gt; to clients will create a false perception. It will also leak the abstractions. Knowing that APIs are here forever, leaking an abstraction will create a dependency on clients that will be just a tech debt from day 1.&lt;/p&gt;

&lt;p&gt;To explain this further, knowing that my DB fails time to time (hardware ceiling, SLAs, downstream issues, tech debt etc.) and if I return &lt;code&gt;5XX&lt;/code&gt; in those occasions to my consumers, the consumer of my API will accept this as a design decision, assuming how this API works in certain way (leaking the abstraction). Client may implement some logic that every time &lt;code&gt;5XX&lt;/code&gt; means API might have failed and retry the request. However, while the system is down, sending more traffic will only create further bigger issues. Returning &lt;code&gt;5XX&lt;/code&gt; will not only create a leaky abstraction but also it will always escalate the system issues to further levels that is hard to recover. If API would actually design the failure gracefully and return &lt;code&gt;4XX&lt;/code&gt;, it could create an abstract system that could put these requests in an expiring queue (i.e. expire the requests in the next 5 mins) so that even if the client keeps trying, I’ll have a way to catch up. From Client perspective, it is abstract, reliable and predictable API behaviour. Also, because API is able to handle DB failure and can put the upcoming requests in a queue while DB is recovering, it will be performant and fast too…&lt;/p&gt;

&lt;h2&gt;
  
  
  Observing Failures
&lt;/h2&gt;

&lt;p&gt;In this sense, clearly, If an API is up &amp;amp; running but only a dependency is failing, it is &lt;code&gt;4XX&lt;/code&gt;. &lt;code&gt;5XX&lt;/code&gt; is when the API is broken for a reason that we cannot capture the details at all. If we cannot write to db due to DynamoDB service is down, it is &lt;code&gt;4XX&lt;/code&gt; because as an API, I know what part of my dependency is failing and I know how to catchup. (put failed requests to SQS and retry later on etc). If API Gateway is down, without that request is ever reaching to my API, I know AWS will throw &lt;code&gt;5XX&lt;/code&gt;. If Lambda service is not able to run my function code because I have a defect in my code pipeline due to wrong compilation, missing a package in dependencies, wrong packaging etc, I know Lambda will throw &lt;code&gt;5XX&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;As engineer, in above cases, you will immediately know that there is a &lt;code&gt;5XX&lt;/code&gt; going on in my system and I’ll know where to look up. Otherwise, for every &lt;code&gt;5XX&lt;/code&gt;, I’ll need to search through logs to see if &lt;code&gt;5XX&lt;/code&gt; is an API response or an exception in the distributed system or at AWS level…&lt;/p&gt;

&lt;p&gt;This perspective will also lead meaningful alerts and observability. Alerts will be thrown at the right level and in exceptional cases. Otherwise, returning &lt;code&gt;5XX&lt;/code&gt; programmatically will create a complex benchmark data for observability and alerting. Deciding when there is more than 0 &lt;code&gt;5XX&lt;/code&gt; is much more cleaner operational action than constantly tweaking &lt;code&gt;5XX&lt;/code&gt; levels in my monitoring system and dashboards. As an engineer, I need to look at under the hood only when there is a real issue, &lt;code&gt;5XX&lt;/code&gt; and not when an arbitrary programming decision in the control flow throws &lt;code&gt;5XX&lt;/code&gt; like when a field is missing in the request and API returning &lt;code&gt;5XX&lt;/code&gt; 🤦🏻‍♂️&lt;/p&gt;

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

&lt;p&gt;To conclude this opinion piece, &lt;code&gt;5XX&lt;/code&gt; is something we should see in rare times as exceptional issues and fixed at infrastructure, system design or wider distributed system levels. Anything above should be handled by the program flow and should be &lt;code&gt;4XX&lt;/code&gt; with a meaningful message to API consumers. Failing fast, gracefully handling failure, having a plan to recover from dependency failures are hard things at API design but when invested early and carefully planned as part of the implementation, it will earn trust, help running the API more reliable and will lead happier engineers &amp;amp; operations.&lt;/p&gt;

</description>
      <category>4xx</category>
      <category>5xx</category>
      <category>apidesign</category>
    </item>
    <item>
      <title>Running NextJS SSR Apps on AWS</title>
      <dc:creator>Mahmut Canga</dc:creator>
      <pubDate>Wed, 22 Dec 2021 14:50:23 +0000</pubDate>
      <link>https://dev.to/mahmutcanga/running-nextjs-ssr-apps-on-aws-3mb3</link>
      <guid>https://dev.to/mahmutcanga/running-nextjs-ssr-apps-on-aws-3mb3</guid>
      <description>&lt;p&gt;At the time of writing this article, AWS is offering multiple options to run a NextJS SSR app. In the scale of self managed to AWS Managed, here are the options:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsufidev.files.wordpress.com%2F2021%2F12%2Funtitled-diagram-1.jpg%3Fw%3D1024" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsufidev.files.wordpress.com%2F2021%2F12%2Funtitled-diagram-1.jpg%3Fw%3D1024"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Out of these options, the best developer experience related AWS Managed offerings are &lt;a href="https://docs.aws.amazon.com/amplify/latest/userguide/server-side-rendering-amplify.html" rel="noopener noreferrer"&gt;Amplify Hosting&lt;/a&gt; and &lt;a href="https://aws.amazon.com/apprunner/" rel="noopener noreferrer"&gt;App Runner&lt;/a&gt;. They come with all the low level details handled by AWS in such a way that all you need is deploying via CI/CD pipeline and the rest is assured (&lt;em&gt;scalability, logs, custom domains, auto-renewal of certificates etc.&lt;/em&gt;)&lt;/p&gt;

&lt;p&gt;I have tried both Amplify Hosting and App Runner for a NextJS SSR app and ended up using and liking App Runner more. I’m hoping the context provided in this post can be useful to others too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Constraints and Prerequisites of Amplify Hosting
&lt;/h2&gt;

&lt;p&gt;Although Amplify Hosting does a great job for running SPAs that doesn’t require server side rendering, when it comes to SSR, the constraints and prerequisites are important to highlight.&lt;/p&gt;

&lt;p&gt;First of all, in order to run a SSR app on Amplify Hosting as of writing this article, you need to connect a source code for &lt;a href="https://docs.amplify.aws/start/getting-started/hosting/q/integration/next/#set-up-cicd-for-your-app" rel="noopener noreferrer"&gt;Next.js apps can only be deployed with Continuous deployment. AWS currently do not support manual deployments of Next.js apps.&lt;/a&gt; This creates a huge challenge if your code is deployed to multiple environments from a single main branch. Because all environments in Amplify Hosting is required to listen to the same main branch and before any approval or testing, your code can go to production at the same time it goes to sandbox or dev environment. One workaround to this could be using a branch per environment but this conflicts with the idea of &lt;a href="https://continuousdelivery.com" rel="noopener noreferrer"&gt;Continuous Delivery&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Second, you need to setup the build definition in a yaml file that requires changing how you export your NextJS SSR. I found this pretty confusing.&lt;/p&gt;

&lt;p&gt;By following the below changes in your build step, you are exposed to issues like not being able to use some of the NextJS features. &lt;a href="https://docs.amplify.aws/guides/hosting/nextjs/q/platform/js/#deploy-and-host-an-ssg-only-app" rel="noopener noreferrer"&gt;Currently, Amplify doesn’t fully support Image Component and Automatic Image Optimization available in Next.js 10. To manually deploy the &lt;code&gt;next-app&lt;/code&gt; example, you must edit the  &lt;strong&gt;index.js&lt;/strong&gt;  file to remove this feature.&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;scripts&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dev&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next dev&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;build&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next build &amp;amp;&amp;amp; next export&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;start&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next start&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Third, &lt;a href="https://docs.aws.amazon.com/amplify/latest/userguide/server-side-rendering-amplify.html#ssr-Amplify-support" rel="noopener noreferrer"&gt;as documented in AWS Docs&lt;/a&gt;, Amplify Hosting builds an S3 bucket, a CloudFront distribution and a Lambda@Edge stack in order to host SSR app. This may sound too many moving parts for a single frontend app while it is promising for a frontend app to have a global availability via CDN. The most interesting finding in this step was &lt;a href="mailto:Lambda@Edge"&gt;Lambda@Edge&lt;/a&gt;. As far as I understand, Amplify Hosting deploys some logic at Lambda@Edge with a very constraint timeout. In some Amplify Console Github issues, I have seen that Lambda@Edge constraints certain workloads in terms of quickly timing out. Also, &lt;a href="https://github.com/aws-amplify/amplify-console/blob/main/FAQ.md#access-lambda-edge-logs" rel="noopener noreferrer"&gt;due to how Lambda@Edge works&lt;/a&gt;, you will require deploying your app to us-east-1 where CloudFront and Lambda@Edge is centrally managed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdocs.amplify.aws%2Fimages%2Fstart-nextjs-deploy-3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdocs.amplify.aws%2Fimages%2Fstart-nextjs-deploy-3.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Last, when you choose Amplify Hosting, you are getting into an usual workflow. On top of your existing CI/CD pipelines, you are pushing your code to Amplify’s CD pipeline which is only visible via AWS Console. This means, you won’t have any realtime visibility in your existing CI/CD pipelines and rather check the progress of deployment from AWS Amplify Console. I’m not sure if there are any workarounds to this but having multiple disconnected tools for a CD pipeline is not a great developer experience. Also, from security point of view, it is tricky to allow all frontend developers touching the NextJS SSR app accessing the AWS Console. Even these frontend developers may not be familiar with the whole AWS Console experience so it is both a security concern and a learning curve issue.&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS App Runner just works!
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/apprunner/" rel="noopener noreferrer"&gt;AWS App Runner&lt;/a&gt; is a fully managed service that makes it easy for developers to quickly deploy containerized web applications and APIs, at scale and with no prior infrastructure experience required.&lt;/p&gt;

&lt;p&gt;This gives a huge advantage building a CI/CD pipeline for a NextJS SSR app using existing tools and a lot of flexibility on how you do it. To achieve this, all you need is couple of lines of CDK code and a docker file.&lt;/p&gt;

&lt;p&gt;The following creates an App Runner app that auto-scales to 25 instances and scales down to 1 when there isn’t any HTTP requests to your system. It is pretty much how you deploy a container to Fargate without dealing with Route53, ALB, VPC, Security Groups etc. All of these lower level constructs are managed by AWS.&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-cdk/core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NestedStack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NestedStackProps&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-cdk/core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;apprunner&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-cdk/aws-apprunner&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ecrAssets&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-cdk/aws-ecr-assets&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

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

        &lt;span class="cm"&gt;/**
         * Create a docker image and push to ECR
         */&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;imageAsset&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;ecrAssets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DockerImageAsset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WebAppImage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../src&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="cm"&gt;/**
         * Deploy to App Runner from the docker image pushed to ECR
         */&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;service&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;apprunner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Service&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WebApp&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;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;apprunner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromAsset&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                &lt;span class="na"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;imageAsset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;}),&lt;/span&gt;
            &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;apprunner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Cpu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TWO_VCPU&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;apprunner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Memory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FOUR_GB&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;There are couple things to note in this setup.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The minimum CPU that requires a responsive and performance NextJS SSR app is &lt;code&gt;apprunner.Cpu.TWO_VCPU&lt;/code&gt;. Otherwise, if you have an API connecting to a downstream service and receiving data, it quickly times out and gives &lt;code&gt;500 Server Error&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The minimum Memory is unfortunately needed to be the maximum App Runner supports, &lt;code&gt;apprunner.Memory.FOUR_GB&lt;/code&gt;. This is again to make sure the &lt;code&gt;node&lt;/code&gt; server has enough head room to process.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The next step is basically just dockerizing your NextJS SSR. For this, NextJS already provides a straightforward Docker file. All you need is putting this on your &lt;code&gt;src&lt;/code&gt; folder. The rest is handled by CDK auto-magically.&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="c1"&gt;# Install dependencies only when needed&lt;/span&gt;
&lt;span class="s"&gt;FROM node:14-alpine AS deps&lt;/span&gt;
&lt;span class="c1"&gt;# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.&lt;/span&gt;
&lt;span class="s"&gt;RUN apk add --no-cache libc6-compat&lt;/span&gt;
&lt;span class="s"&gt;WORKDIR /app&lt;/span&gt;
&lt;span class="s"&gt;COPY package.json ./&lt;/span&gt;
&lt;span class="s"&gt;RUN npm install&lt;/span&gt;

&lt;span class="c1"&gt;# Rebuild the source code only when needed&lt;/span&gt;
&lt;span class="s"&gt;FROM node:14-alpine AS builder&lt;/span&gt;
&lt;span class="s"&gt;WORKDIR /app&lt;/span&gt;
&lt;span class="s"&gt;COPY . .&lt;/span&gt;
&lt;span class="s"&gt;COPY --from=deps /app/node_modules ./node_modules&lt;/span&gt;
&lt;span class="s"&gt;RUN npm run build&lt;/span&gt;

&lt;span class="c1"&gt;# Production image, copy all the files and run next&lt;/span&gt;
&lt;span class="s"&gt;FROM node:14-alpine AS runner&lt;/span&gt;
&lt;span class="s"&gt;WORKDIR /app&lt;/span&gt;

&lt;span class="s"&gt;ENV NODE_ENV production&lt;/span&gt;

&lt;span class="s"&gt;RUN addgroup -g 1001 -S nodejs&lt;/span&gt;
&lt;span class="s"&gt;RUN adduser -S nextjs -u &lt;/span&gt;&lt;span class="m"&gt;1001&lt;/span&gt;

&lt;span class="c1"&gt;# You only need to copy next.config.js if you are NOT using the default configuration&lt;/span&gt;
&lt;span class="s"&gt;COPY --from=builder /app/next.config.js ./&lt;/span&gt;
&lt;span class="s"&gt;COPY --from=builder /app/public ./public&lt;/span&gt;
&lt;span class="s"&gt;COPY --from=builder --chown=nextjs:nodejs /app/.next ./.next&lt;/span&gt;
&lt;span class="s"&gt;COPY --from=builder /app/node_modules ./node_modules&lt;/span&gt;
&lt;span class="s"&gt;COPY --from=builder /app/package.json ./package.json&lt;/span&gt;
&lt;span class="s"&gt;COPY --from=builder /app/.env ./&lt;/span&gt;

&lt;span class="s"&gt;USER nextjs&lt;/span&gt;

&lt;span class="s"&gt;EXPOSE &lt;/span&gt;&lt;span class="m"&gt;3000&lt;/span&gt;

&lt;span class="s"&gt;ENV PORT &lt;/span&gt;&lt;span class="m"&gt;3000&lt;/span&gt;

&lt;span class="c1"&gt;# Next.js collects completely anonymous telemetry data about general usage.&lt;/span&gt;
&lt;span class="c1"&gt;# Learn more here: https://nextjs.org/telemetry&lt;/span&gt;
&lt;span class="c1"&gt;# Uncomment the following line in case you want to disable telemetry.&lt;/span&gt;
&lt;span class="s"&gt;ENV NEXT_TELEMETRY_DISABLED &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;

&lt;span class="s"&gt;CMD ["node_modules/.bin/next", "start"]&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;This really is an uncompromising build process to get the best out of your NextJS build.&lt;/p&gt;

&lt;p&gt;When you need to deploy this stack, all you need to is following in your CI/CD pipeline.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cdk bootstrap
cdk deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Some other benefits of App Runner
&lt;/h2&gt;

&lt;p&gt;Following are other good parts of App Runner worths mentioning.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom Domains and Auto Renewed HTTPS Certificate
&lt;/h3&gt;

&lt;p&gt;You can link a custom domain and within a couple of minutes your NextJS SSR app can be accessed via HTTPS only. For this to really work, as of writing this article, you need to update Route53 records and add a couple of CNAME entries  &lt;/p&gt;

&lt;p&gt;You can also link multiple domains to the same app…&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsufidev.files.wordpress.com%2F2021%2F12%2Fimage-1.png%3Fw%3D1024" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsufidev.files.wordpress.com%2F2021%2F12%2Fimage-1.png%3Fw%3D1024"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Logs are welcome!
&lt;/h3&gt;

&lt;p&gt;Your &lt;code&gt;console.log&lt;/code&gt; is visible via App Runner console auto-magically. Although CloudWatch is a better option generally, for simple workloads, this saves a lot of time when something goes wrong and you need logs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsufidev.files.wordpress.com%2F2021%2F12%2Fimage-2.png%3Fw%3D1024" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsufidev.files.wordpress.com%2F2021%2F12%2Fimage-2.png%3Fw%3D1024"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Of course, metrics!
&lt;/h3&gt;

&lt;p&gt;Some basic metrics without any effort, out of box are available.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsufidev.files.wordpress.com%2F2021%2F12%2Fimage-3.png%3Fw%3D1024" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsufidev.files.wordpress.com%2F2021%2F12%2Fimage-3.png%3Fw%3D1024"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;If your frontend app doesn’t require server side rendering, go all the way Amplify Hosting for great user experience by leveraging S3 and CDN provided out of box by Amplify. It is really one of the best frontend developer experience available in the market.&lt;/p&gt;

&lt;p&gt;However, to improve site’s SEO ranking, Time to first byte (TTFB) and First contentful paint (FCP) and most importantly, connecting to downstream / backend services at the API layer without leaking anything to browser and client side for a better security, using App Runner is much more flexible choice. Not only for NextJS SSR but for any NodeJS app that requires SSR (Express, Gatsby, React SSR (experimental) etc), App Runner seems to be the easier setup and with very low effort CI/CD pipeline.&lt;/p&gt;

&lt;p&gt;One long term benefit of using App Runner is dockerising your SSR web app. This gives opportunities to move your web app into more complex setups like Fargate and even EKS (yes, I have seen SPAs with SSR served from their own k8s clusters at scale 🤓).&lt;/p&gt;

</description>
      <category>amplify</category>
      <category>apprunner</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Connecting Postman to Amazon Cognito User Pools for API Access Tokens</title>
      <dc:creator>Mahmut Canga</dc:creator>
      <pubDate>Tue, 26 Oct 2021 23:21:08 +0000</pubDate>
      <link>https://dev.to/mahmutcanga/connecting-postman-to-amazon-cognito-user-pools-for-api-access-tokens-41dk</link>
      <guid>https://dev.to/mahmutcanga/connecting-postman-to-amazon-cognito-user-pools-for-api-access-tokens-41dk</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AnPe067Y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sufidev.files.wordpress.com/2021/10/pexels-photo-4175032.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AnPe067Y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sufidev.files.wordpress.com/2021/10/pexels-photo-4175032.jpeg" alt="" width="880" height="587"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Photo by Khwanchai Phanthong on Pexels.com&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Majority of the time in my recent projects, I use &lt;a href="https://aws.amazon.com/cognito/"&gt;Amazon Cognito&lt;/a&gt; for user authentication (sign in, sign up, login with identity providers etc) in front of an &lt;a href="https://aws.amazon.com/api-gateway/"&gt;Amazon API Gateway&lt;/a&gt;. Usually the API endpoints control access &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-integrate-with-cognito.html"&gt;using Amazon Cognito user pools as authorizer&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In these type of APIs, testing the API using &lt;a href="https://www.postman.com"&gt;Postman&lt;/a&gt; is a good practice. Use of Postman helps distributing the API contracts easily while helping you as a developer to run different types of tests without a full-blown client implementation.&lt;/p&gt;

&lt;p&gt;The expected way to connect and consume these APIs are providing an &lt;code&gt;id token&lt;/code&gt; from Amazon Cognito authorization in the headers. This token is auto-validated by Amazon API Gateway by leveraging Cognito Authorizers. A typical request to these endpoints would look like below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="nt"&gt;--request&lt;/span&gt; GET &lt;span class="s1"&gt;'https://{API-ID}.execute-api.eu-west-2.amazonaws.com/v1/profiles/123'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: eyJraWQiOiJOZjB0clFHanRG.....'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To add this token in every request, &lt;a href="https://www.postman.com/collection/"&gt;Postman supports Collection&lt;/a&gt; based Authorization setting. (I assume you follow putting all requests under a collection in Postman). As you can see, Postman supports variety of authorization techniques.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vckiIMXQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sufidev.files.wordpress.com/2021/10/image.png%3Fw%3D1024" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vckiIMXQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sufidev.files.wordpress.com/2021/10/image.png%3Fw%3D1024" alt="" width="880" height="797"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What we are going to use from these alternatives is &lt;strong&gt;OAuth 2.0&lt;/strong&gt; which Amazon Cognito supports out of box. There is a detailed deep dive on different grant types available on &lt;a href="https://aws.amazon.com/blogs/mobile/understanding-amazon-cognito-user-pool-oauth-2-0-grants/"&gt;AWS Blog.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As part of your Amazon Cognito setup, you are expected to create an App Client which has access to this user pool. Using this App Client, we will be able to sign in using an existing user and grab an &lt;code&gt;id token&lt;/code&gt; that will be used for API calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  [Step 1] – App Client Id and Callback URL(s)
&lt;/h2&gt;

&lt;p&gt;In order to setup this, go to App Client Settings section of the Cognito pool.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cnT0baZY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sufidev.files.wordpress.com/2021/10/app-client-settings.jpg%3Fw%3D1024" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cnT0baZY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sufidev.files.wordpress.com/2021/10/app-client-settings.jpg%3Fw%3D1024" alt="" width="880" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You will use the &lt;code&gt;App Client Id&lt;/code&gt; and &lt;code&gt;Callback URL(s)&lt;/code&gt; from this page in your OAuth 2.0 setup in Postman.&lt;/p&gt;

&lt;h2&gt;
  
  
  [Step 2] OAuth 2.0 Flows
&lt;/h2&gt;

&lt;p&gt;Also make sure the following OAuth 2.0 flows are enabled on the same App Client Settings page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nnLiPI46--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sufidev.files.wordpress.com/2021/10/image-2.png%3Fw%3D1024" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nnLiPI46--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sufidev.files.wordpress.com/2021/10/image-2.png%3Fw%3D1024" alt="" width="880" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The way to sign in with an existing user account is via &lt;a href="https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-app-integration.html"&gt;Hosted UI&lt;/a&gt; feature of Amazon Cognito. This UI is a very basic sign in/sign up screen that conforms the User Pool settings. It is actually a nice way of testing your Cognito setup out of box&lt;br&gt;&lt;br&gt;
(nice as in available and working, not as in with a slick UI 🙂)&lt;/p&gt;

&lt;h2&gt;
  
  
  [Step 3] Hosted UI Domain
&lt;/h2&gt;

&lt;p&gt;As you may realise from above screenshot, Hosted UI needs to be setup. In order to do that, go to &lt;strong&gt;App Integration&lt;/strong&gt; section and click &lt;strong&gt;Add Domain&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Qr2Qov9a--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sufidev.files.wordpress.com/2021/10/app-integration-settings.jpg%3Fw%3D1024" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Qr2Qov9a--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sufidev.files.wordpress.com/2021/10/app-integration-settings.jpg%3Fw%3D1024" alt="" width="880" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In Add Domain screen, you can set a domain name for your Hosted UI. This URL will be used for OAuth 2.0 settings in Postman. Also, this URL will be used for the sign-up and sign-in pages that are hosted by Amazon Cognito. The prefix must be unique across the selected AWS Region. Domain names can only contain lower-case letters, numbers, and hyphens. &lt;a href="http://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-assign-domain.html"&gt;Learn more about domain prefixes. &lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  [Step 4] Collection Authorization Settings
&lt;/h2&gt;

&lt;p&gt;Next part is setting up Postman Collection in order to sign in / sign up and get an &lt;code&gt;id token&lt;/code&gt; for making authenticated API calls. Fill in the following details by editing the Collection Authorization settings:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TU-ML3f_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sufidev.files.wordpress.com/2021/10/postman-oauth2-settings.jpg%3Fw%3D1018" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TU-ML3f_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sufidev.files.wordpress.com/2021/10/postman-oauth2-settings.jpg%3Fw%3D1018" alt="" width="880" height="594"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Grant Type:&lt;/strong&gt; Implicit

&lt;ul&gt;
&lt;li&gt;Uncheck &lt;code&gt;Authorise using browser&lt;/code&gt; option&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Callback URL:&lt;/strong&gt; &lt;a href="https://example.com"&gt;&lt;/a&gt;&lt;a href="https://example.com"&gt;https://example.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth URL:&lt;/strong&gt; {Hosted UI URL}/login&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client ID:&lt;/strong&gt; {App Client Id}&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scope:&lt;/strong&gt; phone email openid profile aws.cognito.signin.user.admin&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client Authentication:&lt;/strong&gt; Send client credentials in the body&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  [Step 5] Generate Access Token
&lt;/h2&gt;

&lt;p&gt;When you enter these details and click &lt;strong&gt;Get New Access Token&lt;/strong&gt; button, Postman will open the Hosted UI URL for you to sign in or sign up.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_CeWhLZN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sufidev.files.wordpress.com/2021/10/hosted-ui-screen.jpg%3Fw%3D1018" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_CeWhLZN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sufidev.files.wordpress.com/2021/10/hosted-ui-screen.jpg%3Fw%3D1018" alt="" width="880" height="594"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What is nice about this setup is, you can just create a test user and use it for all API calls accordingly using the above UI. Above UI is enabled by setting Cognito Pool setting called &lt;code&gt;User sign ups allowed? Users can sign themselves up&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;As you can see, this UI complies with the user pool settings and gives you a nice way to test your setup. Here is the screenshot of signup screen and look at the password policy in place….&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ONsA2ssK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sufidev.files.wordpress.com/2021/10/image-3.png%3Fw%3D1024" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ONsA2ssK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sufidev.files.wordpress.com/2021/10/image-3.png%3Fw%3D1024" alt="" width="880" height="712"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hosted UI even provides the email and/or phone validation during setup. In my user pool setup, I require users to have validated emails. So, in this below screenshot I can see that Hosted UI shows related information and input. As Cognito sends real emails for verification code, I suggest to use a real email here to avoid any confusion…&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NZdSjnfC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sufidev.files.wordpress.com/2021/10/image-4.png%3Fw%3D1024" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NZdSjnfC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sufidev.files.wordpress.com/2021/10/image-4.png%3Fw%3D1024" alt="" width="880" height="674"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you finish a successful sign in or sign up, Postman confirms this and grabs the token.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EmfZp67e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sufidev.files.wordpress.com/2021/10/image-5.png%3Fw%3D1024" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EmfZp67e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sufidev.files.wordpress.com/2021/10/image-5.png%3Fw%3D1024" alt="" width="880" height="700"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Following screen is shown with the token details. You will see that this screen has an &lt;code&gt;Access Token&lt;/code&gt; and an &lt;code&gt;id_token&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For API Gateway Cognito Authorizer workflow, you will need to use &lt;code&gt;id_token&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;cite&gt;!!! IMPORTANT DETAIL !!!&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--an7Qc1id--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sufidev.files.wordpress.com/2021/10/manage-tokens-postman-1.jpg%3Fw%3D1018" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--an7Qc1id--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sufidev.files.wordpress.com/2021/10/manage-tokens-postman-1.jpg%3Fw%3D1018" alt="" width="880" height="594"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Simply copy the value of &lt;code&gt;id_token&lt;/code&gt; and put it in Access Token value of the Current Token setting. This will make the &lt;code&gt;id_token&lt;/code&gt; available for all requests in that collection.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--k2bd4M9h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sufidev.files.wordpress.com/2021/10/image-6.png%3Fw%3D1024" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k2bd4M9h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sufidev.files.wordpress.com/2021/10/image-6.png%3Fw%3D1024" alt="" width="880" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  [Step 6] Setup Collection Authorization Settings
&lt;/h2&gt;

&lt;p&gt;Last step is updating API requests to use the Collection Authorization settings. Setting the Authorization setting of requests as &lt;code&gt;Inherit auth from parent&lt;/code&gt; will let Postman inject Access Token in the &lt;code&gt;Authorization&lt;/code&gt; header value.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AGqwXYAP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sufidev.files.wordpress.com/2021/10/request-auth-setting.jpg%3Fw%3D1018" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AGqwXYAP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sufidev.files.wordpress.com/2021/10/request-auth-setting.jpg%3Fw%3D1018" alt="" width="880" height="594"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;I hope you will be able to easily test your APIs behind Cognito using this setup via Postman. You can export the Collection as &lt;code&gt;.json&lt;/code&gt; and even make part of your codebase so you will be embracing API Contract Testing as Code – ACTaC (I made this up now…)&lt;/p&gt;

</description>
      <category>accesstoken</category>
      <category>apigateway</category>
      <category>authentication</category>
    </item>
    <item>
      <title>Lambda Result Package is Live!</title>
      <dc:creator>Mahmut Canga</dc:creator>
      <pubDate>Sun, 03 Oct 2021 00:14:37 +0000</pubDate>
      <link>https://dev.to/mahmutcanga/lambda-result-package-is-live-37in</link>
      <guid>https://dev.to/mahmutcanga/lambda-result-package-is-live-37in</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MPnew9Kb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sufidev.files.wordpress.com/2021/10/creative-headline-f10nthkciw8-unsplash.jpg%3Fw%3D1024" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MPnew9Kb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sufidev.files.wordpress.com/2021/10/creative-headline-f10nthkciw8-unsplash.jpg%3Fw%3D1024" alt="" width="880" height="586"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I have been tinkering with this idea of releasing something open source for a while. Recently, I have started to use a pattern in my Lambda code that became so much handy and productive.&lt;/p&gt;

&lt;p&gt;Today, I’m happy to release it and hopefully it will be useful for you too!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/macromania/lambda-result"&gt;https://github.com/macromania/lambda-result&lt;/a&gt;&lt;/p&gt;

</description>
      <category>lambda</category>
    </item>
    <item>
      <title>A Moment of Testing</title>
      <dc:creator>Mahmut Canga</dc:creator>
      <pubDate>Sun, 20 Jun 2021 12:27:20 +0000</pubDate>
      <link>https://dev.to/mahmutcanga/a-moment-of-testing-1d4o</link>
      <guid>https://dev.to/mahmutcanga/a-moment-of-testing-1d4o</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QQbwNT0g--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sufidev.files.wordpress.com/2021/06/image.png%3Fw%3D842" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QQbwNT0g--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sufidev.files.wordpress.com/2021/06/image.png%3Fw%3D842" alt="" width="842" height="202"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sometimes you may question _ &lt;strong&gt;“Why should I write tests? Why TDD is important?”&lt;/strong&gt; _&lt;/p&gt;

&lt;p&gt;I think this little moment of success can explain a lot of things…  &lt;/p&gt;

&lt;p&gt;My tests here are run under 3seconds. This means, I’ll get an immediate feedback from my tests if I break anything….&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the result of not writing tests?&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Instead of only spending &lt;strong&gt;3 seconds of my dev time&lt;/strong&gt; , I’m going to waste a lot of other people’s time in my team by expecting them finding out the issues by manual testing. Immediate feedback as in 3 seconds is extremely valuable during development. &lt;/li&gt;
&lt;li&gt;Plus, I’ll loose trust of my customers too by unintentionally asking them wasting their time using my software and only finding out the bugs…&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Of course, there are moments we have to ask our team to test manually but it should be as minimum as possible….&lt;/p&gt;

</description>
      <category>tdd</category>
    </item>
    <item>
      <title>Refactoring or Retiring Can be a A Feature</title>
      <dc:creator>Mahmut Canga</dc:creator>
      <pubDate>Sun, 18 Apr 2021 13:27:34 +0000</pubDate>
      <link>https://dev.to/mahmutcanga/refactoring-or-retiring-can-be-a-a-feature-2e8</link>
      <guid>https://dev.to/mahmutcanga/refactoring-or-retiring-can-be-a-a-feature-2e8</guid>
      <description>&lt;h2&gt;
  
  
  &lt;a href="https://www.nature.com/articles/d41586-021-00592-0"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IkEraAd3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sufidev.files.wordpress.com/2021/04/d41586-021-00592-0_18964722.png" alt="" width="880" height="495"&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;A really good article from Nature lays out why the bias towards additive solutions might be further compounded by the fact that subtractive solutions are also less likely to be appreciated.&lt;/p&gt;

&lt;p&gt;In product development, friction points around refactoring and retiring features are widely popular in discussions among the engineers in any organisation. However, the bias towards additive solutions as found out in the article can be a strong root cause.&lt;/p&gt;

&lt;p&gt;When business expects all the charts going top right corner, we may be relating this demand to additive solutions unconsciously. I have not seen any success stories at large mentioning why retiring features made their overall KPIs better.&lt;/p&gt;

&lt;p&gt;After reading this article, I’ll try to challenge my bias towards additive solutions. It is going to be harder to come up with ideas subtracting by nature.  However, in the grand schema of leaving a better legacy behind us and sustainability, I don’t see myself being challenged with counter arguments.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;People tend to solve problems by adding features.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Source: &lt;a href="https://www.nature.com/articles/d41586-021-00592-0"&gt;Adding is favoured over subtracting in problem solving&lt;/a&gt;&lt;/p&gt;

</description>
      <category>bias</category>
      <category>productdevelopment</category>
      <category>refactoring</category>
    </item>
    <item>
      <title>Simple, very dumb system that constantly works</title>
      <dc:creator>Mahmut Canga</dc:creator>
      <pubDate>Mon, 22 Feb 2021 15:16:43 +0000</pubDate>
      <link>https://dev.to/mahmutcanga/simple-very-dumb-system-that-constantly-works-29ga</link>
      <guid>https://dev.to/mahmutcanga/simple-very-dumb-system-that-constantly-works-29ga</guid>
      <description>&lt;p&gt;A great article from &lt;a href="https://pages.awscloud.com/amazon-builders-library.html"&gt;Amazon’s Builder Library.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--77M8GRLe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://d1.awsstatic.com/builderslibrary/icons/Nighthawks.e595a07aee1743a6655011891b48de02dc571251.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--77M8GRLe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://d1.awsstatic.com/builderslibrary/icons/Nighthawks.e595a07aee1743a6655011891b48de02dc571251.jpg" alt="" width="880" height="481"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;These patterns have three key features. One, they don’t scale up or slow down with load or stress. Two, they don’t have modes, which means they do the same operations in all conditions. Three, if they have any variation, it’s to do less work in times of stress so they can perform better when you need them most. There’s that anti-fragility again.&lt;/p&gt;

&lt;p&gt;Even when there are only a few health checks active, the health checkers send a set of results to the aggregators that is sized to the maximum. For example, if only 10 health checks are configured on a particular health checker, it’s still constantly sending out a set of (for example) 10,000 results, if that’s how many health checks it could ultimately support. The other 9,990 entries are dummies. However, this ensures that the network load, as well as the work the aggregators are doing, won’t increase as customers configure more health checks. That’s a significant source of variance … gone.&lt;/p&gt;

&lt;p&gt;Something important to remember is that O(1) doesn’t mean that a process or algorithm only uses one operation. It means that it uses a constant number of operations regardless of the size of the input. The notation should really be O(C).&lt;/p&gt;

&lt;p&gt;A unicycle has fewer moving parts than a bicycle, but it’s much harder to ride. That’s not simpler. A good design has to handle many stresses and faults, and over enough time “survival of the fittest” tends to eliminate designs that have too many or too few moving parts or are not practical.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;More on &lt;a href="https://aws.amazon.com/builders-library/reliability-and-constant-work/"&gt;https://aws.amazon.com/builders-library/reliability-and-constant-work/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>amazon</category>
      <category>big0notation</category>
      <category>builder</category>
    </item>
  </channel>
</rss>
