<?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: Rehan van der Merwe</title>
    <description>The latest articles on DEV Community by Rehan van der Merwe (@rehanvdm).</description>
    <link>https://dev.to/rehanvdm</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%2F131633%2F4781e00e-abfa-4adb-840f-b55cf7b36d26.png</url>
      <title>DEV Community: Rehan van der Merwe</title>
      <link>https://dev.to/rehanvdm</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rehanvdm"/>
    <language>en</language>
    <item>
      <title>TypeScript Type Safety with AJV Standalone</title>
      <dc:creator>Rehan van der Merwe</dc:creator>
      <pubDate>Sat, 15 Jan 2022 22:00:00 +0000</pubDate>
      <link>https://dev.to/rehanvdm/typescript-type-safety-with-ajv-standalone-4861</link>
      <guid>https://dev.to/rehanvdm/typescript-type-safety-with-ajv-standalone-4861</guid>
      <description>&lt;p&gt;Originally published on my blog: &lt;a href="https://www.rehanvdm.com/blog/typescript-type-safety-with-ajv-standalone" rel="noopener noreferrer"&gt;https://www.rehanvdm.com/blog/typescript-type-safety-with-ajv-standalone&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this blog we start of by exploring what Type means in TypeScript and how to achieve type safety at runtime. Then a quick comparison of the current tools to achieve this and deep dive into the implementation using the AJV Standalone method.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; TypeScript does a great job at compile time type safety, but &lt;strong&gt;we still need to do runtime checks&lt;/strong&gt; just like in JavaScript. There are many packages and tools to help with this, we focused on AJV Standalone that outputs JS validation functions at compile time to be used at runtime. &lt;strong&gt;Going from TS Types to JSON Schema to JS functions&lt;/strong&gt; allows us to validate TS Types where the other packages all work with classes and reflection.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;strong&gt;code&lt;/strong&gt; referenced in this blog can be found in the example project here: &lt;a href="https://github.com/rehanvdm/ajv-standalone-type-saftey" rel="noopener noreferrer"&gt;https://github.com/rehanvdm/ajv-standalone-type-saftey&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  TS in the land of type safety
&lt;/h2&gt;

&lt;p&gt;TypeScript (TS) is a statically-typed safe language that outputs JavaScript(JS) when compiled. The type safety that TS provides at compile time allows for much higher quality projects than using plain JS.&lt;/p&gt;

&lt;p&gt;TS will produce JS and then corresponding type definition files (&lt;code&gt;.d.ts&lt;/code&gt;) that is used at compile time, many npm packages only expose these two types of files and not the actual TS file. The type definition files informs TS how to use the JS files in the absence of the actual TS file.&lt;/p&gt;

&lt;p&gt;Something often forgotten is that TS, similar to JS, does not provide runtime type safety. It can not know that the JSON object being casted to Type A actually has the correct properties and definitions that fit Type A at compile time. This does not fall within the &lt;a href="https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals" rel="noopener noreferrer"&gt;scope of TS goals&lt;/a&gt;. Runtime saftey has to be added just like any other JS project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ensuring runtime type safety
&lt;/h2&gt;

&lt;p&gt;The following is just a quick rundown of the &lt;strong&gt;current tools available&lt;/strong&gt; to help achieve runtime type safety.&lt;/p&gt;

&lt;h3&gt;
  
  
  1) Classes with decorators
&lt;/h3&gt;

&lt;p&gt;The most popular implementations is &lt;a href="https://www.npmjs.com/package/class-validator" rel="noopener noreferrer"&gt;class-validator&lt;/a&gt; paired with&lt;a href="https://www.npmjs.com/package/class-transformer" rel="noopener noreferrer"&gt;class-transformer&lt;/a&gt;. It uses decorators on class properties to define the rules that will be evaluated at runtime.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;IsInt&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;rating&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// should not pass&lt;/span&gt;
&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rating&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// should not pass&lt;/span&gt;

&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// errors is an array of validation errors&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The major downside of this approach is that decorators do not work interfaces and types, this forces you to represent every type as a class. You can not construct subtypes from other classes, leading to many duplicate unconnected types.&lt;/p&gt;

&lt;p&gt;Composing types from other types using keywords like (&lt;code&gt;Partial&lt;/code&gt;, &lt;code&gt;Omit&lt;/code&gt;, &lt;code&gt;Pick&lt;/code&gt;) is only possible with TS Types, this superpower is lost as soon as you force every type to be a class.&lt;/p&gt;

&lt;h3&gt;
  
  
  2) All types as classes
&lt;/h3&gt;

&lt;p&gt;Then there are a set of packages that also forces you to represent every type as a class but instead of using decorators to define the rules, the properties of the class are other classes/functions. One of these packages is &lt;a href="https://gcanti.github.io/io-ts/" rel="noopener noreferrer"&gt;io-ts&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;io-ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;number&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="nx"&gt;t&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;userData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Person&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;person&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Piet&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;Composing types from other types is possible, but it gets hairy real quickly. Another library,&lt;a href="https://github.com/gcanti/fp-ts" rel="noopener noreferrer"&gt;fp-ts&lt;/a&gt;, is needed to do this composition and like &lt;code&gt;io-ts&lt;/code&gt; has its own learning curve. Another downside is that we still can’t use the native TS Types and make use of their composability.&lt;/p&gt;

&lt;p&gt;In many cases this approach is “technically” better and faster than the previous one that uses class decorators and reflection for validation. I say “technically” because it feels like it ignores the native Type in TypeScript and forces you to use a different type system (opinion).&lt;/p&gt;

&lt;h3&gt;
  
  
  3) AJV
&lt;/h3&gt;

&lt;p&gt;Another Json Validator (AJV) uses &lt;a href="https://json-schema.org/" rel="noopener noreferrer"&gt;JSON Schema&lt;/a&gt; to define the types. JSON Schema is a vocabulary that allows you to annotate and validate JSON documents. Both the data and the JSON Schema is passed to a validator that validates the rules as defined in the schema.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;foo&lt;/span&gt;&lt;span class="p"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;integer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&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;required&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;foo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;additionalProperties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;abc&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;valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ajv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ajv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;At first glance this does not seem ideal, since you have to define all your TS Types and their corresponding JSON Schemas. Fortunately there are tools that easily transform TS Types into JSON Schema for us, this is what we do in the example project.&lt;/p&gt;

&lt;p&gt;One downside is that AJV transforms/compiles JSON Schema to an actual JS function and then caches it for future use. This means that the initial startup time can be significant, especially in short-lived environments like Lambda functions.&lt;/p&gt;

&lt;h3&gt;
  
  
  4) AJV Standalone
&lt;/h3&gt;

&lt;p&gt;This is where AJV Standalone mode really shines, we save the generated validation function from the compiled output as an actual JS function at compile time. This function is then just imported/required at runtime and used as a standard JS function that validates the input.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Generating the JS Validation function&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fs&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;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Ajv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ajv&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;standaloneCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ajv/dist/standalone&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/bar.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;$schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://json-schema.org/draft-07/schema#&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;bar&lt;/span&gt;&lt;span class="p"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&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;required&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;bar&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="c1"&gt;// For ESM, the export name needs to be a valid export name, it can not be `export const https://example.com/bar.json = ...;` so we&lt;/span&gt;
&lt;span class="c1"&gt;// need to provide a mapping between a valid name and the $id field. Below will generate&lt;/span&gt;
&lt;span class="c1"&gt;// `export const Bar = ...;`&lt;/span&gt;
&lt;span class="c1"&gt;// This mapping would not have been needed if the `$id` was just `Bar`&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ajv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Ajv&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;schemas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="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="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;esm&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;moduleCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;standaloneCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ajv&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;Bar&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;https://example.com/bar.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Now you can write the module code to file, notice the file is saved as `.mjs` so that it can be imported as a module.&lt;/span&gt;
&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../consume/validate-esm.mjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;moduleCode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Consuming the JS Validation function&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Bar&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;./validate-esm.mjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;barPass&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;something&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;barFail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// bar: "something" // &amp;lt;= empty/omitted property that is required&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;validateBar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Bar&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;validateBar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;barPass&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ERRORS 1:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;validateBar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;//Never reaches this because valid&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;validateBar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;barFail&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ERRORS 2:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;validateBar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;//Errors array gets logged&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;This means we would end up having multiple JS Types, their corresponding JSON Schema files and then the validation JS function created at compile time. It might seem a bit excessive to achieve runtime type safety, but it is the best option in my opinion. Here is why:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We can use TS Types and &lt;strong&gt;make use of&lt;/strong&gt; all the TS superpowers like &lt;strong&gt;type composition&lt;/strong&gt; (&lt;code&gt;Partial&lt;/code&gt;, &lt;code&gt;Omit&lt;/code&gt;, &lt;code&gt;Pick&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Many other systems and tools can generate/export TS native types from entities like an OpenAPI (aka Swagger) file or a database schema. These &lt;strong&gt;types can then be consumed without changing any of their definitions&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;It is &lt;strong&gt;faster&lt;/strong&gt; than most other implementations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One downside is that an extra compile step is needed to transform the TS Type to JSON Schema to the JS Validation function.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using AJV Standalone to make a TS project type safe
&lt;/h2&gt;

&lt;p&gt;We will use the AJV Standalone method and multiple other tools to create a TS project that is both compile and runtime safe, as well as still providing a good developer experience (DX).&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%2Frehanvdm.com%2Fimages%2Fblog%2Ftypescript-type-safety-with-ajv-standalone%2F01_dir_structure.jpg" 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%2Frehanvdm.com%2Fimages%2Fblog%2Ftypescript-type-safety-with-ajv-standalone%2F01_dir_structure.jpg" alt="Project Directory Structure"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Project directory structure of example project&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Generating the JSON Schema and JS validation function at compile time
&lt;/h3&gt;

&lt;p&gt;Pseudo logic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Generate the JSON Schema files from the TS Types&lt;/strong&gt; and save them as individual JSON Schema files using the&lt;a href="https://github.com/YousefED/typescript-json-schema" rel="noopener noreferrer"&gt;typescript-json-schema&lt;/a&gt; package, optionally can be committed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create the AJV validation code&lt;/strong&gt; in ESM format from the JSON Schema files, save this file.&lt;/li&gt;
&lt;li&gt;Optionally pass the generated code through ES Build to bundle and minify the function and all it’s dependencies into 1 file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create the TS Definition file (typings)&lt;/strong&gt; from the JS validation file using TSC so that the file can be imported in TS.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are the basic steps that happen when you &lt;code&gt;npm run build-types&lt;/code&gt;, this runs the &lt;code&gt;build_types&lt;/code&gt; Gulp task by doing:&lt;code&gt;gulp --color --gulpfile gulpfile.js build_types&lt;/code&gt;. Gulp is a build tool that runs JS code and the defined Gulp task &lt;code&gt;build_types&lt;/code&gt;runs the above pseudo logic by calling the CLI where necessary or calling the JS libraries. The same could have been done with a Bash script, but I always favor JS build/automation tools as they are cross-platform.&lt;/p&gt;

&lt;p&gt;More notes (extra detail, might want to skip on first read):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;a href="https://github.com/YousefED/typescript-json-schema" rel="noopener noreferrer"&gt;typescript-json-schema&lt;/a&gt; package can only be pointed to a single directory and can not exclude certain paths. It will go through your whole &lt;code&gt;node_modules&lt;/code&gt; folder if you point it at the project root. That is why I point it at a specific folder that contains all the project types.&lt;/li&gt;
&lt;li&gt;The generated JSON Schema file will be a single JSON Schema object with each type in the &lt;code&gt;definitions&lt;/code&gt; array property. Each of these definitions are then moved into their own JSON Schema objects and saved to file. Only after replacing all the&lt;code&gt;#/definitions/&lt;/code&gt; within the file so that schemas can correctly reference each other (from within AJV) and also have valid &lt;code&gt;$id&lt;/code&gt; fields which become the ESM exported name.&lt;/li&gt;
&lt;li&gt;Each of the JSON Schema files are then added to the AJV schemas before compiling the validation function.&lt;/li&gt;
&lt;li&gt;We add the &lt;code&gt;ajv-formats&lt;/code&gt; when compiling the JSON schemas into functions because some types have a &lt;code&gt;Date&lt;/code&gt; property which is represented in JSON Schema as &lt;code&gt;{ "type": "string", "format": "date-time" }&lt;/code&gt;. Adding these extra AJV formats, enable automatic identification and parsing of a string in ISO 8601 format into a JS Date type object.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Below is the Pseudo logic as implemented in the Gulp file, this can be skimmed for now as the runtime usage is more important than the generation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;typeScriptToJsonSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;srcDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;destDir&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;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;srcDir&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/**/*.ts&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;schemas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;* TS TO JSONSCHEMA&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;schemaRaw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tsj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createGenerator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;createSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timeEnd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;* TS TO JSONSCHEMA&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="cm"&gt;/* Remove all `#/definitions/` so that we can use the Type name as the $id and have matching $refs with the other Types */&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schemaRaw&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/#&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;definitions&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;/gm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="cm"&gt;/* Save each Type jsonschema individually, use the Type name as $id */&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="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;definition&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;definitions&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;singleTypeDefinition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$id&lt;/span&gt;&lt;span class="dl"&gt;"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$schema&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;http://json-schema.org/draft-07/schema#&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;definition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nx"&gt;schemas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;singleTypeDefinition&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;destDir&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;singleTypeDefinition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;compileAjvStandalone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schemas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;validationFile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;* AJV COMPILE&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;ajv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Ajv&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;schemas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;schemas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="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="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;esm&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="nf"&gt;addFormats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ajv&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;moduleCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;standaloneCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ajv&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timeEnd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;* AJV COMPILE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;validationFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;moduleCode&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;esBuildCommonToEsm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;validationFile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;* ES BUILD&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;esbuild&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;buildSync&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="c1"&gt;// minify: true,&lt;/span&gt;
    &lt;span class="na"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;target&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;node14&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;keepNames&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;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;esm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;entryPoints&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;validationFile&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;outfile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;validationFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;allowOverwrite&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timeEnd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;* ES BUILD&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateTypings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;validationFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;validationFileFolder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;* TSC DECLARATIONS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;execCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tsc&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;-allowJs --declaration --emitDeclarationOnly &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;validationFile&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; --outDir &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;validationFileFolder&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timeEnd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;* TSC DECLARATIONS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildTypes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;paths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/types&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;typesJsonSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/types/schemas&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;validationFile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/types/schemas/validations.js&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;/* Clear the output dir for the AJV validation code, definition and JSON Schema definitions */&lt;/span&gt;
  &lt;span class="nf"&gt;clearDir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;typesJsonSchema&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="cm"&gt;/* Create the JSON Schema files from the TS Types and save them as individual JSON Schema files */&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;schemas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;typeScriptToJsonSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;types&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;typesJsonSchema&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="cm"&gt;/* Create the AJV validation code in ESM format from the JSON Schema files */&lt;/span&gt;
  &lt;span class="nf"&gt;compileAjvStandalone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schemas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;validationFile&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="cm"&gt;/* Bundle the AJV validation code file in ESM format */&lt;/span&gt;
  &lt;span class="nf"&gt;esBuildCommonToEsm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;validationFile&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="cm"&gt;/* Create TypeScript typings for the generated AJV validation code */&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generateTypings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;validationFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;typesJsonSchema&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;gulp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;build_types&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;buildTypes&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The console output of the &lt;code&gt;build_types&lt;/code&gt; task:&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="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;51&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;57&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="nx"&gt;Starting&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;build_types&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;TS&lt;/span&gt; &lt;span class="nx"&gt;TO&lt;/span&gt; &lt;span class="nx"&gt;JSONSCHEMA&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2.586&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;
&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;AJV&lt;/span&gt; &lt;span class="nx"&gt;COMPILE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;56.419&lt;/span&gt;&lt;span class="nx"&gt;ms&lt;/span&gt;
&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;ES&lt;/span&gt; &lt;span class="nx"&gt;BUILD&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;76.501&lt;/span&gt;&lt;span class="nx"&gt;ms&lt;/span&gt;
&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;TSC&lt;/span&gt; &lt;span class="nx"&gt;DECLARATIONS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2.995&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;52&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;03&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="nx"&gt;Finished&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;build_types&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="nx"&gt;after&lt;/span&gt; &lt;span class="mf"&gt;5.72&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;We also have an additional Gulp task that watches all the types in the type directory and recompiles the JSON Schema and validation function as soon as the types change.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;gulp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;watch_types&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;buildTypes&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;gulp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;types/*.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;buildTypes&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;cb&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;
  
  
  Consuming the JS validation function for our types at runtime
&lt;/h3&gt;

&lt;p&gt;The Post and NewPost(not used atm) types in &lt;code&gt;/types/Post.ts&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Post&lt;/span&gt; &lt;span class="o"&gt;=&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;description&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;rating&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;createAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&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;type&lt;/span&gt; &lt;span class="nx"&gt;NewPost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Omit&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;createAt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The Blog type in &lt;code&gt;/types/Blog.ts&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Post&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;./Post&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;type&lt;/span&gt; &lt;span class="nx"&gt;Blog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;site&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;about&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;email&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="c1"&gt;// twitter: string; //Test watch command&lt;/span&gt;
  &lt;span class="nl"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Post&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;A basic example of using the validation function that was generated in &lt;code&gt;/types/schemas/validations.js&lt;/code&gt; by the Gulp task. Here we create a blog with one valid post and then add another invalid post that does not have a &lt;code&gt;createdAt&lt;/code&gt; property. This is possible because we cast the object as that type, the TSC does not complain because this is valid at compile time, but it will fail at runtime.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Post&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;./types/Post&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;Blog&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;./types/Blog&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;DateTime&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;luxon&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;ValidateFunction&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;ajv&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;validations&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;./types/schemas/validations&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;blog&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Blog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;site&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rehanvdm.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rehan.nope@gmail.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;about&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;My blog, the one I never have time to write for but do it anyway.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;:&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Valid Post&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;rating&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;createAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toJSDate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;postInValid&lt;/span&gt; &lt;span class="o"&gt;=&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid Post! Missing createAt, forcing by casting&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;rating&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="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;blog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;postInValid&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;validateBlog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;validations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Blog&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ValidateFunction&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Blog&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;validateBlog&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Validate not defined, schema not found&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="cm"&gt;/* Casting to and from JSON forces the object to be represented in its primitive types.
* The Date object for example will be forced to a ISO 8601 representation which is what we want */&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;validateBlog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blog&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;validateBlog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;validateBlog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Blog not valid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The magic happens here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validateBlog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;validations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Blog&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ValidateFunction&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Blog&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Where we cast the function that returns a boolean as a &lt;code&gt;ValidateFunction&amp;lt;Blog&amp;gt;&lt;/code&gt;. The function still returns a boolean, but it will add an extra property, an &lt;code&gt;errors&lt;/code&gt; array to the function prototype if it evaluated to false.&lt;/p&gt;

&lt;p&gt;A more intuitive way of declaring types that are runtime safe can be achieved with a bit of abstraction. As in the following example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Post&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;./types/Post&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;Blog&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;./types/Blog&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;DateTime&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;luxon&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;ValidateFunction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ErrorObject&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;ajv&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;validations&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;./types/schemas/validations&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TypeError&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;ajvErrors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ErrorObject&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;ajvErrors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ErrorObject&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;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ajvErrors&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;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TypeError&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;ajvErrors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ajvErrors&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ensureType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;validationFunc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;instancePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;parentData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;parentDataProperty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rootData&lt;/span&gt; &lt;span class="p"&gt;}?:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;instancePath&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;parentData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;parentDataProperty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;rootData&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;any&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="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;validationFunc&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ValidateFunction&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Validate not defined, schema not found&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="cm"&gt;/* Casting to and from JSON forces the object to be represented in its primitive types.
   * The Date object for example will be forced to a ISO 8601 representation which is what we want */&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isValid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isValid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&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;blog&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Blog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;site&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rehanvdm.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rehan.nope@gmail.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;about&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;My blog, the one I never have time to write for but do it anyway.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;:&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Valid Post&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;rating&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;createAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toJSDate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;postInValid&lt;/span&gt; &lt;span class="o"&gt;=&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid Post! Missing createAt, forcing by casting&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;rating&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="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;blog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;postInValid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="cm"&gt;/* Passes */&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;another&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ensureType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Post&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;validations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;,&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Quick way to ensure type is valid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Just initiate differently like this&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;createAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toJSDate&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;rating&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="cm"&gt;/* Fails, similar to the basic test */&lt;/span&gt;
  &lt;span class="nx"&gt;ensureType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Blog&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;validations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Blog&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;blog&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TYPE ERROR WITH STACK:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ajvErrors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&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;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ERROR:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;We created a new error &lt;code&gt;TypeError&lt;/code&gt; that is used by the &lt;code&gt;ensureType&lt;/code&gt; function. This error adds the AJV errors aray to the JS error when it is thrown, we also get a full stack trace so we can easily identify the type that failed at runtime.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;ensureType&lt;/code&gt; function takes the validation function as generated by AJV and the unvalidated object as arguments. It uses a bit of generics to return the object when it is valid or throw the &lt;code&gt;TypeError&lt;/code&gt; for when it failed. This means your syntax changes from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;another&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Post&lt;/span&gt; &lt;span class="o"&gt;=&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Quick way to ensure type is valid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Just initiate differently like this&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;createAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toJSDate&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;rating&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;another&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ensureType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Post&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;validations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;,&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Quick way to ensure type is valid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Just initiate differently like this&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;createAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toJSDate&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;rating&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a very small change for the benefit of validation and runtime type safety. Another benefit is that you don’t need to change any of your already defined types, you just need to wrap the initialization of those types.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion (TL;DR)
&lt;/h2&gt;

&lt;p&gt;TypeScript does a great job at compile time type safety, but &lt;strong&gt;we still need to do runtime checks&lt;/strong&gt; just like in JavaScript. There are many packages and tools to help with this, we focused on AJV Standalone that outputs JS validation functions at compile time to be used at runtime. &lt;strong&gt;Going from TS Types to JSON Schema to JS functions&lt;/strong&gt; allows us to validate TS Types where the other packages all work with classes and reflection.&lt;/p&gt;

&lt;p&gt;Representing all your types as classes is not the solution, you lose a lot of TS superpowers (type composability) as soon as you do. It is crucial to do runtime checks against your Input and Outputs, the boundaries of the system. Doing internal runtime checks is optional but always welcome.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;code&lt;/strong&gt; referenced in this blog can be found in the example project here: &lt;a href="https://github.com/rehanvdm/ajv-standalone-type-saftey" rel="noopener noreferrer"&gt;https://github.com/rehanvdm/ajv-standalone-type-saftey&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  PS - The rabbit hole
&lt;/h2&gt;

&lt;p&gt;This blog took quite a while to write(idea + code + blog), the code did not really do what I wanted it to. The AJV standalone functionality only exported the code with CJS (module.exports/require) and I needed it to be ESM (export/import) to correctly generate TS typings and for it to play nice with ES Build and TS. I made my first noteworthy PR to OpenSource to fix these issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/ajv-validator/ajv/issues/1523" rel="noopener noreferrer"&gt;Option to generate ESM for standalone validators - https://github.com/ajv-validator/ajv/issues/1523&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ajv-validator/ajv/issues/1831" rel="noopener noreferrer"&gt;Concrete example of using standalone at runtime - https://github.com/ajv-validator/ajv/issues/1831&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These two PRs did the trick, the second one is the documentation.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/ajv-validator/ajv/pull/1861" rel="noopener noreferrer"&gt;Add option to generate ESM exports instead of CJS - https://github.com/ajv-validator/ajv/pull/1861&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ajv-validator/ajv/issues/1831" rel="noopener noreferrer"&gt;Concrete example of using standalone at runtime - https://github.com/ajv-validator/ajv/issues/1831&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It was fun contributing to the AJV project, the project has great test coverage, guidelines and conventions.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>architecture</category>
      <category>design</category>
      <category>validation</category>
    </item>
    <item>
      <title>LRU cache fallback strategy</title>
      <dc:creator>Rehan van der Merwe</dc:creator>
      <pubDate>Thu, 13 Jan 2022 22:00:00 +0000</pubDate>
      <link>https://dev.to/rehanvdm/lru-cache-fallback-strategy-3gc8</link>
      <guid>https://dev.to/rehanvdm/lru-cache-fallback-strategy-3gc8</guid>
      <description>&lt;p&gt;Originally published on my blog: &lt;a href="https://www.rehanvdm.com/blog/lru-cache-fallback-strategy"&gt;https://www.rehanvdm.com/blog/lru-cache-fallback-strategy&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://en.wikipedia.org/wiki/Cache_replacement_policies#Least_recently_used_(LRU)"&gt;Least Recently Used(LRU)&lt;/a&gt;cache stores items in-memory and evicts the oldest(less used) ones as soon as the allocated memory (or item count) has been reached. Storing data in-memory before reaching for an external cache increases speed and decrease the dependency on the external cache. It is also possible to fallback to in-memory caches like an LRU cache in periods that your external cache goes down without seeing a significant impact on performance.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Conclusion (TL;DR)&lt;/strong&gt; By leveraging an in-memory cache you reduce pressure on the downstream caches, reduce costs and speed up systems. Consider having an in-memory first policy, instead of having an in-memory fallback cache strategy to see the biggest gains.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Experiment
&lt;/h2&gt;

&lt;p&gt;I took some time to write a small little program to simulate and compare the following scenarios:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;No caching, fetching data directly from the DB.&lt;/li&gt;
&lt;li&gt;Caching the DB data in Redis.&lt;/li&gt;
&lt;li&gt;Caching the DB data in Redis first (which fails) and falls back to an in LRU memory cache.&lt;/li&gt;
&lt;li&gt;Caching the DB data in an LRU cache first, then reaching for Redis (which fails) if it is not in the LRU cache.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;strong&gt;code can be found here: &lt;a href="https://github.com/rehanvdm/lru-cache-fallback-strategy"&gt;https://github.com/rehanvdm/lru-cache-fallback-strategy&lt;/a&gt;&lt;/strong&gt;. We won’t go into too many details about the code in this blog, see the README file for more info on the code.&lt;/p&gt;

&lt;p&gt;In a nutshell, &lt;strong&gt;we are simulating&lt;/strong&gt; an API call, where we get the user ID from the JWT and then need &lt;strong&gt;to look that user up in the DB to get their allowed permissions.&lt;/strong&gt; I mocked all the DB (200ms) and Redis(20ms) network calls, with the help of environment variables used as hyperparameters.&lt;/p&gt;

&lt;p&gt;The program runs 100 executions and in the scenario where Redis fails (3,4) it will fail on the 50th execution. A user id is selected from a seeded random number generator that selects a random user id from a sample size of 10, ensuring a high cache hit ratio. The sample space is kept small so that all data is fetched before Redis goes down at the halfway mark.&lt;/p&gt;

&lt;p&gt;When Redis goes down at the halfway mark, we implement a circuit breaker pattern, returning false on the lazy-loaded &lt;code&gt;connect()&lt;/code&gt; function that is called inside every Redis command before it is made. Only if 10 seconds have elapsed do we attempt a reconnection and if it fails, we store the last time we tried to connect for the circuit breaker. This is so that we don’t waste time trying to connect on every request and also reduce pressure on the cache.&lt;/p&gt;

&lt;p&gt;Below is the &lt;strong&gt;pseudocode for scenarios 3&lt;/strong&gt; , where Redis is used first with fallback to an LRU cache. Not visible here is the environment variables that toggle Redis availability and many other options. The important part I want to highlight here is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Redis is queried first, if the user is not found it returns &lt;code&gt;undefined&lt;/code&gt;, but if Redis is down it will try to look the value up in the in-memory LRU cache. If not found it also returns undefined.&lt;/li&gt;
&lt;li&gt;If the user is still undefined, then we fetch the user from the DB. The user is then stored in the Redis cache but if the Redis cache is down, we store it in the LRU cache.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://github.com/rehanvdm/lru-cache-fallback-strategy/blob/master/app/index.mjs"&gt;/app/index.mjs&lt;/a&gt; contains this logic used for scenarios 3. It is also used for scenario 1 and 2 by setting environment variables that either disable all caching or ensures Redis does not go down.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MoY8KUWe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rehanvdm.com/images/blog/lru-cache-fallback-strategy/01_redis_order.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MoY8KUWe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rehanvdm.com/images/blog/lru-cache-fallback-strategy/01_redis_order.png" alt="Caching order" width="766" height="1077"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Scenario 3, accessing strategies with Redis first, the happy path with Redis up&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here the LRU is only used as a fallback for Redis, but we can get better performance if we leverage the LRU cache and put it first as in scenario 4.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;pseudocode for scenario 4&lt;/strong&gt; , the LRU cache is used first with fallback to Redis, the pseudo logic is as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The LRU is queried first, if the user is not found, we query Redis.&lt;/li&gt;
&lt;li&gt;If we still don’t have a user it means Redis does not have it or Redis is down. So we look it up in the DB. Then we store the user in both Redis and the in-memory LRU cache.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://github.com/rehanvdm/lru-cache-fallback-strategy/blob/master/app/index-lru-first.mjs"&gt;app/index-lru-first.mjs&lt;/a&gt;contains this logic used for scenario 4.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--e_z0xRb3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rehanvdm.com/images/blog/lru-cache-fallback-strategy/02_lru_order.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--e_z0xRb3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rehanvdm.com/images/blog/lru-cache-fallback-strategy/02_lru_order.png" alt="Caching order" width="766" height="1077"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Scenario 4, accessing strategies with LRU first, the happy path with Redis up&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;With this method, we prefer the in-memory cache and only fallback to the slower methods/layers when we don’t have the value. When we get the value, we cache it on all layers so that subsequent executions and other processes can get it without needing to query the source.&lt;/p&gt;

&lt;p&gt;This should come as no surprise that it is the fastest method. It can really improve performance of a high throughput system and reduce the dependency as well as back pressure on downward systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;I used &lt;a href="https://www.npmjs.com/package/asciichart"&gt;asciichart&lt;/a&gt; to draw charts with &lt;code&gt;console.log(...)&lt;/code&gt; and then piped the results to files.&lt;/p&gt;

&lt;p&gt;Here are the results of each scenario.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scenario 1 - DB only, ~200ms avg
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; APP VARIABLES
REDIS_GET 0
REDIS_SET 0
DB_FETCHES 100
LRU_SET 0
LRU_GET 0

&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; TEST OUTPUTS
TEST_RUN_TIME &lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt; 21
APP_EXECUTIONS_COUNT 100
APP_EXECUTION_TIME &lt;span class="o"&gt;(&lt;/span&gt;ms&lt;span class="o"&gt;)&lt;/span&gt; 214
AVERAGE&lt;span class="o"&gt;(&lt;/span&gt;APP_EXECUTION_TIME&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;ms&lt;span class="o"&gt;)&lt;/span&gt; 212

&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; CHART
     405.00 ┤ ╭╮ ╭╮  
     384.50 ┼╮ ││ ││  
     364.00 ┤│ ││ ││  
     343.50 ┤│ ││ ││  
     323.00 ┤│ ││ ││  
     302.50 ┤│ ││ ││  
     282.00 ┤│ ││ ││  
     261.50 ┤│ ││ ││  
     241.00 ┤│ ││ ││  
     220.50 ┤│ ╭╮╭╮ ││ ╭╮ ╭╮ ╭╮ ││  
     200.00 ┤╰───────────────────────────────╯╰╯╰───────────╯╰────────╯╰──────────────╯╰─────╯╰──────────────╯╰─ 


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

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Partial log file from db-only.log generated by running &lt;code&gt;npm run app-save-output-db-only&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We see that all 100 queries were made against the &lt;code&gt;DB_FETCHES 100&lt;/code&gt;, the spikes are when Redis reconnects, which should kinda of not be doing that, I just didn’t want to add another environment variable. We can see that the average APP execution time over the 100 iterations were 212ms from &lt;code&gt;AVERAGE(APP_EXECUTION_TIME) (ms) 212&lt;/code&gt;, but is actually closer to 200ms if we ignore the spikes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scenario 2 - DB =&amp;gt; Redis (no failure), 55ms avg
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; APP VARIABLES
REDIS_GET 100
REDIS_SET 10
DB_FETCHES 10
LRU_SET 0
LRU_GET 0

&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; TEST OUTPUTS
TEST_RUN_TIME &lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt; 6
APP_EXECUTIONS_COUNT 100
APP_EXECUTION_TIME &lt;span class="o"&gt;(&lt;/span&gt;ms&lt;span class="o"&gt;)&lt;/span&gt; 31
AVERAGE&lt;span class="o"&gt;(&lt;/span&gt;APP_EXECUTION_TIME&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;ms&lt;span class="o"&gt;)&lt;/span&gt; 55

&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; CHART
     318.00 ┼╮                                                                                                   
     289.10 ┤│                                                                                                   
     260.20 ┤╰───╮ ╭╮╭╮ ╭╮╭╮ ╭╮                                                                    
     231.30 ┤ │ ││││ ││││ ││                                                                    
     202.40 ┤ │ ││││ ││││ ││                                                                    
     173.50 ┤ │ ││││ ││││ ││                                                                    
     144.60 ┤ │ ││││ ││││ ││                                                                    
     115.70 ┤ │ ││││ ││││ ││                                                                    
      86.80 ┤ │ ││││ ││││ ││                                                                    
      57.90 ┤ │ ││││ ││││ ││                                                                    
      29.00 ┤ ╰─╯╰╯╰─╯╰╯╰───────────────╯╰─────────────────────────────────────────────────────────────────── 


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

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Partial log file from db-with-redis.log generated by running &lt;code&gt;npm run app-save-output-db-with-redis&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In this scenario Redis does not fail, we can see we did 10 DB fetches, 10 Redis SET commands and 100 Redis GET commands.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scenario 3 - DB =&amp;gt; Redis (failure) =&amp;gt; LRU, 62ms avg
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; APP VARIABLES
REDIS_GET 50
REDIS_SET 10
DB_FETCHES 20
LRU_SET 10
LRU_GET 50

&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; TEST OUTPUTS
TEST_RUN_TIME &lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt; 6
APP_EXECUTIONS_COUNT 100
APP_EXECUTION_TIME &lt;span class="o"&gt;(&lt;/span&gt;ms&lt;span class="o"&gt;)&lt;/span&gt; 0
AVERAGE&lt;span class="o"&gt;(&lt;/span&gt;APP_EXECUTION_TIME&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;ms&lt;span class="o"&gt;)&lt;/span&gt; 62

&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; CHART
     425.00 ┼ ╭╮                                                 
     382.50 ┤ ││                                                 
     340.00 ┼╮ ││                                                 
     297.50 ┤│ ││                                                 
     255.00 ┤╰───╮ ╭╮╭╮ ╭╮╭╮ ╭╮ ││                                                 
     212.50 ┤ │ ││││ ││││ ││ │╰───╮╭╮╭─╮ ╭╮ ╭╮       
     170.00 ┤ │ ││││ ││││ ││ │ ││││ │ ││ ││       
     127.50 ┤ │ ││││ ││││ ││ │ ││││ │ ││ ││       
      85.00 ┤ │ ││││ ││││ ││ │ ││││ │ ││ ││       
      42.50 ┤ ╰─╯╰╯╰─╯╰╯╰───────────────╯╰─────────────────╯ ││││ │ ││ ││       
       0.00 ┤ ╰╯╰╯ ╰─────────────────────╯╰────────╯╰────── 


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

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Partial log file from db-with-redis-fail-halfway-with-fallback-lru.log generated by running &lt;code&gt;npm run app-save-output-db-with-redis-fail-halfway-with-fallback-lru&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here we can see that the DB data is fetched, stored in the cache and then read from until the halfway mark, just liked before. Then Redis fails, which forces the app to make calls back to the DB and then cache the response in the in-memory LRU cache. By looking at the baselines for when data was fetched from Redis in the first half, and comparing it with the baseline of when the data was fetched from the LRU cache, we observe that &lt;strong&gt;the LRU is much faster.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Why not store data in the LRU first then? That is exactly what we do in scenario 4.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scenario 4 - DB =&amp;gt; LRU =&amp;gt; Redis (failure), 27ms avg
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; APP VARIABLES
REDIS_GET 10
REDIS_SET 10
DB_FETCHES 10
LRU_SET 10
LRU_GET 100

&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; TEST OUTPUTS
TEST_RUN_TIME &lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt; 3
APP_EXECUTIONS_COUNT 100
APP_EXECUTION_TIME &lt;span class="o"&gt;(&lt;/span&gt;ms&lt;span class="o"&gt;)&lt;/span&gt; 0
AVERAGE&lt;span class="o"&gt;(&lt;/span&gt;APP_EXECUTION_TIME&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;ms&lt;span class="o"&gt;)&lt;/span&gt; 27

&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; CHART
     326.00 ┼╮                                                                                                   
     293.40 ┤│                                                                                                   
     260.80 ┤╰───╮ ╭╮╭╮ ╭╮╭╮ ╭╮                                                                    
     228.20 ┤ │ ││││ ││││ ││                                                                    
     195.60 ┤ │ ││││ ││││ ││                                                                    
     163.00 ┤ │ ││││ ││││ ││                                                                    
     130.40 ┤ │ ││││ ││││ ││                                                                    
      97.80 ┤ │ ││││ ││││ ││                                                                    
      65.20 ┤ │ ││││ ││││ ││                                                                    
      32.60 ┤ │ ││││ ││││ ││                                                                    
       0.00 ┤ ╰─╯╰╯╰─╯╰╯╰───────────────╯╰─────────────────────────────────────────────────────────────────── 


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

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Partial log file from db-with-lru-fallback-redis-which-fails-halfway.log generated by running &lt;code&gt;npm run app-save-output-db-with-lru-fallback-redis-which-fails-halfway&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now when Redis fails, we don’t even notice it because all the users has already been cached in the LRU. This method reduces the dependency on Redis and made our demo application &lt;strong&gt;twice as fast&lt;/strong&gt; , going from ~60ms to ~30ms average execution time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not all data can be stored indefinitely
&lt;/h2&gt;

&lt;p&gt;In our example, we assumed that the user data does not change and can be cached forever. This is a luxury that most applications can’t make. Most of the time, you would store data in Redis with an expiration, using commands like &lt;code&gt;SET key data&lt;/code&gt; and &lt;code&gt;EXPIRE key 60&lt;/code&gt; or just &lt;code&gt;SETEX key 60 data&lt;/code&gt;. The data would then be removed from the cache after 60 seconds.&lt;/p&gt;

&lt;p&gt;Caching information even for short periods can have a huge impact on high throughput systems. The&lt;a href="https://github.com/isaacs/node-lru-cache"&gt;LRU library&lt;/a&gt; used in the examples also support setting data with an expiration time using &lt;code&gt;set(key, value, maxAge)&lt;/code&gt;. This means it is possible to set the value both in-memory and in Redis with the same TTL (Time To Live).&lt;/p&gt;

&lt;p&gt;One thing to watch out for is that if you get the data from Redis it has already been in the cache for a certain period of time. &lt;strong&gt;So you can not store the data in the LRU with the same TTL as when you stored it in Redis.&lt;/strong&gt; You can either:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use the &lt;code&gt;TTL&lt;/code&gt; Redis command to get the key’s remaining time&lt;/strong&gt; before expiration and set that in the LRU so that both expire at the same time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make a few tradeoffs&lt;/strong&gt; and set the TTL the same as for when you stored it in Redis. Then the maximum time that the item can exist in either Redis or the LRU becomes twice as much and there might be stale data for a certain period. This is better explained with an example:

&lt;ul&gt;
&lt;li&gt;We set the Redis item with TTL of 60 seconds&lt;/li&gt;
&lt;li&gt;We get the item from Redis on second 59 and set it in our LRU cache for 60 seconds. So the item is “alive” for 2 minutes instead of 1.&lt;/li&gt;
&lt;li&gt;Another process might query Redis and now see that the item does not exist/expired and fetches a new one and stores it. Now two versions of the same item exist across these two process, the first processes has the old version and the new process has the new version.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Another side note, only if you are digging deeper into this. The in-memory LRU library used in this project will check the expiration time when it fetches the object and if it is expired return undefined as if it was not found and deletes it from memory. There is no background process running in the background to check and proactively delete all keys that expired. The &lt;code&gt;prune()&lt;/code&gt; command can be called to check the TTL of all items and remove them if necessary, this is only needed in some scenarios where data is set with a TTL and hardly used after that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let’s talk about Lambda
&lt;/h2&gt;

&lt;p&gt;Lambda functions usually over-provision on memory to get proportional CPU and network throughput, leaving the memory unused. &lt;strong&gt;The majority of workloads are CPU intensive which leads to an over-provisioning of memory&lt;/strong&gt; to get proportional CPU which reduces execution time and cost.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/alexcasalboni/aws-lambda-power-tuning"&gt;AWS Lambda Power Tuning&lt;/a&gt; is a great open source tool to help you find the best memory setting to optimize cost &amp;amp; execution time. Many times the cheapest and fastest option is a higher memory setting.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tKjH6d5e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rehanvdm.com/images/blog/lru-cache-fallback-strategy/03_power_tools.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tKjH6d5e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rehanvdm.com/images/blog/lru-cache-fallback-strategy/03_power_tools.jpg" alt="Caching order" width="880" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Chart from the output of the AWS Lambda Power Tuning&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To prove my previous statement “The majority of workloads are CPU intensive” we observe that the same lambda &amp;amp; program that works with 1024MB also works on 128MB. The only thing that changed is that we have more CPU, network throughput and other proportionally scaled resources.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The unprovisioned memory can be used for caching data in-memory&lt;/strong&gt; instead of reaching for an external cache every time. This can reduce downstream pressure on these caches, reduce costs and speed up systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion (TL;DR)
&lt;/h2&gt;

&lt;p&gt;By leveraging an in-memory cache you reduce pressure on the downstream caches, reduce costs and speed up systems. Consider having an in-memory first policy, instead of having an in-memory fallback cache strategy to see the biggest gains.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>cache</category>
      <category>redis</category>
      <category>lambda</category>
    </item>
    <item>
      <title>Should you use microservices?</title>
      <dc:creator>Rehan van der Merwe</dc:creator>
      <pubDate>Sun, 14 Nov 2021 22:00:00 +0000</pubDate>
      <link>https://dev.to/rehanvdm/should-you-use-microservices-p5o</link>
      <guid>https://dev.to/rehanvdm/should-you-use-microservices-p5o</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Originally published on my blog: &lt;a href="https://www.rehanvdm.com/blog/should-you-use-microservices"&gt;https://www.rehanvdm.com/blog/should-you-use-microservices&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Microservices have been around for more than a decade and yet so many still don’t get it right. I am no different, I have created some pretty coupled architectures in my pursuit to build microservices. But similar to programming, having failed many times and knowing how not to do something is also a success.&lt;/p&gt;

&lt;p&gt;I recently &lt;a href="https://twitter.com/der_rehan/status/1450044694013022209"&gt;posted my decision tree to twitter&lt;/a&gt; for choosing between a Microservice architecture and a Monolith for a greenfield project. It blew up more than I thought it would, but I got some pretty good feedback, click on the above tweet or see the original image below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tY4sE55X--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rehanvdm.com/images/blog/should-you-use-microservices/0_1_oroginal.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tY4sE55X--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rehanvdm.com/images/blog/should-you-use-microservices/0_1_oroginal.png" alt="Revised diagram" width="880" height="705"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Original diagram&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The revised diagram (thank you internet)
&lt;/h2&gt;

&lt;p&gt;The revised diagram is a direct result of all the feedback I got. I could not fit it into a decision tree 🤷‍♂️ anymore, so have a table instead:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4yrsc26E--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rehanvdm.com/images/blog/should-you-use-microservices/2_monolith_microservice_architecture.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4yrsc26E--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rehanvdm.com/images/blog/should-you-use-microservices/2_monolith_microservice_architecture.jpg" alt="Revised diagram" width="729" height="463"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Revised diagram&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Important points raised
&lt;/h2&gt;

&lt;p&gt;Team size and communication plays a huge factor in choosing the right architecture.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Any organization that designs a system (defined broadly) will produce a design whose structure is a copy of the organization’s communication structure. - Melvin E. Conway (1967)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Bringing Conway’s law into this discussion, translates to multiple teams will gravitate towards microservices and a large singular team will tend to build monolithic software.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--m4SS5XPj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rehanvdm.com/images/blog/should-you-use-microservices/1_conways_law.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--m4SS5XPj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rehanvdm.com/images/blog/should-you-use-microservices/1_conways_law.jpg" alt="Conways Law" width="880" height="746"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://sketchplanations.com/conways-law"&gt;https://sketchplanations.com/conways-law&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you are doing microservices as a single team, then be prepared for the &lt;strong&gt;cognitive overhead&lt;/strong&gt; and discipline that is needed to properly implement microservices. Many times it does not make sense from a business perspective, you will most likely have less developing velocity and increased cost.&lt;/p&gt;

&lt;p&gt;Your &lt;strong&gt;engineering standards have to be really high to do microservices as a single team&lt;/strong&gt;. On the other hand, multiple teams where each team owns at least one service can quickly lead to &lt;strong&gt;disjointed communication if the right processes aren’t followed.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Serverless != Microservices&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Another misconception that I often see, is the belief that as soon as you are doing Serverless, you are doing Microservices. Just because you have 100’s of lambdas for your API does not mean you are doing Microservices. It is still a Monolith if it is a singular deployment with business logic shared between the lambdas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges of microservices
&lt;/h2&gt;

&lt;p&gt;In the original diagram I had limited space but decided to pad the empty space with some of the most important points, but could not include all of them. Here it is again, including what I didn’t mention and some that was also raised by others:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Schema versioning and management is difficult, you need to honor backward and forward compatibility.&lt;/li&gt;
&lt;li&gt;Transactions become distributed, needs to be done with the saga pattern, event choreography or event orchestration&lt;/li&gt;
&lt;li&gt;Data duplication and consistency make bulk changes (or “fixes”) to entities difficult. You can not just update all rows in a DB column. Events need to be emitted to other services as well&lt;/li&gt;
&lt;li&gt;Synchronous service communication introduce latency and are unavoidable at times. It is easy to inadvertently create long service call chains between your services, leading to over chatty and dependant services.&lt;/li&gt;
&lt;li&gt;Asynchronous service communication introduces eventual consistency, this usually requires systems to handle failures and be idempotent as well.&lt;/li&gt;
&lt;li&gt;End-to-end monitoring is difficult, distributed logging and tracing is now needed.&lt;/li&gt;
&lt;li&gt;Testing, especially E2E, becomes more difficult. Your environment X, let’s say pre-prod, needs to communicate with all other services in environment X with clean data for tests to pass.&lt;/li&gt;
&lt;li&gt;Reporting and aggregations become difficult, data needs to be pulled from many services.&lt;/li&gt;
&lt;li&gt;Each service needs its own CICD pipeline, testing, environments ect.
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Research
&lt;/h2&gt;

&lt;p&gt;Research and educate the team, at least have a shallow understanding of why these concepts exist and what problems they solve. Many of these points are not directly related to microservices but will be needed to implement microservices correctly.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Event sourcing and event storming&lt;/li&gt;
&lt;li&gt;Event choreography and event orchestration&lt;/li&gt;
&lt;li&gt;The saga pattern and the circuit breaker pattern&lt;/li&gt;
&lt;li&gt;Eventual consistency and the CAP theorem&lt;/li&gt;
&lt;li&gt;Event Carried State Transfer&lt;/li&gt;
&lt;li&gt;DDD: Domain Driven Design and bounded context&lt;/li&gt;
&lt;li&gt;CQRS: Command Query Responsibility Segregation
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I had wrong
&lt;/h2&gt;

&lt;p&gt;There are 2 concepts closely related and that is the Distributed monolith and the Modular monolith. The main difference is in how you structure your business logic and deploy. To sum it up, &lt;strong&gt;your architecture will fall within one of these categories:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Traditional monolith:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No boundaries between code, it is one application.&lt;/li&gt;
&lt;li&gt;Deployed as a single application.&lt;/li&gt;
&lt;li&gt;No physical boundaries.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Distributed monolith:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Loose Boundaries, sharing business logic between services.&lt;/li&gt;
&lt;li&gt;Services deployed independently (usually in the same repo, but can be multiple).&lt;/li&gt;
&lt;li&gt;Always has physical boundaries between them (think each service having their own compute or DB).
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Modular monolith:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Strict Boundaries, services might be sharing code(think utility functions) but not business logic.&lt;/li&gt;
&lt;li&gt;Services deployed together (usually in the same repo, but can be multiple).&lt;/li&gt;
&lt;li&gt;Might not have physical boundaries.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Microservice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Strict Boundaries.&lt;/li&gt;
&lt;li&gt;Services deployed independently.&lt;/li&gt;
&lt;li&gt;Always have physical boundaries.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The main difference between a distributed and modular monolith is that a &lt;strong&gt;distributed monolith&lt;/strong&gt; shares business logic between services, have independent service deployments and each service has their own physical boundaries and resources. This &lt;strong&gt;is the worst of the 4 categories.&lt;/strong&gt; Some signs of a distributed monolith include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Shared business logic like each service querying the authentication service DB directly for authentication.&lt;/li&gt;
&lt;li&gt;Long deployment times because each service needs to deploy independently.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Shoot for Microservice and fall on a Modular monolith if you have to.&lt;/strong&gt; They are usually my go-to for any project that is small to medium with a single team. Modular monoliths usually live within a single project, where each folder might be a service.&lt;/p&gt;

&lt;p&gt;Purists will argue that no code must be shared between services, but it usually happens that within a modular monolith utility functions are shared. As long as business logic stays within the boundaries of a service. They also share physical resources like DBs and are deployd together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion (TL;DR)
&lt;/h2&gt;

&lt;p&gt;Use the right architecture for the job, &lt;strong&gt;consider the non-technical requirements first&lt;/strong&gt; when deciding to use microservices. Does it fit with our company/project structure? Does the team have the required skills? Do I understand the business impact this will have?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;There is nothing wrong with a monolith&lt;/strong&gt; , you can still implement best practices and hit the targets set out by business. At some point your team will grow or usage pattern might change, then only consider the transition to microservices.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>microservices</category>
      <category>design</category>
      <category>eventdriven</category>
    </item>
    <item>
      <title>4 Methods to configure multiple environments in the AWS CDK</title>
      <dc:creator>Rehan van der Merwe</dc:creator>
      <pubDate>Sun, 24 Jan 2021 15:56:56 +0000</pubDate>
      <link>https://dev.to/rehanvdm/4-methods-to-configure-multiple-environments-in-the-aws-cdk-2fg7</link>
      <guid>https://dev.to/rehanvdm/4-methods-to-configure-multiple-environments-in-the-aws-cdk-2fg7</guid>
      <description>&lt;p&gt;In this post I will explore 4 different methods that can be used to pass configuration values to the AWS CDK. We will first look at using the &lt;strong&gt;context&lt;/strong&gt; variables in the cdk.json file, then move those same variables out to &lt;strong&gt;YAML&lt;/strong&gt; files. The third method will read the exact same config via &lt;strong&gt;SDK(API) call&lt;/strong&gt; from AWS SSM Parameter Store. The fourth and my favourite is a &lt;strong&gt;combination of two and three in conjunction with using GULP.js as a build tool&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The accompanying code for this blog can be found here: &lt;a href="https://github.com/rehanvdm/cdk-multi-environment"&gt;https://github.com/rehanvdm/cdk-multi-environment&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The CDK recommended method of Context
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--neCcgPKp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2021/01/cdk_multi_env_01.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--neCcgPKp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2021/01/cdk_multi_env_01.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first method follows the &lt;a href="https://docs.aws.amazon.com/cdk/latest/guide/context.html"&gt;recommended method&lt;/a&gt; of reading external variables into the CDK at build time. The main idea behind it is to have the &lt;strong&gt;configuration&lt;/strong&gt; values that determine what resources are being built, &lt;strong&gt;committed alongside your CDK code&lt;/strong&gt;. This way you are assured of repeatable and consistent deployments without side effects.&lt;/p&gt;

&lt;p&gt;There are few different ways to &lt;strong&gt;pass context values&lt;/strong&gt; into your CDK code. The first and easiest might be to use the context variables on the CDK CLI command line &lt;strong&gt;via&lt;/strong&gt; &lt;code&gt;--context&lt;/code&gt; &lt;strong&gt;or&lt;/strong&gt; &lt;code&gt;-c&lt;/code&gt; for short. Then in your code you can use &lt;code&gt;construct.node.tryGetContext(…)&lt;/code&gt; to get the value. Be sure to &lt;strong&gt;validate the returned values, TypeScripts (TS) safety won’t cut it for reading values at runtime&lt;/strong&gt;, more in the validation section at the end. Passing a lot of variables like this isn’t ideal so you can also populate the context from file.&lt;/p&gt;

&lt;p&gt;When you start a new project, every &lt;code&gt;cdk.json&lt;/code&gt; will have a context property with some values already populated that are used by the CDK itself. This was my first pain point with using this method, it just didn’t feel right to store parameters used by the CDK CLI in the same file as my application configuration (opinionated). Note that it is possible to also store the .json file in other places, please check out the official docs (link above) for more info.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://100.25.80.98/contents/data/2021/01/cdk_multi_env_02.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XJ0kx87n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2021/01/cdk_multi_env_02.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We are storing both development and production configuration values in the same file. Then when executing the CDK CLI commands we pass another context variable called config.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CW9AYF2r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2021/01/cdk_multi_env_03.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CW9AYF2r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2021/01/cdk_multi_env_03.png" alt=""&gt;&lt;/a&gt;Passing in a context variable to select the correct config within index.ts&lt;/p&gt;

&lt;p&gt;This is read within &lt;code&gt;index.ts&lt;/code&gt; and it &lt;strong&gt;chooses one of the available environment configurations&lt;/strong&gt; as defined in our &lt;code&gt;cdk.json&lt;/code&gt; file. It is all done inside the &lt;code&gt;getConfig(…)&lt;/code&gt; function, notice that we read each context value individually and assign them to our own &lt;code&gt;BuildConfig&lt;/code&gt; interface, located at &lt;code&gt;/stacks/lib/build-config.ts&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YLcH6ID5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2021/01/cdk_multi_env_03_02.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YLcH6ID5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2021/01/cdk_multi_env_03_02.png" alt=""&gt;&lt;/a&gt;/stacks/lib/build-config.ts&lt;/p&gt;

&lt;p&gt;An instance of the &lt;code&gt;buildConfig&lt;/code&gt; &lt;strong&gt;is then passed down to every stack&lt;/strong&gt; , of which we only have one in this example. We also add tags to the CDK app which will place them on every stack and resource when/if possible. Passing the region and account to the stack enables us to deploy that specific stack to other accounts and/or regions. Only if the &lt;code&gt;--profile&lt;/code&gt; argument passed in has the correct permissions for that account as well.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2021/01/cdk_multi_env_04.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AyO6D4-F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2021/01/cdk_multi_env_04-773x1024.png" alt=""&gt;&lt;/a&gt;Loading config and passing buildConfig to the MainStack in /index.ts [Click to enlarge]&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The next methods all have the exact same code and structure the only differences are the&lt;/strong&gt; &lt;code&gt;getConfig&lt;/code&gt; &lt;strong&gt;function and execution of CLI commands.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The MainStack (below) that we are deploying has a single Lambda in it, with a few ENV variables and the Lambda Insights Layer of which we all get from the config file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2021/01/cdk_multi_env_05.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DrYBKW9j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2021/01/cdk_multi_env_05-1024x822.png" alt=""&gt;&lt;/a&gt;/stacks/main.ts&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Read config from a YAML file
&lt;/h2&gt;

&lt;p&gt;With this method we &lt;strong&gt;split our application configuration&lt;/strong&gt; from the CDK context file and &lt;strong&gt;store it in multiple YAML files&lt;/strong&gt;. Where the &lt;strong&gt;name of the file indicates the environment.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2021/01/cdk_multi_env_06.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bg3L-y6P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2021/01/cdk_multi_env_06-1024x286.png" alt=""&gt;&lt;/a&gt;YAML config files&lt;/p&gt;

&lt;p&gt;Then a slight change in our &lt;code&gt;index.ts&lt;/code&gt; for the &lt;code&gt;getConfig&lt;/code&gt; function so that it reads and parses the new YAML files instead of the JSON from the context.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2021/01/cdk_multi_env_07.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wHMkAUr2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2021/01/cdk_multi_env_07.png" alt=""&gt;&lt;/a&gt;getConfig now reading from file and parsing YAML&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Read config from AWS SSM Parameter Store
&lt;/h2&gt;

&lt;p&gt;This method is &lt;strong&gt;not limited to just the AWS SSM Parameter Store&lt;/strong&gt; but &lt;strong&gt;any third-party API/SDK&lt;/strong&gt;  &lt;strong&gt;call&lt;/strong&gt; can be used to get config and plug it into the CDK build process.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2021/01/cdk_multi_env_08.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YSw8bV4B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2021/01/cdk_multi_env_08-567x1024.png" alt=""&gt;&lt;/a&gt;Loading config from AWS SSM Parameter Store&lt;/p&gt;

&lt;p&gt;The first “trick” is to &lt;strong&gt;wrap all the code inside an async function&lt;/strong&gt; , and then execute it. Now we can make full use of &lt;strong&gt;async/await&lt;/strong&gt; functions before the stack is created. Inside the &lt;code&gt;getConfig(…)&lt;/code&gt; function we now also require that the profile and region context variables be passed when executing the CLI commands.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2021/01/cdk_multi_env_09.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VP00Dhb5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2021/01/cdk_multi_env_09.png" alt=""&gt;&lt;/a&gt;Now we also need to pass the profile and region used by the AWS SDK&lt;/p&gt;

&lt;p&gt;This is so that we can set them to be used by the AWS SDK which in return makes authenticated API calls to AWS for us. We created the SSM Parameter Store record (below) with the exact same content as the YAML files. So that after retrieving it, we parse and populate the BuildConifg exactly the same as we did for the YAML files method.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2021/01/cdk_multi_env_ssm_03.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0wq8eSxC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2021/01/cdk_multi_env_ssm_03.png" alt=""&gt;&lt;/a&gt;AWS SSM Parameter Store record storing config as YAML&lt;/p&gt;

&lt;p&gt;This method has the advantage that your &lt;strong&gt;configuration file is now independent of any project&lt;/strong&gt; , is stored in a single location and can even be used by multiple projects. Storing the complete project config like this is a bit &lt;strong&gt;unorthodox&lt;/strong&gt; and not something that you will do often. You would &lt;strong&gt;ideally store most of the config on a project level and then pull a few global values used by all projects&lt;/strong&gt; , more on this in the next method.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Make use of an external build script with both local and global config
&lt;/h2&gt;

&lt;p&gt;In this example make use of method 3 and 4 above by having:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Project config&lt;/strong&gt; (YAML file), for this project, including AWS profile and region.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A global config&lt;/strong&gt; (AWS SSM Parameter Store) to be used by all projects.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We only &lt;strong&gt;store the Lambda Insight Layer ARN in our global config&lt;/strong&gt; which is AWS SSM Parameter store. So that when AWS releases a new version of the layer, we can just update it in our global config once and &lt;strong&gt;all projects will update their usage of it&lt;/strong&gt; the next time they are deployed.&lt;/p&gt;

&lt;p&gt;We are using of a &lt;strong&gt;GULP.js&lt;/strong&gt; script and executing it with Node. It basically &lt;strong&gt;does the following&lt;/strong&gt; :&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Reads the local YAML config file, depending on the environment, this defaults to the branch name. &lt;/li&gt;
&lt;li&gt;Get the AWS SSM Parameter Name (from the local config) which holds the global config. Fetch the global config and add to the local config. &lt;/li&gt;
&lt;li&gt;Validate the complete configuration, with JSON Schema using the AJV package.&lt;/li&gt;
&lt;li&gt;Write the complete config to file to disk so that it is committed with the repo.&lt;/li&gt;
&lt;li&gt;Run npm build to transpile the CDK TS to JS.&lt;/li&gt;
&lt;li&gt;Build and execute the CDK command by passing arguments like the AWS profile and config context variable. When the CDK is synthesised to CloudFormation in the &lt;code&gt;index.ts&lt;/code&gt;, just like before in method 2, it will read the complete config that we wrote to disk at step 4.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2021/01/cdk_multi_env_11.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--o9szuTiV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2021/01/cdk_multi_env_11.png" alt=""&gt;&lt;/a&gt;The generateConfig method in the /config/gulpfile.js&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2021/01/cdk_multi_env_12.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hdeB8D6H--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2021/01/cdk_multi_env_12-1024x313.png" alt=""&gt;&lt;/a&gt;The gulp task that builds the CDK, gets the config and runs the diff CDK CLI command&lt;/p&gt;

&lt;p&gt;Now instead of running &lt;code&gt;npm run cdk-diff-dev&lt;/code&gt;, we run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node node_modules\gulp\bin\gulp.js --color --gulpfile config\gulpfile.js generate_diff

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

&lt;/div&gt;



&lt;p&gt;and for deploying:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node node_modules\gulp\bin\gulp.js --color --gulpfile config\gulpfile.js deploy_SKIP_APPROVAL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that we &lt;strong&gt;don’t pass the environment&lt;/strong&gt; in these commands and &lt;strong&gt;let it default to the branch name&lt;/strong&gt; , with the exception that if on the master branch it uses the prod config. The getConfig(…) function within the GULP.js file allows for this to be passed down explicitly. This deployment method also works on CI tools.&lt;/p&gt;

&lt;p&gt;The getConfig function used in the &lt;code&gt;index.ts&lt;/code&gt; is similar to method 2, except that it does validation using AJV and JSON Schema (see section below on validation).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2021/01/cdk_multi_env_14.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VhbnZR7t--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2021/01/cdk_multi_env_14-1024x508.png" alt=""&gt;&lt;/a&gt;The getConfig(…) function inside index.ts&lt;/p&gt;

&lt;p&gt;One of the &lt;strong&gt;biggest advantages of using a GULP.js&lt;/strong&gt; file and executing it with Node is that it &lt;strong&gt;makes our deployment process operating system (OS) independent&lt;/strong&gt;. This is important to me since I am on Windows and most people always write Make and Bash scripts forcing me to use the Ubuntu WSL2.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;One of the biggest advantages of using a GULP.js file and executing it with Node is that it makes our deployment process OS independent.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This deployment process is quite versatile. I have used this GULP.js method from before I was using Infrastructure as Code (IaC) tools, back when we only wanted to update Lambda code. Some form of it has since been used to deploy CloudFormation , then SAM and now the AWS CDK.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few words about:
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Validation
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;TypeScript only does compile time checking,&lt;/strong&gt; which means it does not know if that YAML/JSON that you are decoding is actually a string or defined at runtime. Thus, we need to manually &lt;strong&gt;verify and put safe guards in place at runtime.&lt;/strong&gt; Method 1 through 3 just did a basic check within the &lt;code&gt;index.ts&lt;/code&gt; using function &lt;code&gt;ensureString(…)&lt;/code&gt; where the config is read.&lt;/p&gt;

&lt;p&gt;For this method we are using a slightly more advance approach. The &lt;a href="https://www.npmjs.com/package/ajv"&gt;AJV&lt;/a&gt;package &lt;strong&gt;validates a JSON object against the JSON Schema of our BuildConfig&lt;/strong&gt; file. This way we can write a single schema file that defines rules like ensuring certain properties are set and start with the correct AWS ARN.&lt;/p&gt;

&lt;p&gt;Writing JSON Schema and keeping it up to date is cumbersome, that is why we opted to use the &lt;a href="https://www.npmjs.com/package/json-schema-to-typescript"&gt;typescript-json-schema&lt;/a&gt; package. It converts our already existing TypeScript BuildConfig interface (at &lt;code&gt;/stacks/lib/build-config.ts&lt;/code&gt;) into a JSON Schema and stores it in the config directory at &lt;code&gt;/config/schema.json&lt;/code&gt;. Now when the GULP.js and &lt;code&gt;index.ts&lt;/code&gt; files read the config, they both validate it against this JSON Schema.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2021/01/cdk_multi_env_13.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fwQWp3UA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2021/01/cdk_multi_env_13-1024x694.png" alt=""&gt;&lt;/a&gt;BuildConfig TS Interface converted to JSON Schema&lt;/p&gt;

&lt;h3&gt;
  
  
  Project structure
&lt;/h3&gt;

&lt;p&gt;If you are following along with the code, you will also notice that I don’t structure my CDK projects like the initial/standard projects.&lt;/p&gt;

&lt;p&gt;This again is &lt;strong&gt;opinionated&lt;/strong&gt; , but &lt;strong&gt;the initial structure doesn’t seem logical&lt;/strong&gt; to me and doesn’t always work for every project.&lt;/p&gt;

&lt;p&gt;All stacks go into &lt;code&gt;/stacks&lt;/code&gt;, the main CDK construct is on the root as &lt;code&gt;index.ts&lt;/code&gt; and all &lt;strong&gt;application specific code goes into&lt;/strong&gt; &lt;code&gt;/src&lt;/code&gt;. The &lt;code&gt;/src&lt;/code&gt; dir will have sub directories for things like &lt;code&gt;/lambda&lt;/code&gt;, &lt;code&gt;/docker&lt;/code&gt;, &lt;code&gt;/frontend&lt;/code&gt; as long as it makes logical sense. Then not displayed here is the sometimes needed &lt;code&gt;/build&lt;/code&gt; &lt;strong&gt;dir&lt;/strong&gt;  &lt;strong&gt;where the&lt;/strong&gt; &lt;code&gt;/src&lt;/code&gt; &lt;strong&gt;code gets built for production&lt;/strong&gt; and stored. The CDK then reads from the &lt;code&gt;/build&lt;/code&gt; instead of&lt;code&gt;/src&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion ( TL;DR )
&lt;/h2&gt;

&lt;p&gt;The accompanying code for this blog can be found here: &lt;a href="https://github.com/rehanvdm/cdk-multi-environment"&gt;https://github.com/rehanvdm/cdk-multi-environment&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are many different ways to store config for a CDK project. With my favourite being the last method of storing them as YAML files at project level and using a GULP.js script as a build tool. Which ever method you choose, always remember to validate the inputs.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cdk</category>
      <category>typescript</category>
      <category>node</category>
    </item>
    <item>
      <title>CloudFront reverse proxy API Gateway to prevent CORS</title>
      <dc:creator>Rehan van der Merwe</dc:creator>
      <pubDate>Tue, 20 Oct 2020 20:10:50 +0000</pubDate>
      <link>https://dev.to/rehanvdm/cloudfront-reverse-proxy-api-gateway-to-prevent-cors-449e</link>
      <guid>https://dev.to/rehanvdm/cloudfront-reverse-proxy-api-gateway-to-prevent-cors-449e</guid>
      <description>&lt;p&gt;In this blog we will do a quick recap of CORS and reverse proxies. Then we will show how a reverse proxy can eliminate CORS, specifically in the context of a SPA hosted on CloudFront with an API Gateway backend. The sample code focuses on public, authenticated routes (Authorization header) and IAM signed request all being reverse proxied through CloudFront. Everything is done with the AWS CDK and can be found here =&amp;gt; &lt;a href="https://github.com/rehanvdm/cloudfront-reverse-proxy-apigw"&gt;https://github.com/rehanvdm/cloudfront-reverse-proxy-apigw&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This article was originally published on my &lt;a href="https://www.rehanvdm.com/serverless/cloudfront-reverse-proxy-api-gateway-to-prevent-cors/index.html"&gt;blog&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I wonder if it is safe to assume that every developer that has worked with an API, knows what CORS is. I bet you are here because like many others you have lost countless hours against the battle to properly implement CORS.&lt;/p&gt;

&lt;p&gt;Pre-requisites and assumptions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You have basic knowledge of AWS services like CloudFront, S3, Lambda, API GW and IAM.&lt;/li&gt;
&lt;li&gt;If you are following the code samples, that you may already have the AWS SDK and CDK installed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Quick CORS intro
&lt;/h2&gt;

&lt;p&gt;CORS stands for &lt;strong&gt;Cross-Origin Resource Sharing&lt;/strong&gt; , it restricts a web application running on one &lt;strong&gt;origin (protocol &amp;amp; domain &amp;amp; port)&lt;/strong&gt; from accessing resources on a different origin.&lt;/p&gt;

&lt;p&gt;The browser will first have to do what is known as a &lt;strong&gt;preflight&lt;/strong&gt; request. It sends an &lt;strong&gt;OPTIONS&lt;/strong&gt; request to the target, asking if it is allowed and willing to accept the actual request. This way the target domain can decide who and what the trusted sources are.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/10/cf_reverse_proxy_01_wikipedia_logic.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--R1-c1E4K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2020/10/cf_reverse_proxy_01_wikipedia_logic-1024x528.png" alt=""&gt;&lt;/a&gt;CORS logic, credits &lt;a href="https://en.wikipedia.org/wiki/File:Flowchart_showing_Simple_and_Preflight_XHR.svg%20" rel="noreferrer noopener"&gt;Wikipedia&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This results in &lt;strong&gt;two separate requests for every request that you make&lt;/strong&gt; , which really makes for a &lt;strong&gt;terrible user experience especially for users that are not close to your origin&lt;/strong&gt;. To give you a better idea, here in South Africa the round trip to &lt;em&gt;us-east-1&lt;/em&gt; is anything from 400 to 800ms on a decent connection. With the latency that CORS adds, you can expect a basic request in our region, as observed by the user to be between 800 and 1200ms. *&lt;em&gt;Eliminating CORS would almost yield a 50% increase in API latency. *&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/10/cf_reverse_proxy_02_mozilla_double_request.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6kKJwi_Y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2020/10/cf_reverse_proxy_02_mozilla_double_request.png" alt=""&gt;&lt;/a&gt; &lt;br&gt;CORS flow showing latency, credits &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" rel="noreferrer noopener"&gt;Mozilla&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The only way to eliminate CORS and prevent the preflight requests is to have both the frontend and the backend on the same origin&lt;/strong&gt;.  This problem basically does not exist in a traditional application where both the frontend and the backend are on the same server and thus origin.&lt;/p&gt;

&lt;p&gt;It is only when your frontend and backend are on different origins like &lt;strong&gt;in our case the frontend is running as a SPA on CloudFront and the backend is done using API Gateway&lt;/strong&gt;. A reverse proxy solves this by allowing the frontend to call a path on its origin that forwards the request to API Gateway.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does a Reverse Proxy Solve?
&lt;/h2&gt;

&lt;p&gt;A reverse proxy &lt;strong&gt;requests resources on behalf of the current origin from one or more of the target origins.&lt;/strong&gt; These resources are &lt;strong&gt;returned to the client appearing as if they originated from the current origin&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is better understood visually, in the image below, CORS will be present as the frontend code makes requests to API Gateway directly to interact with the backend.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6d_wlyXw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2020/10/cf_reverse_proxy_04_cors_setup-1024x454.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6d_wlyXw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2020/10/cf_reverse_proxy_04_cors_setup-1024x454.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The solution here is to set CloudFront up as a reverse proxy on let’s say path &lt;em&gt;/backend-api/*&lt;/em&gt; so that whenever data is sent to &lt;em&gt;/backend-api/*&lt;/em&gt;, it is sent to the API Gateway. The frontend code then needs to make requests to itself (the origin it uses) at path &lt;em&gt;/backend-api&lt;/em&gt; instead of using the different origin that is API Gateway.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Ee8SWzyM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2020/10/cf_reverse_proxy_05_no_cors_setup.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ee8SWzyM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2020/10/cf_reverse_proxy_05_no_cors_setup.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CloudFront acts as both a CDN and a reverse proxy&lt;/strong&gt;. The benefits that we gain from having this specific CloudFront setup includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No CORS preflight request&lt;/strong&gt; is needed, both frontend and backend API are on the same origin. Thus an &lt;strong&gt;approximate 50% decrease in API request latency.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More consistent (and usually faster) API request routing.&lt;/strong&gt; From a user perspective, the API requests will hit the closest CloudFront Point-of-Presence(POP) and then &lt;strong&gt;traverse to API Gateway on the AWS backbone network&lt;/strong&gt; as opposed to traversing the public internet to the API Gateway.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can also leverage other functionality, for example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Terminate HTTPS at CloudFront and send data using HTTP to the backend, saving backend resources from doing the computationally expensive SSL dance.&lt;/li&gt;
&lt;li&gt;Response compression.&lt;/li&gt;
&lt;li&gt;Independent request caching available that can be set using the backend.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I am not going into further details about why/what and how CORS &amp;amp; Reverse Proxies work, this should be sufficient information for the rest of the post. I linked to additional resources at the end of the post if you want to do further reading.&lt;/p&gt;

&lt;h2&gt;
  
  
  Show me the codes!
&lt;/h2&gt;

&lt;p&gt;The project has a stock standard &lt;strong&gt;CDK&lt;/strong&gt; layout and is written in &lt;strong&gt;TypeScript&lt;/strong&gt; , while the backend &lt;strong&gt;Lambdas&lt;/strong&gt; are written &lt;strong&gt;JavaScript&lt;/strong&gt;. The frontend is basic HTML and JavaScript, there is both a &lt;em&gt;/src&lt;/em&gt; and a &lt;em&gt;/dist&lt;/em&gt; folder for the frontend as one of the libraries needed is bundled using &lt;em&gt;browsify&lt;/em&gt;. The complete project can be found here =&amp;gt; &lt;a href="https://github.com/rehanvdm/cloudfront-reverse-proxy-apigw"&gt;https://github.com/rehanvdm/cloudfront-reverse-proxy-apigw&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/10/cf_reverse_proxy_06_project_setup_apigw.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fwePmofd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2020/10/cf_reverse_proxy_06_project_setup_apigw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our CloudFront has a specific behavior to forward all requests at path _ &lt;strong&gt;/cf-apigw&lt;/strong&gt; _ &lt;strong&gt;to our API Gateway domain&lt;/strong&gt; , it is very important that we use the API Gateway stage as the origin path. This configuration eliminates CORS as the frontend no longer has to call the API Gateway directly but just a path on the same frontend domain.&lt;/p&gt;

&lt;p&gt;The majority of the time you don’t want to integrate directly to an API Gateway domain so that it can be treated as ephemeral when you are doing Infrastructure as Code (IaC). Most scenarios front API Gateway with an API Gateway Custom domain that you own which then forwards to the AWS API Gateway domain and stage.&lt;/p&gt;

&lt;p&gt;I have also included this as a different path on the same CloudFront. This path being _ &lt;strong&gt;/cf-cust-domain&lt;/strong&gt; _ &lt;strong&gt;which will forward all requests to the custom domain which in return forwards it to the actual API Gateway&lt;/strong&gt;. Keep in mind that the API Gateway Custom domain service is a “specially” designed CloudFront that AWS controls for you.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/10/cf_reverse_proxy_07_project_setup_custom_domain.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Qj2XUb3a--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2020/10/cf_reverse_proxy_07_project_setup_custom_domain.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The main takeaway from these diagrams is to show the configuration for the CloudFronts and what path it will result in when it hits your API Gateway. If you haven’t figured it out already, &lt;strong&gt;CloudFront prepends the behavior path pattern to the path that it is forwarding&lt;/strong&gt;. This means that we need the whole API to be beneath a resource with the same name as the path pattern. This &lt;strong&gt;logic holds true for anything that you reverse proxy through CloudFront.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Below are a few screenshots of what the AWS console looks like for these resources configured with the CDK in the above setup. I am &lt;strong&gt;only showing the API Gateway Custom domain setup&lt;/strong&gt; as it is the trickier of the two to get right.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XgZzieII--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2020/10/cf_reverse_proxy_08_setup_origin.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XgZzieII--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2020/10/cf_reverse_proxy_08_setup_origin.png" alt=""&gt;&lt;/a&gt;CloudFront Origin for behavior&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--K6oWvbwo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2020/10/cf_reverse_proxy_09_setup_behavior_1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--K6oWvbwo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2020/10/cf_reverse_proxy_09_setup_behavior_1.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ye-6vnQ9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2020/10/cf_reverse_proxy_09_setup_behavior_2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ye-6vnQ9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2020/10/cf_reverse_proxy_09_setup_behavior_2.png" alt=""&gt;&lt;/a&gt;CloudFront Behavior&lt;/p&gt;

&lt;p&gt;Be sure to &lt;strong&gt;allow ALL methods for your API&lt;/strong&gt; as you would need the full range when implementing REST. Since we authenticated paths, you &lt;strong&gt;must whitelist the Authorization header&lt;/strong&gt;. Never whitelist the Host header, the second CloudFront(Custom Domain) will just refuse the request.&lt;/p&gt;

&lt;p&gt;I am effectively &lt;strong&gt;disabling the caching for this behavior by setting all the TTL values to 0&lt;/strong&gt;. The backend processing, in our case Lambda, can respond with the appropriate caching headers and CloudFront will apply them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fKOY6pDJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2020/10/cf_reverse_proxy_10_setup_custom_domain.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fKOY6pDJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2020/10/cf_reverse_proxy_10_setup_custom_domain.png" alt=""&gt;&lt;/a&gt;API Gateway custom domain&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6u0U0RYS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2020/10/cf_reverse_proxy_11_setup_apigw_stage.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6u0U0RYS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2020/10/cf_reverse_proxy_11_setup_apigw_stage.png" alt=""&gt;&lt;/a&gt;API Gateway prod stage&lt;/p&gt;

&lt;p&gt;This is the &lt;strong&gt;important&lt;/strong&gt; part, especially if you are not proxying all requests to the same Lambda. &lt;strong&gt;All of your API resources now need to live under the same path as the CloudFront behavior path&lt;/strong&gt; , in our case, it is /cf-cust-domain. &lt;strong&gt;All of these methods hit the same Lambda function that just echoes back the event object as received by the Lambda.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I want to focus on 3 different requests as made from CloudFront:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;/cf-cust-domain/no-auth&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This route will &lt;strong&gt;not specifically match anything&lt;/strong&gt; that we defined in our API Gateway stage above, thus it will &lt;strong&gt;fall under the /{proxy+} path&lt;/strong&gt; under the root. I specifically set up this “catch undefined routes” resource to debug and get my head around the problem. This is also how I am testing unauthenticated routes.&lt;/p&gt;

&lt;p&gt;By inspecting the browser web console, we can see the responses for this request, observe the path that the Lambda saw and what resource was hit on API Gateway:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iwikncYP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2020/10/cf_reverse_proxy_12_path_auth.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iwikncYP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2020/10/cf_reverse_proxy_12_path_auth.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;/cf-cust-domain/auth&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This route does have a resource and &lt;strong&gt;method defined which is set up to use a Lambda Token Authorizer&lt;/strong&gt; with the Token Source set to the Authorization header. We can see from the response that this route resource was hit.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5QnEUjP5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2020/10/cf_reverse_proxy_12_path_proxy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5QnEUjP5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2020/10/cf_reverse_proxy_12_path_proxy.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can also inspect and see the authorizer that is attached values that have been added by the stock standard &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html"&gt;AWS example of the Lambda Token Authorizer&lt;/a&gt; that I implemented. The request included an &lt;strong&gt;Authorization header with the value of allow&lt;/strong&gt; as per example, this allowed the authorizer to pass.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;/cf-cust-domain/auth-iam&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the last and most complicated route, the method on &lt;strong&gt;API Gateway has Auth set to AWS_IAM&lt;/strong&gt;. This requires you to &lt;strong&gt;first sign the request&lt;/strong&gt; with your current IAM profile/role &lt;strong&gt;before making the request and then adding the signing headers&lt;/strong&gt; when you make the request, you can read more about this &lt;a href="https://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html"&gt;here&lt;/a&gt;and &lt;a href="https://docs.aws.amazon.com/general/latest/gr/signing_aws_api_requests.html"&gt;here&lt;/a&gt;. I used the &lt;a href="https://www.npmjs.com/package/aws4"&gt;aws4&lt;/a&gt;npm package to do the signing process for me as &lt;a href="https://github.com/mhart/aws4/tree/cfbf3e38012ab82be48518048d1ed87fbca5bf5a/browser"&gt;per browser example&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The function that we have been using so far looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zzqQU9Jd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2020/10/cf_reverse_proxy_13_request_basic.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zzqQU9Jd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2020/10/cf_reverse_proxy_13_request_basic.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The one that includes the signed headers that we must use for IAM auth looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/10/cf_reverse_proxy_13_request_signed.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vyXrST56--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2020/10/cf_reverse_proxy_13_request_signed.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You still require the Custom domain &amp;amp; path mapping&lt;/strong&gt; _ &lt;strong&gt;OR&lt;/strong&gt; _ &lt;strong&gt;the API Gateway domain &amp;amp; stage for signing the request&lt;/strong&gt;. You can then make the request as per usual. Below are all the requests made to both CloudFront paths, more info can be found under &lt;em&gt;/src/frontend/src/index.js&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/10/cf_reverse_proxy_14_requesting.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FgfS0OXG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2020/10/cf_reverse_proxy_14_requesting.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Result
&lt;/h2&gt;

&lt;p&gt;Navigating to the CloudFront domain that was created by the CDK stack will greet you with this very basic html page to capture the details required to make the requests above.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zze4rmg1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2020/10/cf_reverse_proxy_15_basic_form.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zze4rmg1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2020/10/cf_reverse_proxy_15_basic_form.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I left the default values for example only, please change these if you are following along in code. &lt;strong&gt;Running this on my localhost&lt;/strong&gt; means I have a &lt;strong&gt;different domain than the CloudFront&lt;/strong&gt; one, the browser will thus send OPTIONS requests as per CORS specification. I left CORS enabled on the API Gateway with very permissive values of all (or *) so that we can compare the result while running on our localhost during development.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/10/cf_reverse_proxy_16_local_run.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VqbDJMtR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2020/10/cf_reverse_proxy_16_local_run-1024x402.png" alt=""&gt;&lt;/a&gt;Requests made from localhost origin&lt;/p&gt;

&lt;p&gt;You can see the impact CORS has on latency by inspecting the Waterfall, the &lt;strong&gt;GET request is made and is then blocked until the OPTIONS request informs the browser to continue with the GET request.&lt;/strong&gt; The same requests on the CloudFront site results in this network output:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pELbphn---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2020/10/cf_reverse_proxy_17_cloudfront_run-1024x309.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pELbphn---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2020/10/cf_reverse_proxy_17_cloudfront_run-1024x309.png" alt=""&gt;&lt;/a&gt;Requests made from CloudFront origin&lt;/p&gt;

&lt;p&gt;We see that there are &lt;strong&gt;no more OPTIONS request being made, success!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For those who might be interested in the full project code, it can be found here =&amp;gt; &lt;a href="https://github.com/rehanvdm/cloudfront-reverse-proxy-apigw"&gt;https://github.com/rehanvdm/cloudfront-reverse-proxy-apigw&lt;/a&gt;. Find below for those curious in the &lt;strong&gt;200 line CDK stack that produced the 1500 lines CloudFormation&lt;/strong&gt; template for this example.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/10/cf_reverse_proxy_18_complete_cdk.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JFoyk99r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2020/10/cf_reverse_proxy_18_complete_cdk.png" alt=""&gt;&lt;/a&gt;Click to enlarge&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion (TL;DR)
&lt;/h2&gt;

&lt;p&gt;We showed you can use CloudFront to reverse proxy to your backend on API Gateway. This eliminates CORS which can hugely decrease request latency up to 50%. You have no excuse to not prevent CORS, if you control both the backend and frontend, every technology and framework has some concept of a reverse proxy.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Final note, you can use Lambda@Edge to remove the path that CloudFront prepends when it forwards the request. This adds extra latency to the request, but it will be much less than a round trip. My recommendation is to create a completely new API Gateway and API Custom domain to be used by CloudFront that is the exact same copy as used by the original Custom domain. This is becomes really easy if you are using an IaC tool.&lt;/em&gt;&lt;/p&gt;




&lt;h4&gt;
  
  
  Additional Resources:
&lt;/h4&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://javascript.info/fetch-crossorigin"&gt;https://javascript.info/fetch-crossorigin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Cross-origin_resource_sharin"&gt;https://en.wikipedia.org/wiki/Cross-origin_resource_sharin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS"&gt;https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Reverse Proxy:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.cloudflare.com/learning/cdn/glossary/reverse-proxy/"&gt;https://www.cloudflare.com/learning/cdn/glossary/reverse-proxy/&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Reverse_proxy"&gt;https://en.wikipedia.org/wiki/Reverse_proxy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.imperva.com/learn/performance/reverse-proxy/"&gt;https://www.imperva.com/learn/performance/reverse-proxy/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>cdk</category>
    </item>
    <item>
      <title>Refactoring a distributed monolith to microservices</title>
      <dc:creator>Rehan van der Merwe</dc:creator>
      <pubDate>Thu, 30 Jul 2020 04:28:50 +0000</pubDate>
      <link>https://dev.to/aws-heroes/refactoring-a-distributed-monolith-to-microservices-1g02</link>
      <guid>https://dev.to/aws-heroes/refactoring-a-distributed-monolith-to-microservices-1g02</guid>
      <description>&lt;p&gt;This article documents the thought process and steps involved in refactoring a distributed monolith to microservices. We are going to remove API GW, use Amazon Event Bridge and implement BASE consistency in the system to truly decouple our microservices.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This blog is also available as a presentation. Reach out if you would like me to present it at an event.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I will use the codebase from the previous installment to the series that can be found &lt;a href="https://www.rehanvdm.com/general/aws-serverless-you-might-not-need-third-party-monitoring/index.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;. The first part focuses on creating the codebase and implementing AWS native observability, monitoring and alerting services. As always you can find the code that we used in the respective GitHub repositories over here: &lt;a href="https://github.com/rehanvdm/MicroService" rel="noopener noreferrer"&gt;https://github.com/rehanvdm/MicroService&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Original System
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://www.rehanvdm.com/general/aws-serverless-you-might-not-need-third-party-monitoring/index.html" rel="noopener noreferrer"&gt;original system&lt;/a&gt; is a distributed monolith that consists of three microservices.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/07/refactor_dist_mon_01_SystemArchitecture.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rehanvdm.com%2Fcontents%2Fdata%2F2020%2F07%2Frefactor_dist_mon_01_SystemArchitecture-1024x715.png"&gt;&lt;/a&gt;Original system (Click to enlarge)&lt;/p&gt;

&lt;p&gt;Within each project you can find an &lt;strong&gt;OpenAPI&lt;/strong&gt; (&lt;a href="https://github.com/rehanvdm/MicroServicePerson/blob/master/part1/src/lambda/api/api-definition.yaml" rel="noopener noreferrer"&gt;part2/src/lambda/api/api-definition.yaml&lt;/a&gt;) file that defines the API definition for each service. &lt;strong&gt;AWS CDK&lt;/strong&gt; is used and they all follow the similar stock standard CDK project layout: Typescript for the CDK and &lt;strong&gt;ES6 JS&lt;/strong&gt; for the application code. NPM commands have been written to do deployments and it also contains &lt;strong&gt;end-to-end tests&lt;/strong&gt; using Mocha and Chai. In addition, each service contains a detailed README inside the /part2 path. Note that I only have a single Lambda for the API endpoint and do internal routing. Yes, I believe in a &lt;strong&gt;Lambalith for the API&lt;/strong&gt;!😊 and also prefer JSON POST over REST (more about this later).&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;problem&lt;/strong&gt; arises as soon as these microservices start to call one another. We will focus on the creation of a new person to demonstrate how this &lt;strong&gt;tight coupling&lt;/strong&gt; is working.&lt;/p&gt;

&lt;p&gt;The client service stores clients and has basic &lt;em&gt;create-client&lt;/em&gt; and &lt;em&gt;find-client&lt;/em&gt; functionalities as well as an endpoint to increment the person count for a specific client. The person service also has basic &lt;em&gt;create-person&lt;/em&gt; and &lt;em&gt;find-person&lt;/em&gt; endpoints. When a person is created, it calls the common service which notifies me by email about the new person that was added using an SNS subscription. The common service first needs to do a lookup on the client service so that it can enrich the email. It also increments the counter on the client. Click on the image below to see the step-by-step path for creating a person:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/07/refactor_dist_mon_02_SystemArchitecturePathFocus.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rehanvdm.com%2Fcontents%2Fdata%2F2020%2F07%2Frefactor_dist_mon_02_SystemArchitecturePathFocus-1024x553.png"&gt;&lt;/a&gt; &lt;br&gt;Original system – full create person flow (Click to enlarge) &lt;/p&gt;

&lt;p&gt;The &lt;em&gt;create-person&lt;/em&gt; call is highly dependent on the common service and does not even know that the common service is dependent on the client service. As a result, the person service is also dragged down if either the common or the client service is down. Not to mention that it now has to wait for the completion of every step in the synchronous chain. This wastes money and increases the probability of hitting the API Gateway timeout of 29 seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decoupling with Amazon Event Bridge
&lt;/h2&gt;

&lt;p&gt;Amazon Event Bridge is a serverless event bus that makes it easy to work with &lt;strong&gt;event-driven architectures&lt;/strong&gt;. It works on a basic &lt;strong&gt;publish and subscribe&lt;/strong&gt; model. We use it to emit certain events like: &lt;em&gt;person-created&lt;/em&gt; and &lt;em&gt;client-created.&lt;/em&gt; Other services can then listen to only the events that they want and act on it. The new system is refactored to incorporate this and remove the direct HTTP API calls between services.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/07/refactor_dist_mon_03_EVentBridge.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rehanvdm.com%2Fcontents%2Fdata%2F2020%2F07%2Frefactor_dist_mon_03_EVentBridge-1024x522.png"&gt;&lt;/a&gt; &lt;br&gt;New system (Click to enlarge) &lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;client service&lt;/strong&gt; has not changed much. It is stillfronted with API Gateway (GW). It now emits an event onto the bus whenever aclient is created. A new Lambda function is added that listens to the &lt;em&gt;create-person&lt;/em&gt; events. This increments the person counter for that specific client.This feature was previously on the common service but has now moved to theclient service.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;person service&lt;/strong&gt; is working exactly as before. Justlike the client service, it also emits an event onto the event bus,specifically the &lt;em&gt;create-person&lt;/em&gt; event.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;common service&lt;/strong&gt; no longer needs to be fronted byAPI GW. Instead it listens to both the &lt;em&gt;create-client&lt;/em&gt; and _create-person_events. The common service stores the client data in its own DynamoDB table. Ituses this to look the client up within itself (locally), rather than calling anHTTP API to get the data for a specific client. The common service still sendsan email when a new person is added.&lt;/p&gt;

&lt;p&gt;From an &lt;strong&gt;external integration point of view&lt;/strong&gt; , all API endpoints stayed exactly the same. The diagrams below clearly illustrate that each service is only concerned with its own data and responsibilities. The event that is emitted onto the bus is also added for convenience.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/07/refactor_dist_mon_04_PartialPerson.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rehanvdm.com%2Fcontents%2Fdata%2F2020%2F07%2Frefactor_dist_mon_04_PartialPerson-1024x711.png"&gt;&lt;/a&gt; &lt;br&gt;New system – partial create person flow (Click to enlarge) &lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/07/refactor_dist_mon_05_PartialClient.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rehanvdm.com%2Fcontents%2Fdata%2F2020%2F07%2Frefactor_dist_mon_05_PartialClient-1024x715.png"&gt;&lt;/a&gt; &lt;br&gt;New system – partial client person flow (Click to enlarge) &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Internally&lt;/strong&gt; the common service has an Event Bridge Rule with the Lambda function as target. It listens to the &lt;em&gt;create-client&lt;/em&gt; events and then stores only the client-id and client name fields within its own DynamoDB table. This &lt;strong&gt;removes the need for it to do an HTTP API call&lt;/strong&gt; to the client service as it can now just do the lookup locally against its own data store.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/07/refactor_dist_mon_06_FullClient.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rehanvdm.com%2Fcontents%2Fdata%2F2020%2F07%2Frefactor_dist_mon_06_FullClient-1024x529.png"&gt;&lt;/a&gt; &lt;br&gt;New system – full create client flow (Click to enlarge) &lt;/p&gt;

&lt;p&gt;The common service also listens to the &lt;em&gt;create-person&lt;/em&gt; event. It &lt;strong&gt;looks up the client information in its own DynamoDB table&lt;/strong&gt; and then sends the SNS message. &lt;strong&gt;At the same time&lt;/strong&gt; , the client service also listens to the &lt;em&gt;create-person&lt;/em&gt; events. It uses the client-id that comes with the event to increment the person counter for that specific client in the client service DynamoDB table.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/07/refactor_dist_mon_07_FullPerson.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rehanvdm.com%2Fcontents%2Fdata%2F2020%2F07%2Frefactor_dist_mon_07_FullPerson-1024x522.png"&gt;&lt;/a&gt; &lt;br&gt;New system – full create person flow (Click to enlarge) &lt;/p&gt;

&lt;h2&gt;
  
  
  What has changed?
&lt;/h2&gt;

&lt;p&gt;We used Event Bridge to &lt;strong&gt;remove direct HTTP API calls&lt;/strong&gt; between microservices. It also allowed us to &lt;strong&gt;move some logic&lt;/strong&gt; to where it belongs. The common service should not be responsible to increment the specific client’s person counter in the first place. That functionality is now contained within the client service, where it belongs.&lt;/p&gt;

&lt;p&gt;We basically borrowed two principles from the &lt;strong&gt;SOLID OOP principles&lt;/strong&gt; :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single Responsibility – Each service is only concerned with its own core functionality.&lt;/li&gt;
&lt;li&gt;Open Closed – Each service is now open for extension, but the core functionality is closed for modification.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then we introduced &lt;strong&gt;BASE consistency&lt;/strong&gt; into the system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;B&lt;/strong&gt; asic &lt;strong&gt;A&lt;/strong&gt; vailability – Even if the client service is down the common service can still operate as it has a copy of the data. Thus the data layer/plane is still operational.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;S&lt;/strong&gt; oft State – Stores don’t have to be write-consistent or mutually consistent all the time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;E&lt;/strong&gt; ventual Consistency – The system will become consistent over time, given that the system doesn’t receive input during that time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A BASE system is almost always going to be an &lt;strong&gt;AP system&lt;/strong&gt; if you look at the CAP theorem. Meaning it favors &lt;strong&gt;A&lt;/strong&gt; vailability and &lt;strong&gt;P&lt;/strong&gt; artitioning over &lt;strong&gt;C&lt;/strong&gt; onsistency.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/07/refactor_dist_mon_08_CAP.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rehanvdm.com%2Fcontents%2Fdata%2F2020%2F07%2Frefactor_dist_mon_08_CAP.png"&gt;&lt;/a&gt;CAP theorem applied to Databases (Click to enlarge)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DynamoDB&lt;/strong&gt; is an example of an &lt;strong&gt;AP system&lt;/strong&gt; as well. Your data is stored on multiple 10GB partition drives spread over multiple Availability Zones. That replication takes a few milliseconds, up to a second or two. Obviously, there are cases where you need to read the data back directly after writing. That is why you can specify a Strongly Consistent read, which just goes back to the writer node and queries the data there instead of waiting for the data to have propagated to all nodes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;S3&lt;/strong&gt; has &lt;strong&gt;read-after-write consistency for the PUT item&lt;/strong&gt; command but all &lt;strong&gt;other commands are subject to eventual consistency.&lt;/strong&gt; This means that after updating an item, the old content may still be returned for a short amount of time until the content of that file has propagated to all the storage nodes. There are more examples such as these all over the AWS ecosystem.&lt;/p&gt;

&lt;p&gt;Event-driven architectures come with their own &lt;strong&gt;pros and cons&lt;/strong&gt;. Firstly, you need to &lt;strong&gt;approach the problem with a different distributed mindset&lt;/strong&gt; that needs to be present within the company/team as well. Event &lt;strong&gt;versioning and communication&lt;/strong&gt; between teams that own microservices are also crucial. It is a good idea to have a service that keeps a &lt;strong&gt;ledger of all events that happen&lt;/strong&gt; in the system. Worst case, this history of events can be replayed to fix any processing errors in the downstream processing.&lt;/p&gt;

&lt;p&gt;Event bridge, like SNS and SQS, guarantees delivery of a message at least once. This means your &lt;strong&gt;system needs to be idempotent&lt;/strong&gt;. An example of an idempotent flow is when the client is created and the common service does a PUT command into the DynamoDB table. If that event gets delivered more than once, it just overwrites the current client with the exact same data.&lt;/p&gt;

&lt;p&gt;An example of a non-idempotent flow is when the person iscreated. If the client service gets more than one message, it increments theclient-person counter more than once. There are ways to make this callidempotent, but we’ll leave that for a different blog.&lt;/p&gt;

&lt;p&gt;Another thing to consider is that not all systems can accept the delay that eventual consistency introduces into a system. It is perfectly acceptable in our system as a person will probably not be created immediately within one second after a client has been created. Thus, whenever the common service does the client lookup locally, it can be assured that the client data is always populated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resilience to Failure
&lt;/h2&gt;

&lt;p&gt;One of the benefits that we have achieved by refactoring to a microservice system is that we are now r &lt;strong&gt;esilient to complete services failure&lt;/strong&gt;. The client service can still operate if the person and common service is down. Similarly, the person service can operate on its own and is not dependent on the other services.&lt;/p&gt;

&lt;p&gt;We also &lt;strong&gt;removed any timeout problems that was introduced&lt;/strong&gt; by the previous architecture that synchronously chained API calls. If an Event Bridge target service (like Lambda, in our case) is down, it will retry sending the message with exponential bakeoff for up to 24 hours.&lt;/p&gt;

&lt;p&gt;All Lambda functions that process the asynchronous events from Event Bridge have &lt;strong&gt;Dead Letter Queues&lt;/strong&gt; (DLQ) attached.  The event will be moved to the DLQ after three unsuccessful processing attempts by the Lambda function. We can then inspect the message later, fix any errors and replay the message if necessary.&lt;/p&gt;

&lt;p&gt;Basic &lt;strong&gt;chaos&lt;/strong&gt;  &lt;strong&gt;can be introduced&lt;/strong&gt; into the system to test its resilience. This is built into the code and can be toggled on the Lambda functions with environment variables. If &lt;em&gt;ENABLE_CHAOS&lt;/em&gt; is true, then the following environment variables are applied:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;INJECT_LATENCY&lt;/em&gt; – Number || false&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;INJECT_ERROR&lt;/em&gt; – String; two possible values: &lt;u&gt;error &lt;/u&gt;will throw a hard error and &lt;u&gt;handled &lt;/u&gt;will throw a soft error&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A Lambda service failure can be simulated by setting Reserved Concurrency to 0. Event Bridge will then retry delivering the event for up to 24 hours.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion (TL;DR)
&lt;/h2&gt;

&lt;p&gt;We refactored a distributed monolith to a microservice architecture using Event Bridge and broke dependencies using BASE consistency. The code for this blog can be found here: &lt;a href="https://github.com/rehanvdm/MicroService" rel="noopener noreferrer"&gt;https://github.com/rehanvdm/MicroService&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>cdk</category>
      <category>microservices</category>
    </item>
    <item>
      <title>AWS Serverless: you might not need third party monitoring</title>
      <dc:creator>Rehan van der Merwe</dc:creator>
      <pubDate>Wed, 15 Jul 2020 08:15:00 +0000</pubDate>
      <link>https://dev.to/aws-heroes/aws-serverless-you-might-not-need-third-party-monitoring-2pmo</link>
      <guid>https://dev.to/aws-heroes/aws-serverless-you-might-not-need-third-party-monitoring-2pmo</guid>
      <description>&lt;p&gt;I hardly ever find myself reaching for third party monitoring services these days. I rather use the AWS native observability, monitoring and alerting services. The primary reasons being that I can use my favorite Infrastructure as Code (IaC) tool to define the infrastructure as well as the monitoring, observability and dashboards for every project in one place. I also only pay for what I use; there are no monthly subscriptions.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This blog is also available as a presentation. Reach out if you would like me to present it at an event. It consists of about 30% slides and 70% live demo.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In this two-part series, we’ll first build a bad microservice system and add observability, monitoring and alerting. The second part will focus on refactoring the code and go into more details on the decisions made.&lt;/p&gt;

&lt;p&gt;Like most of my blogs, this one is also accompanied by code. I decided to create three microservices, each in their own repositories, with the fourth one used to reference all of them. The code is available on github: &lt;a href="https://github.com/rehanvdm/MicroService" rel="noopener noreferrer"&gt;https://github.com/rehanvdm/MicroService&lt;/a&gt;. These microservices were designed poorly for demo purposes and to explain the importance of certain points, like structured logging. Below are all three services:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/07/no_third_monit_01_SystemArchitecture.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rehanvdm.com%2Fcontents%2Fdata%2F2020%2F07%2Fno_third_monit_01_SystemArchitecture-1024x715.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Within each project you can find an &lt;strong&gt;OpenAPI&lt;/strong&gt; (&lt;a href="https://github.com/rehanvdm/MicroServicePerson/blob/master/part1/src/lambda/api/api-definition.yaml" rel="noopener noreferrer"&gt;part1/src/lambda/api/api-definition.yaml&lt;/a&gt;) file that defines the API definition for each service. &lt;strong&gt;AWS CDK&lt;/strong&gt; is used and they all follow the similar stock standard CDK project layout: Typescript for the CDK and &lt;strong&gt;ES6 JS&lt;/strong&gt; for the application code. NPM commands have been written to do deployments and it also contains &lt;strong&gt;end-to-end tests&lt;/strong&gt; using Mocha and Chai. In addition, each service contains a detailed README inside the /part1 path. Note that I only have a single Lambda for the API endpoint and do internal routing. Yes, I believe in a &lt;strong&gt;Lambalith for the API&lt;/strong&gt;!😊and also prefer JSON POST over REST (more about this later).&lt;/p&gt;

&lt;p&gt;The client service stores clients and has basic &lt;em&gt;create-client&lt;/em&gt; and &lt;em&gt;find-client&lt;/em&gt; functionalities as well as an endpoint to increment the person count for a specific client. The person service also has basic &lt;em&gt;create-person&lt;/em&gt; and &lt;em&gt;find-person&lt;/em&gt; endpoints. When a person is created, it calls the common service which notifies me by email about the new person that was added using an SNS subscription. The common service first needs to do a lookup on the client service so that it can enrich the email. It also increments the counter on the client. Click on the image below to see the step-by-step path for creating a person:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/07/no_third_monit_02_SystemArchitecturePathFocus.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rehanvdm.com%2Fcontents%2Fdata%2F2020%2F07%2Fno_third_monit_02_SystemArchitecturePathFocus-1024x553.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In Part 2 we will focus on refactoring and decoupling this system. That brings me to the reason why the current system is poorly designed. I &lt;strong&gt;purposefully created a distributed monolith&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The create-person call is highly dependent on the commonservice and does not even know that the common service is dependent on theclient service. As a result, the person service is also dragged down if eitherthe common or the client service is down. Not to mention that it now has towait for the completion of every step in the synchronous chain. This wastes moneyand increases the probability of hitting the API Gateway timeout of 29 seconds.&lt;/p&gt;

&lt;p&gt;Let’s first look at a few generic concepts that are referenced throughout the post. Then we will look at the AWS native services.&lt;/p&gt;

&lt;h2&gt;
  
  
  Structured logging, types of errors and metrics
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Errors
&lt;/h3&gt;

&lt;p&gt;&lt;u&gt;Hard Errors&lt;/u&gt; are infrastructure and runtime errors. You should always have alerts on these. Ex. time out, unexpected error and runtime errors not caught by try-catch blocks.&lt;/p&gt;

&lt;p&gt;&lt;u&gt;Soft Errors&lt;/u&gt; are completely software-defined. This is when your infrastructure and services are working but the &lt;strong&gt;result was undesired&lt;/strong&gt;. An example would be that your API returned an HTTP status code 200 with a validation error message in the body.&lt;/p&gt;

&lt;h3&gt;
  
  
  Metrics
&lt;/h3&gt;

&lt;p&gt;&lt;u&gt;Business Metrics &lt;/u&gt;– Key performance indicators (KPIs) that you use to measure your application performance against. Ex. orders placed.&lt;/p&gt;

&lt;p&gt;&lt;u&gt;Customer Experience Metrics&lt;/u&gt; – Percentiles and perceived latencies that the user is experiencing. A typical scenario would be page load times. Another would be that even though your API is fast, the front-end needs to make 10 concurrent API calls when the application starts. The browsers then queues these concurrent requests and the user waits at least two or three times longer.&lt;/p&gt;

&lt;p&gt;&lt;u&gt;System Metrics&lt;/u&gt; – Application level metrics that indicate system health. Ex. number of API requests, queue length, etc.&lt;/p&gt;

&lt;h3&gt;
  
  
  Structured logging
&lt;/h3&gt;

&lt;p&gt;All microservices write logs in the format below. This is done by wrapping around the &lt;em&gt;console&lt;/em&gt; class and writing in JSON format. Levels will include all your basics, like info, log, debug, error, warning, with the only new one being audit.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/07/no_third_monit_10_StructuredLogging1.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rehanvdm.com%2Fcontents%2Fdata%2F2020%2F07%2Fno_third_monit_10_StructuredLogging1.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A single audit record is written per Lambda execution and gives a summary for the result of that execution. The image below shows an audit record that contains the API path, run time, status code, reason and many more fields used in the Log Insight queries later on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/07/no_third_monit_10_StructuredLogging2.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rehanvdm.com%2Fcontents%2Fdata%2F2020%2F07%2Fno_third_monit_10_StructuredLogging2.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The image below shows an unsuccessful execution:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/07/no_third_monit_10_StructuredLogging3.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rehanvdm.com%2Fcontents%2Fdata%2F2020%2F07%2Fno_third_monit_10_StructuredLogging3.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that the runtime is not the one reported by Lambda.There is an environment variable on the Lambda itself that indicates what thefunction timeout value is. We then subtract the _context.getRemainingTimeInMillis()_to get a close estimate to the actual reported runtime.&lt;/p&gt;

&lt;p&gt;Let’s take a closer look at the AWS Native services that we will use.&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS CloudWatch Logs
&lt;/h2&gt;

&lt;p&gt;Logs are crucial to any application. CloudWatch stores logs in the format of log groups and log streams. Each log group can be considered a Lambda function and a stream is the executions of that Lambda function. The real magic happens when you do log insights over your structured logs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/07/no_third_monit_03_CloudWatchLogs.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rehanvdm.com%2Fcontents%2Fdata%2F2020%2F07%2Fno_third_monit_03_CloudWatchLogs-1024x402.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS CloudWatch Metrics
&lt;/h2&gt;

&lt;p&gt;Metrics are best described as the logging of discrete data points for a system against time. These metrics can then be displayed on graphs, like; database CPU versus time or the types of API response over time. They are at the heart of many services, like dashboards, alerts and auto scaling. If you write a log line in a specific format, called the &lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format.html" rel="noopener noreferrer"&gt;Embedded Metric Format&lt;/a&gt;, it automatically transforms it into a metric. Find the client libraries that help write this format &lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format_Libraries.html" rel="noopener noreferrer"&gt;here.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Below shows the amount of API calls summed by 1-minute intervals for the client API after the &lt;a href="https://artillery.io/" rel="noopener noreferrer"&gt;artillery.io&lt;/a&gt; load test was run.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/07/no_third_monit_04_CloudWatchMetric.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rehanvdm.com%2Fcontents%2Fdata%2F2020%2F07%2Fno_third_monit_04_CloudWatchMetric.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS CloudWatch Alarms
&lt;/h2&gt;

&lt;p&gt;Alarms perform an action when certain conditions on Metrics are met.  For example, CPU more than 50% for 3 minutes. Actions include emailing a person or sending the message to an SNS topic. This topic can then be subscribed to by other services. We subscribe to this topic with AWS Chatbot to deliver the notifications to a Slack channel.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/07/no_third_monit_05_CloudWatchAlarm.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rehanvdm.com%2Fcontents%2Fdata%2F2020%2F07%2Fno_third_monit_05_CloudWatchAlarm-1024x637.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is important to subscribe to both the ALERT and the OKAY actions. Otherwise you will never know if your system stabilized after an alert unless you log into the console and inspect the system.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/07/no_third_monit_05_CloudWatchAlarm2.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rehanvdm.com%2Fcontents%2Fdata%2F2020%2F07%2Fno_third_monit_05_CloudWatchAlarm2.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Composite Alarms are great when you want to string together some sort of logic to give a higher order of alarm/event. For example, you can create an alarm if the database CPU is more than 50% and the API hit count is less than 1000 requests per minute. This will set off an event/alarm informing you that your database might be crunching away at a difficult query and that it might be a result of a person executing a heavy analytical query rather than your application.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/07/no_third_monit_05_CloudWatchAlarm4-1024x259.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rehanvdm.com%2Fcontents%2Fdata%2F2020%2F07%2Fno_third_monit_05_CloudWatchAlarm4-1024x259.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/07/no_third_monit_05_CloudWatchAlarm3.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rehanvdm.com%2Fcontents%2Fdata%2F2020%2F07%2Fno_third_monit_05_CloudWatchAlarm3-1024x593.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Anomaly detection uses machine learning (random cut forest) to train on up to two weeks of metric data. This creates upper and lower bands around your metric which are defined by standard deviations. Alerts can then be created whenever your metric is outside or within these bands. They are great at monitoring predictable periodic metrics, like API traffic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/07/no_third_monit_05_CloudWatchAlarm5.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rehanvdm.com%2Fcontents%2Fdata%2F2020%2F07%2Fno_third_monit_05_CloudWatchAlarm5.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS CloudWatch Metric filters
&lt;/h2&gt;

&lt;p&gt;CloudWatch Metric filter will &lt;strong&gt;search the Logs for patterns and publish the search results as Metrics&lt;/strong&gt;. For example, we can search for the word ‘retry’ in the logs and then publish it as a metric that we can view on a dashboard or &lt;strong&gt;create an alarm from&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is &lt;strong&gt;how we count soft errors&lt;/strong&gt; , which are errors that don’t crash the Lambda but return an undesired result to the caller. In our example, all the API Lambdas always return HTTP Status code 200. Within the body of the response is our request status code: 2000 – Success, 5000 – Unexpected, 5001 – Handled, 5002 – Validation, 3001 – Auth. Structured logging always writes the audit record in a specific format. We use this to create metrics and then alarms based on those metrics.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/07/no_third_monit_05_CloudWatchAlarm6.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rehanvdm.com%2Fcontents%2Fdata%2F2020%2F07%2Fno_third_monit_05_CloudWatchAlarm6-1024x730.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS CloudWatch Dashboards
&lt;/h2&gt;

&lt;p&gt;Dashboards are great to get an overview of the operational status of your system. &lt;strong&gt;In the example services, each one also deploys their own dashboard&lt;/strong&gt; to monitor basic metrics of the Lambda, API Gateway and DynamoDB table. Everything is defined as IaC using the &lt;strong&gt;AWS CDK&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rehanvdm.com%2Fcontents%2Fdata%2F2020%2F07%2Fno_third_monit_06_CloudWatchDashboards1.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%2Fwww.rehanvdm.com%2Fcontents%2Fdata%2F2020%2F07%2Fno_third_monit_06_CloudWatchDashboards1.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A manual dashboard can also be created to combine all the services onto one dashboard. I usually tend to make it less granular by just displaying the overall status of each microservice and other useful information.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/07/no_third_monit_06_CloudWatchDashboards2.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rehanvdm.com%2Fcontents%2Fdata%2F2020%2F07%2Fno_third_monit_06_CloudWatchDashboards2-1024x771.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The dashboard above contains three CloudWatch Log Insight query widgets. We can even write basic markup to create links/buttons, as seen in the bottom right corner.&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS CloudWatch Log Insights
&lt;/h2&gt;

&lt;p&gt;Log Insights enable us to do SQL-like querying over one or more Log Groups. This tool is &lt;strong&gt;extremely powerful&lt;/strong&gt; to get insights out of your structured logs. It also has basic grouping functionality that can graph results. For example, we use a single query to &lt;strong&gt;query the audit records of all three microservices&lt;/strong&gt; over the last 2 weeks (see below).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/07/no_third_monit_07_CloudWatchLogInsights1.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rehanvdm.com%2Fcontents%2Fdata%2F2020%2F07%2Fno_third_monit_07_CloudWatchLogInsights1-1024x634.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/07/no_third_monit_07_CloudWatchLogInsights4.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rehanvdm.com%2Fcontents%2Fdata%2F2020%2F07%2Fno_third_monit_07_CloudWatchLogInsights4.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can also compare the latencies of all the microservice API calls and visually graph it in a bar chart.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/07/no_third_monit_07_CloudWatchLogInsights2.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rehanvdm.com%2Fcontents%2Fdata%2F2020%2F07%2Fno_third_monit_07_CloudWatchLogInsights2-1024x762.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/07/no_third_monit_07_CloudWatchLogInsights5.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rehanvdm.com%2Fcontents%2Fdata%2F2020%2F07%2Fno_third_monit_07_CloudWatchLogInsights5.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Lastly, I want to highlight the &lt;strong&gt;most impactful API calls&lt;/strong&gt;. This is taking the amount of calls and multiplying it by the 95-percentile latency. This gives us a quick indication of which API calls, if optimized, will have the biggest impact on the client calling the API.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/07/no_third_monit_07_CloudWatchLogInsights3.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rehanvdm.com%2Fcontents%2Fdata%2F2020%2F07%2Fno_third_monit_07_CloudWatchLogInsights3-1024x813.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are many more queries such as these that you can do to &lt;strong&gt;help identify if caching will work on a specific API endpoint&lt;/strong&gt; and also which type, client or server side, will work best. Other queries are documented in the GitHub README file here: &lt;a href="https://github.com/rehanvdm/MicroService" rel="noopener noreferrer"&gt;https://github.com/rehanvdm/MicroService&lt;/a&gt;. A quick summary of what we can find:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All Audit Records&lt;/li&gt;
&lt;li&gt;All Audit Records for a specific TraceID&lt;/li&gt;
&lt;li&gt;All Audit Records for a specific User&lt;/li&gt;
&lt;li&gt;All Hard errors&lt;/li&gt;
&lt;li&gt;All Soft errors&lt;/li&gt;
&lt;li&gt;All log lines for TraceID&lt;/li&gt;
&lt;li&gt;Most expensive API calls&lt;/li&gt;
&lt;li&gt;Most impactful API calls&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These queries would not be possible without structured logging. When APIs call each other, they also send the TraceID/CorrelationID downstream. This ID is then used in the logs by the service receiving the request. It enables us to &lt;strong&gt;query a single TraceID and find the complete execution path over all three services and their logs&lt;/strong&gt; , saving a ton of time when you’re debugging.&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS X-Ray
&lt;/h2&gt;

&lt;p&gt;Distributed Tracing is not something new, but it is a must-have for any distributed and micro service application. X-Ray allows you to easily see interactions between services and identify problems at a glance. Segments are represented by circles with the colour indicating the status of that segment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/07/no_third_monit_08_Xray1.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rehanvdm.com%2Fcontents%2Fdata%2F2020%2F07%2Fno_third_monit_08_Xray1.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each service that runs the X-Ray agent sends trace data to the X-Ray service. This agent is already installed on the container that runs the Lambda function and uses less than 3% of your memory or 16MB, whichever is greater. Tracing is made possible by passing a TraceID downstream to all the services it calls. Each service that has X-Ray enabled uses the received TraceID and continues to attach segments to the trace. The X-Ray service collects and orders all these traces. The traces can be viewed in the Service Map (image above) or as Traces (image below).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/07/no_third_monit_08_Xray2.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rehanvdm.com%2Fcontents%2Fdata%2F2020%2F07%2Fno_third_monit_08_Xray2-1024x644.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The X-Ray service isn’t perfect. For some services it requires manual wrapping and passing of the TraceID in order to get a fully traced execution. One of these services is SQS and it is documented &lt;a href="https://github.com/aws/aws-xray-sdk-node/issues/208" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;X-Ray also has &lt;strong&gt;sampling options&lt;/strong&gt; to not trace every request. This is helpful if you have high throughput applications and tracing every request would just be too expensive. By default, the X-Ray SDK records the first request each second, and five percent of any additional requests. This sampling rate can be adjusted by creating rules on the console.&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS Chatbot
&lt;/h2&gt;

&lt;p&gt;AWS Chatbot is an interactive bot that makes it easy to monitor and interact with your AWS resources from within your Slack channels and Amazon Chime chatrooms.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/07/no_third_monit_09_ChatBot1.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rehanvdm.com%2Fcontents%2Fdata%2F2020%2F07%2Fno_third_monit_09_ChatBot1.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We only use it for &lt;strong&gt;sending alarms to a Slack channel&lt;/strong&gt; because it is such an eyesore to look at the alarm emails. AWS Chatbot can do a lot more though; you can directly run a Lambda function or log a support ticket when interacting with the bot.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/07/no_third_monit_09_ChatBot2.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rehanvdm.com%2Fcontents%2Fdata%2F2020%2F07%2Fno_third_monit_09_ChatBot2.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick word about the &lt;em&gt;&lt;code&gt;&lt;/code&gt;&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;As mentioned before, the API function is a &lt;strong&gt;Lambdalith&lt;/strong&gt;. Just to summarize the reasoning behind this madness:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;By having 1 Lambda, I save $$ by setting provisioned capacity on 1 Lambda as opposed to setting it on each endpoint.&lt;/li&gt;
&lt;li&gt;Less downstream calls are made, like fetching secrets.&lt;/li&gt;
&lt;li&gt;Less cold starts occur.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A fewpoints about the overall structure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All &lt;strong&gt;API calls are proxied to a single Lambda&lt;/strong&gt; function. Then the Lambda ‘routes’ to a certain file within its code base for that endpoint logic.&lt;/li&gt;
&lt;li&gt;The common directory has the &lt;em&gt;data_schemas&lt;/em&gt; which are 1-to-1 mappings of how data is stored in DynamoDB. The &lt;em&gt;v1&lt;/em&gt; directory that handles the endpoints does all the business logic.&lt;/li&gt;
&lt;li&gt;There is an OpenAPI 3 doc to describe the API.&lt;/li&gt;
&lt;li&gt;There is &lt;strong&gt;structured logging&lt;/strong&gt; done by a basic helper class that wraps the console.log function.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistent error handling&lt;/strong&gt; is forced.&lt;/li&gt;
&lt;li&gt;There is &lt;strong&gt;one audit record for every execution&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Part 2 – coming soon
&lt;/h2&gt;

&lt;p&gt;Asmentioned above, Part 2 will fix the coupling between the micro services. Wewill look at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creating BASE ( &lt;strong&gt;B&lt;/strong&gt; asic &lt;strong&gt;A&lt;/strong&gt; vailability, &lt;strong&gt;S&lt;/strong&gt; oft state, &lt;strong&gt;E&lt;/strong&gt; ventual Consistency) consistency over the whole system.&lt;/li&gt;
&lt;li&gt;Decoupling the service with Event Bridge.&lt;/li&gt;
&lt;li&gt;Adding a Queue between the Common and Client API to increment the counter.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Basically,the common service will be listening to the client and person create events.When a client is created, the common service stores a local copy of that clientin its own database so that it does not have to do the lookup on an externalservice but can rather go to its own service data. The write from the commonservice to the client to increment the person count can also be decoupled usinga queue.&lt;/p&gt;

&lt;p&gt;This changes our distributed monolith to microservice-based architecture, it looks something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/07/no_third_monit_11_Part2.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rehanvdm.com%2Fcontents%2Fdata%2F2020%2F07%2Fno_third_monit_11_Part2-1024x525.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion (TL;DR)
&lt;/h2&gt;

&lt;p&gt;In this post, we focus on the strengths and capabilities of using AWS Native service to monitor distributed applications. In this first instalment of the series, we create a distributed monolith consisting of three services using the AWS CDK. The code can be found here: &lt;a href="https://github.com/rehanvdm/MicroService" rel="noopener noreferrer"&gt;https://github.com/rehanvdm/MicroService&lt;/a&gt;. The second part will focus on refactoring the monolith to a decoupled microservice.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This post has been edited by&lt;/em&gt; &lt;a href="https://www.nuanced.co.za/" rel="noopener noreferrer"&gt;&lt;em&gt;Nuance Editing &amp;amp; Writing&lt;/em&gt;&lt;/a&gt;&lt;em&gt;. Check them out for all your editing and writing needs.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>cdk</category>
      <category>lambda</category>
    </item>
    <item>
      <title>An unexpected journey with Lambda &amp; OracleDB</title>
      <dc:creator>Rehan van der Merwe</dc:creator>
      <pubDate>Tue, 19 May 2020 05:58:44 +0000</pubDate>
      <link>https://dev.to/rehanvdm/an-unexpected-journey-with-lambda-oracledb-fb7</link>
      <guid>https://dev.to/rehanvdm/an-unexpected-journey-with-lambda-oracledb-fb7</guid>
      <description>&lt;p&gt;This blog serves to document the unexpected struggles of connecting to an Oracle database with a Lambda NodeJS function. This turned out to be more complex than a single package installation. We will create a Lambda layer for the NodeJS Lambda function to consume; this consists of the Oracle Instant Client Basic Lite v19.x libs + the libaio.so.1 file. While we (developers) will manually install the Instant Client libraries as dev dependencies to locally run and test the application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is an article can also be found on my &lt;a href="https://www.rehanvdm.com/serverless/an-unexpected-journey-with-lambda-oracledb/index.html"&gt;blog&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We will be using the AWS CDK for the infrastructure definitions and deployments. As a bonus we will look at the cold start times and discover how a higher memory setting can reduce connection times.&lt;/p&gt;

&lt;h2&gt;
  
  
  Whatyou need to know
&lt;/h2&gt;

&lt;p&gt;There are packages out there such as &lt;a href="https://github.com/nalbion/node-oracledb-for-lambda"&gt;this one&lt;/a&gt; that work, but they are old and unmaintained. The last built was for node 8 which means that the oracledb package is 2 major versions behind as all of the variations of this repo and tutorials are using the v12 of the Instant Client libraries while the current version is v19. That repo will also not work for local testing on all operating systems which will be the cause of many headaches if you’re doing team development.&lt;/p&gt;

&lt;p&gt;Thus we took the plunge and decided to do this properly. We are using the &lt;a href="https://www.npmjs.com/package/oracledb"&gt;official oracledb package&lt;/a&gt; from oracle themselves. Not a lot in the npm documentation, so we jump over to the &lt;a href="https://oracle.github.io/node-oracledb/INSTALL.html#quickstart"&gt;quick start guide&lt;/a&gt; and immediately run into an enormous wall of text.&lt;/p&gt;

&lt;p&gt;It turns out that to talk to an OracleDB you need the &lt;strong&gt;operating system specific&lt;/strong&gt; Oracle Instant Client Basic libraries. After giving the docs a quick (it wasn’t quick) scan you find the download page, hit download and extract. We ran into the first problem, size. These libraries are about 230MB, so if you add the AWS SDK package which is  approximately 50MB you won’t be able to deploy your Lambda due to the &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html"&gt;hard size limit of 250MB extracted&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--km3_ganB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2020/05/lambda_oracle_01-1-1024x141.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--km3_ganB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2020/05/lambda_oracle_01-1-1024x141.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After further investigation we find the Oracle Instant Client Basic &lt;u&gt;&lt;strong&gt;Lite&lt;/strong&gt; &lt;/u&gt;libraries, which after extraction is about 110MB. This is better but still huge for a Lambda.&lt;/p&gt;

&lt;p&gt;The second ‘gotcha’ buried away in the “quick” start guide is that you will need an extra package; libaio or libaio.so.1. This needs to downloaded and placed into the Instant Client Basic Lite directory along with the other .so files.&lt;/p&gt;

&lt;p&gt;The instant client libraries + libaio file needs to be in your Lambda root directory under /lib. Alternatively, you can place them in /opt/lib as the node oracledb package will still detect them without the need for changing any environment variables. This specific environment variable in question is &lt;code&gt;LD_LIBRARY_PATH&lt;/code&gt;&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html"&gt;which already defaults to&lt;/a&gt;&lt;code&gt;/lib64:/usr/lib64:$LAMBDA_RUNTIME_DIR:$LAMBDA_RUNTIME_DIR/lib:$LAMBDA_TASK_ROOT:$LAMBDA_TASK_ROOT/lib:/opt/lib&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Get it working on AWS
&lt;/h2&gt;

&lt;p&gt;The full source code can be found here -&amp;gt; &lt;a href="https://github.com/rehanvdm/lambda-oracle-instant-client-blog"&gt;https://github.com/rehanvdm/lambda-oracle-instant-client-blog&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Instant Client libraries can either be in the Lambda or within a Lambda layer. By removing these approximately 110MB libraries from the Lambda package, we can benefit from faster deployments, less bandwidth usage and we optimize not to hit the 75GB combined Lambda storage per account. &lt;strong&gt;Using a Lambda layer is a no brainer.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We are using AWS CDK and can create this layer from lines 10 to 15. Consuming this layer is just as easy and can be seen on line 33.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/05/lambda_oracle_02_cdk_code.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sw3LcCJr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2020/05/lambda_oracle_02_cdk_code-1024x894.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The instant client libraries are in the /lib path and includes the libaio.so file as described above. The directory structure for the layer then looks like this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1QdMk47E--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2020/05/lambda_oracle_03_contents_of_layer.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1QdMk47E--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2020/05/lambda_oracle_03_contents_of_layer.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Below is our quick test Lambda function that creates a pool with 1 connection to query the system date and time from the database. The DB credentials are passed in through environment variables (please don’t do this in production, this is just a quick experiment).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/05/lambda_oracle_04_lambda_test_code.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cGoNOBNY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2020/05/lambda_oracle_04_lambda_test_code-635x1024.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To deploy we can simply use the following command whichcompiles our CDK TypeScript to JS and then deploys to our account using the.aws/credentials profile called rehan.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# tsc &amp;amp;&amp;amp; cdk deploy --profile rehan
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Get it working locally on multiple operating systems
&lt;/h2&gt;

&lt;p&gt;Operating system specific files are usually a pain when a team collaborates on a project. The oracledb nodejs package is treated like any other NodeJS package, it is just the OS dependencies that we need to install as developers to locally run and test our application.&lt;/p&gt;

&lt;h4&gt;
  
  
  Windows:
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Download the Instant Client Basic Lite v19.x libs &lt;a href="https://www.oracle.com/database/technologies/instant-client/winx64-64-downloads.html"&gt;here&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Extract them to C:\oracle\instantclient_19_6&lt;/li&gt;
&lt;li&gt;Add this new directory to your PATH variable. Restart IE or just the computer.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Linux:
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Download the Instant Client Basic Lite v19.x libs &lt;a href="https://www.oracle.com/database/technologies/instant-client/linux-x86-64-downloads.html"&gt;here&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Extract to a any path, say  &lt;code&gt;/opt/oracle/instantclient_19_6&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Make sure that path is in your &lt;code&gt;LD_LIBRARY_PATH&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Download the libaio (libaio.so.1) which can be done using; &lt;em&gt;sudo yum install libaio -y&lt;/em&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Other:
&lt;/h4&gt;

&lt;p&gt;Please refer to the “quick” start guide &lt;a href="https://oracle.github.io/node-oracledb/INSTALL.html#-3-node-oracledb-installation-instructions"&gt;here&lt;/a&gt; as I haven’t done the installs on all platforms, but the gist of it should be the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running the application
&lt;/h2&gt;

&lt;p&gt;We can test locally by setting the environment variables at the top of the test file at _/tests/lambda/oracle-test/test-connection.j_s and then running the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# node ./node\_modules/mocha/bin/mocha --ui bdd./tests/lambda/oracle-test/test-connection.js --grep "^Test Success TestConnect$"  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;If everything was setup successfully you should see the DB system time.&lt;/p&gt;

&lt;p&gt;To test on AWS, navigate to the Lambda console, click on test and just use an empty json event. Click test and then you should see log files similar to these:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt; 2020-05-17T13:47:06.252Z INFO Pool
 2020-05-17T13:47:10.436Z INFO Pool created
 2020-05-17T13:47:10.436Z INFO Connection
 2020-05-17T13:47:10.817Z INFO Connection created
 2020-05-17T13:47:10.836Z INFO Query
 2020-05-17T13:47:10.897Z INFO Query returned
 2020-05-17T13:47:10.917Z INFO 2020-05-17T13:47:10.000Z
 2020-05-17T13:47:10.936Z INFO Connection
 2020-05-17T13:47:10.936Z INFO Connection closed
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Going deeper down the rabbit hole
&lt;/h2&gt;

&lt;p&gt;There were some initial concerns surrounding the large size of the Lambda package and how that will influence the cold start times. Below are some X-Ray screenshots and console logs for different memory settings.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;128MB took a TOTAL time of ~5,400ms&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/05/lambda_oracle_06_lambda_128MB.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aS3fAmLj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2020/05/lambda_oracle_06_lambda_128MB-1024x597.png" alt=""&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt; 2020-05-17T13:47: **06.252** Z INFO Pool
 2020-05-17T13:47: **10.436** Z INFO Pool created
 2020-05-17T13:47:10.436Z INFO Connection
 2020-05-17T13:47:10.817Z INFO Connection created
 2020-05-17T13:47:10.836Z INFO Query
 2020-05-17T13:47:10.897Z INFO Query returned
 2020-05-17T13:47:10.917Z INFO 2020-05-17T13:47:10.000Z
 2020-05-17T13:47:10.936Z INFO Connection
 2020-05-17T13:47:10.936Z INFO Connection closed
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;strong&gt;256MB took a TOTAL time of ~3,600ms&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Logs and X-ray screenshot omitted..&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;512MB took a TOTAL time of ~2,300ms&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Logs and X-ray screenshot omitted..&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;1024MB took a TOTAL time of ~1,500ms&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2020/05/lambda_oracle_07_lambda_1024MB.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xyvNWhDR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2020/05/lambda_oracle_07_lambda_1024MB-1024x604.png" alt=""&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt; 2020-05-17T13:52: **52.547** Z INFO Pool
 2020-05-17T13:52: **53.037** Z INFO Pool created
 2020-05-17T13:52:53.037Z INFO Connection
 2020-05-17T13:52:53.091Z INFO Connection created
 2020-05-17T13:52:53.091Z INFO Query
 2020-05-17T13:52:53.097Z INFO Query returned
 2020-05-17T13:52:53.098Z INFO 2020-05-17T13:52:53.000Z
 2020-05-17T13:52:53.098Z INFO Connection
 2020-05-17T13:52:53.099Z INFO Connection closed
 2020-05-17T13:52:53.099Z INFO Pool
 2020-05-17T13:52:53.101Z INFO Pool closed
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Yan Cui wrote an excellent post on &lt;a href="https://theburningmonk.com/2019/03/just-how-expensive-is-the-full-aws-sdk/"&gt;how expensive the full aws sdk is&lt;/a&gt; so this provides a good baseline of what to expect. If you require the full AWS SDK you are looking at around 245ms of cold start. Since we are only requiring 2 packages, the full aws-sdk and oracledb, we can calculate the time oracledb adds which is 482(from X-Ray)-245 = 237ms.  It sounds bad, but not as bad as originally thought, we can live with this.&lt;/p&gt;

&lt;p&gt;The real surprise was that the Lambda finished at different times for different memory settings even though &lt;strong&gt;the initialization times stay reasonably constant&lt;/strong&gt;. The 1024MB setting is approximately 4 times faster than on 128MB, so this means that the memory setting has a linear correlation on how fast an extremely simple query runs and that the memory setting has no effect on how long it takes to initialize the big packages.&lt;/p&gt;

&lt;p&gt;To find the pain point in the test application, we added good old fashion console.log statements. The logs above show that the connection pool creation is the culprit. There also seems to be a bug in the oracledb package that even if the minPool property is set to 0, it still opens at least 1 connection. This is confirmed by setting the minPool property to 1 and observing the exact same Lambda execution times and logs.&lt;/p&gt;

&lt;p&gt;This &lt;strong&gt;correlation between memory setting and connection time&lt;/strong&gt; seems to stabilizes around the 1024MB memory setting. Observing the logs we can see that it takes &lt;strong&gt;~4,200ms on 128MB&lt;/strong&gt; and &lt;strong&gt;~500ms on 1024MB&lt;/strong&gt; to open a single connection to the OracleDB which is about 8 times faster.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;p&gt;If you get an error like this :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"DPI-1047: Cannot locate a 64-bit Oracle Clientlibrary: \"/var/task/lib/libclntsh.so: file too short\"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;It means one of two things, either your path variable to the instant client libs are incorrect and it cannot detect them OR the libiao file that needs to be manually added is missing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion (TL;DR)
&lt;/h2&gt;

&lt;p&gt;We created a Lambda layer that hosts the Oracle Instant Client Basic Lite libraries and included the libaio.so file as well. For local development, each developer needs to manually install these operating system specific libraries on their machines. Watch out for the initial connection to the database which seems to be tied to the amount of memory you specify for the function up until about 1024MB&lt;/p&gt;

&lt;p&gt;If you told me a year ago that I would be connecting to an OracleDB using a Lambda function, I would most properly have laughed at you. But hey, here we are. Jokes on me.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>database</category>
      <category>serverless</category>
      <category>lambda</category>
    </item>
    <item>
      <title>DynamoDB Importer</title>
      <dc:creator>Rehan van der Merwe</dc:creator>
      <pubDate>Sun, 10 Nov 2019 20:00:09 +0000</pubDate>
      <link>https://dev.to/rehanvdm/dynamodb-importer-23k3</link>
      <guid>https://dev.to/rehanvdm/dynamodb-importer-23k3</guid>
      <description>&lt;p&gt;This blog will demonstrate the high throughput rate that DynamoDB can handle by writing &lt;strong&gt;1 million records in 60 seconds with a single Lambda&lt;/strong&gt; , that is approximately &lt;strong&gt;17k writes per second&lt;/strong&gt;. This is all done with less than 250 lines of code and less than 70 lines of CloudFormation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is an article can also be found on my &lt;a href="https://www.rehanvdm.com/serverless/dynamodb-importer/index.html"&gt;blog&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We will also show how to reach &lt;strong&gt;40k writes per second (2.4 million per minute)&lt;/strong&gt; by running a few of the importer Lambdas concurrently to observe the DynamoDB burst capacity in action.&lt;/p&gt;

&lt;h2&gt;
  
  
  Knowing the Limits
&lt;/h2&gt;

&lt;p&gt;It is always nice to talk about the limits and capabilities of DynamoDB but few people get to actually test them and walk the walk. This blog &lt;strong&gt;originated from&lt;/strong&gt; &lt;a href="https://dev.to/djviolin/how-to-import-a-big-delimited-datatable-into-aws-dynamodb-without-opening-your-piggy-bank-ncb"&gt;&lt;strong&gt;this&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;dev.to&lt;/strong&gt;  &lt;strong&gt;discussion&lt;/strong&gt; in the comments. It will also hopefully be one in many DynamoDB blogs to come.&lt;/p&gt;

&lt;p&gt;DynamoDB is an Online Transactional Processing (OLTP) database that is built for massive scale. It takes a different type of mindset to develop for NoSQL and particularly DynamoDB, working with and around limitations but when you hit that sweet spot, the sky is the limit. This post will &lt;strong&gt;test some of those limits.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The fastest write throughput that I heard of was in this &lt;a href="https://aws.amazon.com/blogs/database/amazon-dynamodb-auto-scaling-performance-and-cost-optimization-at-any-scale/"&gt;AWS blog post&lt;/a&gt; were they was pushed to &lt;strong&gt;1.1 Million records per second!&lt;/strong&gt; We won’t be going that high, as we would need to contact AWS to get a quota increase, for now we just want to demonstrate the ability and show some code. Enough chit chat, let’s get to the good stuff.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pseudo code
&lt;/h2&gt;

&lt;p&gt;This app consists of a NodeJS Lambda function that streams a S3 file and then imports it into DynamoDB using the Batch API.&lt;/p&gt;

&lt;p&gt;What’s happening behind the scenes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Streaming the file from S3&lt;/strong&gt; allows us to read the file in chunks and not fill the memory. It still counts as a single S3 read operation with the added benefit of not storing the full file in memory.&lt;/li&gt;
&lt;li&gt;The data can be generated by running a script in /data-generator/generate.js. It will output a CSV file of 3 million lines of person records with a size of about 250MB.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rows are read until 25 records are accumulated&lt;/strong&gt; ; these are then &lt;strong&gt;used to create a DynamoDB batch write promise&lt;/strong&gt;.  X amount of DynamoDB batch write &lt;strong&gt;promises are stored in an array&lt;/strong&gt; and will be &lt;strong&gt;executed in parallel but limited to only Y concurrent executions.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DynamoDB API Retries are set to 0 on the SDK client&lt;/strong&gt;. This is so that we can &lt;strong&gt;handle partial batch throttles&lt;/strong&gt;. This is done by adding the unprocessed items returned from the batch write call into an array and then calling the same batch write function recursively. This retry mechanism is hard coded to stop at 15 calls and to have exponential back off with a 50ms jitter between retires.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTTP Keep Alive on the AWS SDK is turned on&lt;/strong&gt; with the environment variable: AWS_NODEJS_CONNECTION_REUSE_ENABLED = 1. DynamoDB API operations are usually short lived and the latency to open the TCP connection is greater than the actual API call. Check out Yan Cui’s post &lt;a href="https://theburningmonk.com/2019/02/lambda-optimization-tip-enable-http-keep-alive/"&gt;here.&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For example; if &lt;strong&gt;X&lt;/strong&gt; (batch write promises) &lt;strong&gt;= 40&lt;/strong&gt; and &lt;strong&gt;Y&lt;/strong&gt; (the parallel execution limit) &lt;strong&gt;= 20&lt;/strong&gt; it means that the stream will be read until it has 25*40 = 1000 records and then execute 20 batch writes operations at a time until all 40 is complete. Then it will repeat by reading another 40 batches and so on. The &lt;strong&gt;S3 stream pausing&lt;/strong&gt; gives the DynamoDB rate limiting a bit of time to recover, compared to reading ALL the data at once and then just hammering the Dynamo API nonstop. The pausing &lt;strong&gt;gives it a bit of a breather.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The rest is just boiler plate to log the throughput, providing arguments through environment variables, comments and handling permissions. The actual code will be far less if this is removed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code walk through
&lt;/h2&gt;

&lt;p&gt;The full source code can be found here -&amp;gt; &lt;a href="https://github.com/rehanvdm/DynamoDBImporter"&gt; https://github.com/rehanvdm/DynamoDBImporter.&lt;/a&gt; I am only going to highlight the few pieces that are of importance.&lt;/p&gt;

&lt;p&gt;To prevent a HOT partition while writing to Dynamo, a UUID v4 is used as the Partition Key(PK) with no Sort Key (SK).&lt;/p&gt;

&lt;p&gt;A single data record/row is kept small and fits within 1KB so that 1 record equals 1 WCU. This makes the tests and calculations easier. &lt;a href="https://zaccharles.github.io/dynamodb-calculator/"&gt;This is a nice tool&lt;/a&gt; that gives you the size of a single record as seen below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--J0Z6eV_4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2019/11/dynamo_importer_1_record_size.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--J0Z6eV_4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2019/11/dynamo_importer_1_record_size.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Below the batch writing function as explained in Pseudo code with manual retries to handle partial batch failures.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2019/11/dynamo_importer_2_batch_writing.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tVNwSCLp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2019/11/dynamo_importer_2_batch_writing-885x1024.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then the streaming function that reads from S3 and builds the array of batches.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2019/11/dynamo_importer_3_s3_Streaming.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CmNn2UKr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2019/11/dynamo_importer_3_s3_Streaming.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The proof is in the pudding
&lt;/h2&gt;

&lt;p&gt;A single lambda running at full power (Memory = 3GB) can write 1 Million records into a DynamoDB table (first smaller spike on graph). Running a few concurrent lambdas, we can test the burst capacity of the table (right side larger spike).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2019/11/dynamo_importer_4_dynamo_cap.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IFE_rFfL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2019/11/dynamo_importer_4_dynamo_cap-1024x519.png" alt=""&gt;&lt;/a&gt; &lt;br&gt; From the DynamoDB Metric page we can observe the WCU per second &lt;/p&gt;

&lt;p&gt;The CloudWatch metrics below shows total capacity consumed for the duration of the test per minute.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2019/11/dynamo_importer_5_total_cap_consumed.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qcD4qi9b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2019/11/dynamo_importer_5_total_cap_consumed-1024x335.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What is amazing is that the Batch Write API &lt;strong&gt;Latency stayed below 10ms&lt;/strong&gt; for all of the writes, there were some throttles though but the program handled and retried them correctly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2019/11/dynamo_importer_6_put_latency.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mnRZkl8P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2019/11/dynamo_importer_6_put_latency-1024x518.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Things to keep in mind
&lt;/h2&gt;

&lt;p&gt;A few things that I did/noticed while coding and doing the experiment.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The first time the tables WCU’s were scaled to 40k, it took about 1 hour to provision, I hope I didn’t get charged for that hour. After that switching between a lower capacity and back to 40k took about 1 minute.&lt;/li&gt;
&lt;li&gt;The AWS SDK TCP keep alive makes a huge difference.&lt;/li&gt;
&lt;li&gt;Provisioned capacity billing mode can go from zero to full, almost instantaneously. On demand takes a while to warm up, Yan Cui wrote a great post on this behavior &lt;a href="https://theburningmonk.com/2019/03/understanding-the-scaling-behaviour-of-dynamodb-ondemand-tables/"&gt;here&lt;/a&gt;. There is a “cheat” to get a warm (40k) table right from the get-go with on demand pricing. You can set your table to provisioned 40k WCU and then after it has been provisioned, change back to on demand billing.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>aws</category>
      <category>database</category>
      <category>serverless</category>
      <category>dynamodb</category>
    </item>
    <item>
      <title>3 Ways to Autoscale on AWS</title>
      <dc:creator>Rehan van der Merwe</dc:creator>
      <pubDate>Tue, 24 Sep 2019 19:17:08 +0000</pubDate>
      <link>https://dev.to/rehanvdm/3-ways-to-autoscale-on-aws-5ff4</link>
      <guid>https://dev.to/rehanvdm/3-ways-to-autoscale-on-aws-5ff4</guid>
      <description>&lt;p&gt;In this article, we will be looking at three different methods of Autoscaling applications. We’ll also try to leverage AWS manged services as much as possible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is an article written for &lt;a href="https://afonza.com/" rel="noopener noreferrer"&gt;Afonza&lt;/a&gt;, it can be found &lt;a href="https://afonza.com/3-ways-to-autoscale-on-aws/" rel="noopener noreferrer"&gt;here&lt;/a&gt; or on my &lt;a href="https://www.rehanvdm.com/aws/3-ways-to-autoscale-on-aws/index.html" rel="noopener noreferrer"&gt;blog&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Scaling is when similar resources are added or removed depending on demand, this is usually done manually. An &lt;strong&gt;Autoscaling&lt;/strong&gt; application will monitor key performance metrics and when these cross certain thresholds, either &lt;strong&gt;scale out&lt;/strong&gt; (add similar resources) or &lt;strong&gt;scale in&lt;/strong&gt; (remove similar resources) to &lt;strong&gt;meet demand&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Compared to scaling, Autoscaling can happen &lt;strong&gt;almost&lt;/strong&gt;  &lt;strong&gt;instantaneously&lt;/strong&gt; depending on the setup. This can help maintain Service Level Agreements (SLAs) by having a Highly Available (HA) setup spread across more than 1 geographic location.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2019/09/AutoScale_Intro.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rehanvdm.com%2Fcontents%2Fdata%2F2019%2F09%2FAutoScale_Intro.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Some definitions that you might find around scaling include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scale out&lt;/strong&gt;  – Addition of similar resource to meet demand&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scale in&lt;/strong&gt;  – Removal of similar resource to meet demand&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Autoscaling Group&lt;/strong&gt; – A group of similar resources that are/will be Autoscaled&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Desired capacity&lt;/strong&gt; – This is the count of resources in the Autoscaling Group&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Max and min capacity&lt;/strong&gt; – These define the limits for the number of resources that Autoscaling can add or remove from the Autoscaling group so that it meets demand. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalable Dimension&lt;/strong&gt; – The dimension of the resource that will be used to scale the Autoscaling group, if the resource is a service, this might be the CPU usage or network in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scale in and scale out cool down&lt;/strong&gt; – The amount of time before another scale in or scale out can take place&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Health Check&lt;/strong&gt; – A Health Check is an API call to the application/resource to determine if it can still receive traffic. If it cannot, it will be marked as unhealthy and removed. This is usually with regard to the Load Balancer that sends traffic to the individual resources in the Autoscaling group.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Health Check grace period&lt;/strong&gt; – This gives the resource/application time to start up, only after this time will the checks begin&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is important to note that not any application can use autoscaling. The application must be designed with scaling kept in mind. It must be &lt;strong&gt;stateless&lt;/strong&gt; to handle new resources and any failures that might prevent your High Available setup from keeping its SLAs.&lt;/p&gt;

&lt;p&gt;With most of the terminology out of the way, let’s start looking at our examples. They are ordered by their age and ability to scale.&lt;/p&gt;

&lt;h1&gt;
  
  
  1. Elastic beanstalk
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;The CloudFormation template can be found here: &lt;a href="https://github.com/rehanvdm/awsautoscaling/blob/master/ElasticBeanstalk/cf.yaml" rel="noopener noreferrer"&gt;https://github.com/rehanvdm/awsautoscaling/blob/master/ElasticBeanstalk/cf.yaml&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Elastic Beanstalk (EB) is one of the earliest AWS &lt;strong&gt;orchestration services&lt;/strong&gt;. It is easy to configure and orchestrates a lot of other AWS services like EC2, SQS, RDS, S3 SNS, Autoscaling, Cloudwatch Alarms, Loadbalancer, etc to bring your whole application together as one. Under the hood, the configuration provided to &lt;strong&gt;Elastic Beanstalk&lt;/strong&gt;  &lt;strong&gt;writes a CloudFormation&lt;/strong&gt; template to orchestrate an manage all these services for you.&lt;/p&gt;

&lt;p&gt;Elastic Beanstalk is often overlooked. It is &lt;strong&gt;still a great entry&lt;/strong&gt;  &lt;strong&gt;point&lt;/strong&gt; for anyone starting out on AWS, some might argue that it is outdated and should not be used. It is just so easy to do complex setups while &lt;strong&gt;requiring little knowledge of the underlying infrastructure&lt;/strong&gt; ; this is to me is not a sign of age but of maturity. That is why Elastic Beanstalk is first on the list, it is old and wise, perfect for large websites (my opinion) and shouldn’t be used for any compute intensive work.&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%2Fwww.rehanvdm.com%2Fcontents%2Fdata%2F2019%2F09%2FAutoScale_0_ELasticBeanstalk.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%2Fwww.rehanvdm.com%2Fcontents%2Fdata%2F2019%2F09%2FAutoScale_0_ELasticBeanstalk.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The architecture above visualizes the CloudFormation template to define an HA Elastic Beanstalk environment and comes in &lt;strong&gt;under 85 lines&lt;/strong&gt; of YAML. Elastic Beanstalk then creates and &lt;strong&gt;manages a much bigger CloudFormation template&lt;/strong&gt; on your behalf that is &lt;strong&gt;about 2500 lines&lt;/strong&gt; , containing more than 15 resources that are about 1500 lines long. The gist of this architecture can be seen below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2019/09/AutoScale_1_ELasticBeanstalk.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rehanvdm.com%2Fcontents%2Fdata%2F2019%2F09%2FAutoScale_1_ELasticBeanstalk-1024x762.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Important points to mention here is that you get a &lt;strong&gt;Classic Load balancer&lt;/strong&gt; that balances over your subnets which could be either in public or private depending on your external VPC setup. Then Elastic Beanstalk creates all the Security Groups, Classic Load Balancer, Target Group, Autoscaling groups, etc.&lt;/p&gt;

&lt;p&gt;It will &lt;strong&gt;pull the ZIP code from S3&lt;/strong&gt; and deploy it to the Elastic Beanstalk environment. Each environment has specific configuration options depending on what programming language you choose. Then Elastic Beanstalk also has &lt;strong&gt;script hooks&lt;/strong&gt; that can be used to setup the instance and container. These can be easily customized to install all kinds of applications and create custom configurations on the actual instances and . Also noteworthy is the Elastic Beanstalk CLI that has many features and commands which can be extremely helpful when migrating to Elastic Beanstalk.&lt;/p&gt;

&lt;p&gt;Many may start off with Elastic Beanstalk and then &lt;strong&gt;later move to a pure EC2 Autoscaling or ECS&lt;/strong&gt;. This is to get extra benefits that Elastic Beanstalk cannot provide out of the box. Some of these include; making use of Spot Instances to optimize cost, multi-region deployments and reusing resources like sharing a load balancer for multiple apps.&lt;/p&gt;

&lt;p&gt;The whole point of Elastic Beanstalk is to &lt;strong&gt;abstract complexity away from you&lt;/strong&gt; , so that you can only bring your application in a ZIP and then have it autoscaled over multi AZs with minimal setup and knowledge of the underlying infrastructure.&lt;/p&gt;

&lt;h1&gt;
  
  
  2. ECS Fargate
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;The CloudFormation template can be found here:&lt;/em&gt; &lt;a href="https://github.com/rehanvdm/awsautoscaling/blob/master/Fargate/cf.yaml" rel="noopener noreferrer"&gt;https://github.com/rehanvdm/awsautoscaling/blob/master/Fargate/cf.yaml&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Elastic Container Service (ECS)&lt;/strong&gt; is one of the options AWS provides for running your containers. ECS offers &lt;strong&gt;two modes of operation&lt;/strong&gt; where you manage the underlying EC2 instances that run your Docker images yourself or let AWS do it for you, the latter is known as &lt;strong&gt;ECS Fargate&lt;/strong&gt;. Fargate only cares about how much resources (CPU, memory, etc) and the number of container applications it needs to create for a service.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Fargate manages the underlying EC2 host instances and container placement.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Fargate like Elastic Beanstalk can be run with or without Autoscaling but is &lt;strong&gt;more complex&lt;/strong&gt; than the Elastic Beanstalk implementation, this is because we need to define every resource manually. The CloudFormation template specifies 14 resources to run a &lt;strong&gt;minimal Fargate setup&lt;/strong&gt; , coming in at around &lt;strong&gt;200 lines&lt;/strong&gt; of code without comments. The architecture diagram can be seen below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2019/09/AutoScale_2_Fargate.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rehanvdm.com%2Fcontents%2Fdata%2F2019%2F09%2FAutoScale_2_Fargate-1024x783.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It resembles &lt;strong&gt;much of the same components as Elastic Beanstalk&lt;/strong&gt;. The major &lt;strong&gt;differences&lt;/strong&gt; being that; the applications run on &lt;strong&gt;containers&lt;/strong&gt; orchestrated by ECS Fargate and that the application is a now created from a &lt;strong&gt;Docker image&lt;/strong&gt;. Fargate also requires a load balancer to distribute traffic to the service, which is the grouping of individual Docker applications running that may scale in and out depending on demand.&lt;/p&gt;

&lt;p&gt;Fargate is &lt;strong&gt;great&lt;/strong&gt; when you already have your applications dockerized and &lt;strong&gt;do not want to manage container hosts.&lt;/strong&gt; Cost-wise it is also perfect for &lt;strong&gt;spikey and underutilized&lt;/strong&gt; Docker applications that might not use the full EC2 instance host. When you have high throughput, constant resource intensive applications that can be tightly packed on a single instance, then ECS will be the better option.&lt;/p&gt;

&lt;h1&gt;
  
  
  3. Application Load Balancer and Lambda
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;The CloudFormation template can be found here:&lt;/em&gt; &lt;a href="https://github.com/rehanvdm/awsautoscaling/blob/master/LambdaALB/cf.yaml" rel="noopener noreferrer"&gt;https://github.com/rehanvdm/awsautoscaling/blob/master/LambdaALB/cf.yaml&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We are sticking with the load balancer approach as the previous solutions have. AWS Lambda could just as well have been invoked by API Gateway, which is the most popular method to call Lambda. Both invocation methods &lt;strong&gt;scale&lt;/strong&gt; Lambda &lt;strong&gt;near real-time&lt;/strong&gt; , depending on the configuration. AWS Lambda &lt;strong&gt;scales the fastest by&lt;/strong&gt;  &lt;strong&gt;far&lt;/strong&gt; but has its own unique set of limitations.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2019/09/AutoScale_3_LambdaALB.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.rehanvdm.com%2Fcontents%2Fdata%2F2019%2F09%2FAutoScale_3_LambdaALB.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;*Note the architecture diagram excludes any mention of VPCs on purpose, that is a bit out of scope for this topic.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;API Gateway&lt;/strong&gt; is the most &lt;strong&gt;cost-effective when you have spikey, low usage for the API&lt;/strong&gt;. While the Application Load Balancer ( &lt;strong&gt;ALB&lt;/strong&gt; ) trumps API Gateway &lt;strong&gt;when the API requires high/constant throughput.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;API Gateway offers other sidecars like authentication, VTL templates, stages, usage plans and throttling that ALB does not. ALB also has its strengths, the biggest being that a connection is not limited to 30 seconds like API Gateway but rather the Lambda limit of 15 minutes.&lt;/p&gt;

&lt;p&gt;Whether ALB or API Gateway is used, the Lambda compute engine is using the &lt;strong&gt;Firecracker&lt;/strong&gt; (blazing fast &lt;strong&gt;micro VMs&lt;/strong&gt; ) which &lt;strong&gt;starts and scales almost instantly&lt;/strong&gt;. Lambda definitely requires the &lt;strong&gt;least amount of setup&lt;/strong&gt; and hardware babysitting compared to the other solutions. This can be seen when looking at the CloudFormation template needed to define the minimal setup for ALB and Lambda. The ALB and Lambda CloudFormation template comes in &lt;strong&gt;under 100 lines&lt;/strong&gt; of YAML and requires only 6 other AWS resources compared to the more than 15 resources the other two methods required.     &lt;/p&gt;

&lt;h1&gt;
  
  
  Tests
&lt;/h1&gt;

&lt;p&gt;It is a bit difficult to test and compare 3 different architectures against each other, each with their own unique set of parameters. A simple load test is done with Artillery, which can also be found in the Github repo. The test just calls &lt;em&gt;/fibonaci.php&lt;/em&gt; for 10 minutes doing 4 calls per second. The result can be seen in CloudWatch Metrics, depending on the solution you can see EC2 Instances for EB and Tasks for Fargate scaling in and out.&lt;/p&gt;

&lt;p&gt;For Elastic Beanstalk and Fargate the outcome is pretty close. &lt;strong&gt;Elastic Beanstalk is slower&lt;/strong&gt; because it needs to &lt;strong&gt;create the actual EC2 instance&lt;/strong&gt; and do a bunch of things to get the instance and container within ready. Baked AMIs will see significant improvement here, this is the recommended process for EC2 Autoscaling. &lt;strong&gt;Fargate&lt;/strong&gt;  &lt;strong&gt;also&lt;/strong&gt;  &lt;strong&gt;uses&lt;/strong&gt; the same &lt;strong&gt;Firecracker micro VMs&lt;/strong&gt; as Lambda, so they pull and deploy a docker image &lt;strong&gt;much faster than what Elastic Beanstalk can do&lt;/strong&gt; with EC2 instances.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lambda is obviously the winner&lt;/strong&gt; when it comes to scale in and scale out timing. It scales near real-time, to be honest, it still baffles me that on a cold start it can pull and deploy your code sub-second (for this repo example +- 500ms).&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;TL;DR&lt;/strong&gt; Choose a scaling method that suits your application. Lambda will always scale the fastest.
&lt;/h4&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;The table below gives a quick summary of what we discussed.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Complexity&lt;/th&gt;
&lt;th&gt;Extensibility&lt;/th&gt;
&lt;th&gt;Compute Engine&lt;/th&gt;
&lt;th&gt;Time to Scale&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Elastic Beanstalk&lt;/td&gt;
&lt;td&gt;Easy&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;Servers&lt;/td&gt;
&lt;td&gt;+++++++++&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ECS Fargate&lt;/td&gt;
&lt;td&gt;Moderate – High&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Docker&lt;/td&gt;
&lt;td&gt;+++&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lambda&lt;/td&gt;
&lt;td&gt;Low – Moderate&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Serverless Lambda&lt;/td&gt;
&lt;td&gt;+&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Elastic Beanstalk is a good fit if you have legacy apps requiring scaling with minimal knowledge of AWS. &lt;/li&gt;
&lt;li&gt;Use ECS Fargate if your apps are already dockerized and you don’t already have too many sidecars helping with docker orchestration.&lt;/li&gt;
&lt;li&gt;AWS Lambda will be the best choice If you are starting something from scratch that has low to moderate traffic. It has other limitations that need to be kept in mind though.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>alb</category>
      <category>autoscaling</category>
      <category>elasticbeanstalk</category>
    </item>
    <item>
      <title>13 AWS Lambda design considerations you need to know about – Part 2</title>
      <dc:creator>Rehan van der Merwe</dc:creator>
      <pubDate>Sat, 03 Aug 2019 14:03:19 +0000</pubDate>
      <link>https://dev.to/rehanvdm/13-aws-lambda-design-considerations-you-need-to-know-about-part-2-1lfa</link>
      <guid>https://dev.to/rehanvdm/13-aws-lambda-design-considerations-you-need-to-know-about-part-2-1lfa</guid>
      <description>&lt;p&gt;The first installment mainly covered the technical side of things, like the limits and configuration options. This next part looks at how to use all the technical considerations we checked out in part one to effectively design serverless and Lambda systems.&lt;/p&gt;

&lt;p&gt;By the end of this post, you should have a good understanding of the key considerations to keep in mind when designing around AWS Lambda. Let’s dive in. If you have checked out &lt;a href="https://www.rehanvdm.com/uncategorized/13-aws-lambda-design-considerations-you-need-to-know-about-part-1/"&gt;&lt;strong&gt;Part one&lt;/strong&gt;&lt;/a&gt;, pop over and have a read before getting stuck into part two.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is an article written for&lt;/strong&gt; &lt;a href="https://www.jeffersonfrank.com/"&gt;&lt;strong&gt;Jefferson Frank&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;, it can be found &lt;a href="https://www.jeffersonfrank.com/aws-blog/aws-lambda-design-considerations-continued/"&gt;here&lt;/a&gt; or on my &lt;a href="https://www.rehanvdm.com/serverless/13-aws-lambda-design-considerations-you-need-to-know-about-part-2/index.html"&gt;blog&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  General design considerations
&lt;/h1&gt;

&lt;h2&gt;
  
  
  9) Types of AWS Lambda errors
&lt;/h2&gt;

&lt;p&gt;How you handle errors and failures all depends on the use case and the Lambda service that invoked the Lambda.&lt;/p&gt;

&lt;p&gt;There are different types of errors that can occur:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Configuration:&lt;/strong&gt; These happen when you’ve incorrectly specified the file or handler, or have incorrect directory structures, missing dependencies, or the function has insufficient privileges. Most of these won’t be a surprise and can be caught and fixed after deployment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime:&lt;/strong&gt; These are usually related to the code running in the function, unforeseen bugs that we introduce ourselves and will be caught by the runtime environment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Soft errors:&lt;/strong&gt; Usually not a bug, but an action that our code identifies as an error. One example being when the Lambda code intentionally throws an error after retrying three times to call a third-party API.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timeouts and memory:&lt;/strong&gt; They fall in a special kind of category as our code usually runs without problems, but it might have received a bigger event than we expected and has to do more work than we budget for. They can be fixed by either changing the code or the configuration values.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Remember—certain errors can’t be caught by the runtime environment.&lt;/strong&gt; As an example, in NodeJS, if you throw an error inside a promise without using the reject callback, then the whole runtime will crash. It won’t even report the error to CloudWatch Logs or Metrics, it just ends. These must be caught by your code, as most runtimes have events that are emitted on exit and report the exit code and reason &lt;em&gt;before&lt;/em&gt; exiting.&lt;/p&gt;

&lt;p&gt;When it comes to SQS &lt;strong&gt;, a message can be delivered more than once, and if it fails, it’ll be re-queued&lt;/strong&gt; after the visibility timeout and then retried. When your function has a concurrency of less than five, the AWS polling function will still take messages from the queue and try to invoke your function. &lt;strong&gt;This will return a concurrency limit reached exception,&lt;/strong&gt; and the message will then be marked as being unsuccessful and returned to the queue—this is unofficially called &lt;strong&gt;“over-polling.”&lt;/strong&gt; If you have a DLQ configured on your function, messages might be sent there without being processed, but we’ll say more about this later.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;SQS “over-polling”: If the Lambda is being throttled and then messages are sent to the DLQ without being processed.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then for stream-based services like DynamoDB and Kinesis streams, you have to handle the error within the function or it’ll be retried indefinitely; you can’t use the built-in Lambda DLQs here.&lt;/p&gt;

&lt;p&gt;For all other async invocations, if it fails the first invocation, it will retry two or more times. These retries mostly happen within three minutes of each other, but in rare cases it may take up to six hours, and there might also be more than three retries.&lt;/p&gt;

&lt;h2&gt;
  
  
  10) Handling Errors
&lt;/h2&gt;

&lt;p&gt;Dead Letter Queues (DLQ) to the rescue. Maybe not, DLQs only apply to async invocations; it doesn’t work for services like SQS, DynamoDB streams and Kinesis streams. For SQS use a Redrive Policy on the SQS queue and specify the Dead Letter Queue settings there. It’s important to set the visibility timeout to at least six times the timeout of your function and the &lt;em&gt;maxRecieveCount&lt;/em&gt; value to at least five. This helps prevent over-polling, messages being throttled and then being sent to the DLQ when the Lambda concurrency is low.&lt;/p&gt;

&lt;p&gt;Alternatively, you could handle all errors in your code with &lt;strong&gt;a try-catch-finally block&lt;/strong&gt;. You get more control over your error handling this way and can send the error to a DLQ yourself.&lt;/p&gt;

&lt;p&gt;Now that the events/messages are in the DLQ and the error is fixed, &lt;strong&gt;these events have to be replayed so that they’re processed&lt;/strong&gt;. They need to be taken off the DLQ, and then that event must be sent to the Lambda once again so that it can be processed successfully.&lt;/p&gt;

&lt;p&gt;There are different methods to do this  and it might not happen often, so &lt;strong&gt;a small script to pull the messages and invoke the Lambda will do the trick&lt;/strong&gt;. Replaying functionality can also be built into the Lambda, so that it knows it if receives a message from the DLQ to extract the original message and run the function. The trigger between the DLQ and the Lambda will always be disabled, but then enabled after the code is fixed and the message can be reprocessed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2019/08/LambdaConsiderations_2-1_DLQ_Replay.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pqIr7-iu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2019/08/LambdaConsiderations_2-1_DLQ_Replay.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AWS Step Functions also give you granular control over how errors are handled. We can control how many times it needs to be retried, the delay between retries, and the next state.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2019/08/LambdaConsiderations_2-2_StepFunctionsConfig.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MaaV8nkp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2019/08/LambdaConsiderations_2-2_StepFunctionsConfig.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With all these methods available, &lt;strong&gt;it’s crucial that your function is idempotent&lt;/strong&gt;. Even for something complex like credit card transactions, it can be made idempotent by first checking if the transaction with your stored transaction callback ID has been successful, or if it exists. If it doesn’t, then only carry out the credit deduction.&lt;/p&gt;

&lt;p&gt;If you can’t get your functions to be idempotent, consider the Saga pattern. For each action, there must also be a rollback action. Taking the credit card example again, the Lambda that has a Create Transaction function must also have a Reverse Transaction function, so that if an error happens after the transaction has been created, it can propagate back and the reverse transaction function can be fired. So that the state is exactly the same as it was before the transaction began. Of course, it’s never this straightforward when working with money, but it’s a solid example.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you can’t get your functions to be idempotent, consider the Saga pattern. For each action, there must also be a rollback action.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Duplicate messages can be identified by looking at the &lt;em&gt;context.awsRequestId&lt;/em&gt; inside the Lambda&lt;/strong&gt;. It can be used to de-dupe duplicate messages, if a function cannot be idempotent then this should be used. Store this ID in a cache like Redis or a DB to use it in the de-dupe logic; this introduces a new level of complexity to the code, so keep it as a last resort and always try to code your functions to be idempotent.&lt;/p&gt;

&lt;p&gt;A Lambda can also look at the &lt;strong&gt;&lt;em&gt;context.getRemainingTimeInMillis()&lt;/em&gt; function to know how much time is left before the function will end.&lt;/strong&gt; This is so that if processing takes longer than usual, it can stop, gracefully do some end function logic, and return a soft error to the caller.&lt;/p&gt;

&lt;h2&gt;
  
  
  11) Coupling
&lt;/h2&gt;

&lt;p&gt;Coupling goes beyond Lambda design considerations—it’s more about the system as a whole. Lambdas within a microservice are sometimes tightly coupled, but &lt;strong&gt;this is nothing to worry about as long as the data passed between Lambdas within their little black box of a microservice is &lt;em&gt;not&lt;/em&gt; over-pure HTTP and isn’t synchronous&lt;/strong&gt;. Lambdas shouldn’t be directly coupled to one another in a Request Response fashion, but asynchronously. Consider the scenario when an S3 Event invokes a Lambda function, then that Lambda also needs to call another Lambda within that same microservice and so on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2019/08/LambdaConsiderations_2-3_Coupling_1.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gThHIGmY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2019/08/LambdaConsiderations_2-3_Coupling_1-1024x241.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You might be tempted to implement direct coupling, like allowing Lambda 1 to use the AWS SDK to call Lambda 2 and so on. This introduces some of the following problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If Lambda 1 is invoking Lambda 2 synchronously, it needs to wait for the latter to be done first. Lambda 1 might not know that Lambda 2 also called Lambda 3 synchronously, and Lambda 1 may now need to wait for both Lambda 2 and 3 to finish successfully. Lambda 1 might timeout as it needs to wait for all the Lambdas to complete first, and you’re also paying for each Lambda while they wait.&lt;/li&gt;
&lt;li&gt;What if Lambda 3 has a concurrency limit set and is also called by another service? The call between Lambda 2 and 3 will fail until it has concurrency again. The error can be returned to all the way back to Lambda 1 but what does Lambda 1 then do with the error? It has to store that the S3 event was unsuccessful and that it needs to replay it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;This process can be redesigned to be event driven:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2019/08/LambdaConsiderations_2-3_Coupling_2.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7tBwO8ZY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2019/08/LambdaConsiderations_2-3_Coupling_2-1024x362.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not only is this the solution to all the problems introduced by the direct coupling method, it also:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provides a method of replaying the DLQ if an error occurred for each Lambda.&lt;/li&gt;
&lt;li&gt;No message will be lost or need to be stored externally.&lt;/li&gt;
&lt;li&gt;The demand is decoupled from the processing. The direct coupling method would have had failures if more than 1,000 objects were uploaded at once and generated events to invoke the first Lambda. This way, Lambda 1 can set its concurrency to be five and use the batch size to only take X amount of records from the queue and thus control maximum throughput.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Going beyond a single microservice, when events are passed between them both needs to understand and agree upon the structure of the data. Most of the time both microservices can’t be updated at the exact same time, so be sure to version all your events. This way you can change all the micro services that listen for event version 1 and add the code to handle version 2. Then update the emitting microservice to emit version 2 instead of 1, always with backwards compatibility in mind.&lt;/p&gt;

&lt;h2&gt;
  
  
  12) AWS Lambda batching
&lt;/h2&gt;

&lt;p&gt;Batching is particular useful in high transaction environments. SQS and Kinesis streams are some services that offer batching messages, sending the batch to the Lambda function instead of each and every message separately. By batching the values in groups of around 10 messages instead of one, you might reduce your AWS Lambda bill by 10 and see an increase in system performance throughput.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;By batching the values in groups of around 10 messages instead of one, you might reduce your AWS Lambda bill by 10 and see an increase in system performance throughput.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One of the downsides to batching is that it makes error handling complex. For example, one message might throw an error and the other nine are processed successfully. Then Lambda needs to either manually put that one failure on the DLQ, or return an error so that the external error handling mechanisms, like the Lambda DLQ, do their jobs. It could be that case that a whole batch of messages need to be reprocessed; here, being idempotent is again the key to success.&lt;/p&gt;

&lt;p&gt;If you’re taking things to the next level, sometimes batching is not enough. &lt;strong&gt;Consider a use case where you’ve got a CSV file with millions of records that needs to be inserted into DynamoDB.&lt;/strong&gt; The file is too big to load into the Lambda memory, so instead you stream it within your Lambda from S3. The Lambda can then put the data on a SQS queue and another Lambda that can take the rows in batches of 10 and write them to DynamoDB using the batch interface.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This sounds okay, right?&lt;/strong&gt; The thing is, a much higher throughput and lower cost can actually be achieved if the Lambda function that streams the data writes to DynamoDB in parallel. Start building groups of batch write API calls, where each can hold a maximum of 25 records. These can then be started and limited to roughly 40 parallel/concurrent batch writes, without much tuning, you will be able to reach 2k writes per second.&lt;/p&gt;

&lt;h2&gt;
  
  
  13) Monitoring and Observability
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Modern problems require modern solutions.&lt;/strong&gt; Since traditional tools won’t work for a Lambda and serverless environment, it’s difficult to find visibility and to monitor the system. There are many tools out there to help with this including internal &lt;strong&gt;AWS Services like&lt;/strong&gt;  &lt;strong&gt;X-Ray, CloudWatch Logs, CloudWatch Alarms, and CloudWatch Insights.&lt;/strong&gt; You could also turn to third-party tools like Epsagon, IOPipe, Lumigo, Thunderbird, and Datadog to just name a few.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EufTpiTe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2019/08/LambdaConsiderations_2-4_XRayConfig.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EufTpiTe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2019/08/LambdaConsiderations_2-4_XRayConfig.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All these tools deliver valuable insights in the form of logs and charts to help evaluate and monitor the serverless environment. One of the best things you can do is to &lt;strong&gt;get visibility early on and fine-tune your Lambda architecture&lt;/strong&gt;. Finding the root cause of a problem and tracing the event as it goes through the whole system can be extremely valuable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus tips and tricks!
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Within a Lambda your code can &lt;strong&gt;do parallel work&lt;/strong&gt;. If you’re receiving a batch of 10 messages, instead of doing 10 downstream API calls synchronously, do them in parallel. It reduces the run time as well as the cost of the function. This should always be considered first before moving on to the more complex &lt;em&gt;fan out-fan in.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Lambdas doing work in parallel will usually benefit from an increase in memory.&lt;/li&gt;
&lt;li&gt;All functions must be &lt;strong&gt;idempotent&lt;/strong&gt; , and &lt;em&gt;do&lt;/em&gt; consider making them &lt;strong&gt;stateless&lt;/strong&gt;. One example we can look at is when functions need to keep some small amount of session data for API calls. Let the caller send the SessionData with the SessionID and make sure both these fields are encrypted. Then, decrypt their values and use it in the Lambda, this can spare you from carrying out repeated external calls or using a cache.&lt;/li&gt;
&lt;li&gt;You &lt;strong&gt;might not need an external cache&lt;/strong&gt; ; a small amount of data can be stored above the Lambda function handler method in memory. This data will remain for the duration of the Lambda container.&lt;/li&gt;
&lt;li&gt;Alternately, each Lambda is allowed &lt;strong&gt;500 MB of file storage in /tmp&lt;/strong&gt; , and data can be cached here as a file system call will always be faster than a network call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep data inside the same region&lt;/strong&gt; , to avoid paying for data to be transferred beyond that region.&lt;/li&gt;
&lt;li&gt;Only put your Lambda inside a VPC if it needs to access private services or needs to go through a NAT for downstream services that whitelist IPs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remember NAT data transfer costs&lt;/strong&gt; money, and services like S3 and DynamoDB are publicly available. All data that flows to your Lambdas inside the VPC will need to go through the NAT.&lt;/li&gt;
&lt;li&gt;Consider using &lt;strong&gt;S3 and DynamoDB VPC Gateway Endpoints—they’re free&lt;/strong&gt; and you only pay for Interface Endpoints.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Batching messages can increase throughput&lt;/strong&gt; and reduce Lambda invocations, but this also increases complexity of error handling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Big files can be streamed from S3&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step functions are expensive&lt;/strong&gt; , so try and avoid them for high throughput systems.&lt;/li&gt;
&lt;li&gt;If you have a monorepo where all the Lambda functions live for that microservice, consider creating a directory that gets symlinked into each of those Lambda directories. Shared code only needs to be managed in one place; alternately, you could look into putting shared code into a Lambda layer.&lt;/li&gt;
&lt;li&gt;A lot of AWS Services make use of encryption to protect data, so consider using that instead of encrypting on an application level.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The less code you write the less technical debt you build up.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Where possible use the AWS Services as they are intended to be used&lt;/strong&gt; , or you could end up needing to build tooling around your specific use case and manipulate those services. This once again adds to your technical debt. AWS Lambda scales quickly and integration with other systems—like a MySQL DB with a limited amount of open connections—can quickly become a problem. There is no silver bullet for integrating a service that scales and one that doesn’t. The best thing that can be done is to either limit the scaling or implement queuing mechanisms between the two.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Environment variables should not hold security credentials&lt;/strong&gt; , so try using AWS SSM‘s Parameter Store instead. It’s free and is great for most use cases; when you want higher concurrency consider using Secret Manger, it also supports secret rotation for RDS but it comes at higher cost than Parameter Store.&lt;/li&gt;
&lt;li&gt;Consider using &lt;strong&gt;Infrastructure as Code (IaC)&lt;/strong&gt; if you aren’t.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API Gateway has the ability to proxy the HTTP request to other AWS Services, eliminating the need for an intermediate Lambda function.&lt;/strong&gt; Using Velocity Mapping Templates you can change the normal POST parameters into the arguments that DynamoDB requires for the PUT action. This is great for simple logic where the Lambda would have just transformed the request before doing the DynamoDB command.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  So what’s the bottom line?
&lt;/h2&gt;

&lt;p&gt;Always try to &lt;strong&gt;make Lambda functions stateless and idempotent&lt;/strong&gt; , regardless of the invocation type and model. Lambdas aren’t designed to work on a single big task, so break it down to smaller tasks and process it in parallel. After that, the single best thing to do would be to measure three times and cut once; &lt;strong&gt;do a lot of upfront planning, experiments, and research&lt;/strong&gt; and you’ll soon develop a good intuition for serverless design.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>lambda</category>
      <category>architecture</category>
    </item>
    <item>
      <title>13 AWS Lambda design considerations you need to know about – Part 1</title>
      <dc:creator>Rehan van der Merwe</dc:creator>
      <pubDate>Sat, 03 Aug 2019 13:32:02 +0000</pubDate>
      <link>https://dev.to/rehanvdm/13-aws-lambda-design-considerations-you-need-to-know-about-part-1-1k4k</link>
      <guid>https://dev.to/rehanvdm/13-aws-lambda-design-considerations-you-need-to-know-about-part-1-1k4k</guid>
      <description>&lt;p&gt;When you hear the word ‘serverless’, AWS Lambda is most likely the first thing you think about. That’s no surprise; the tech hit our industry by storm and brings with it a whole new paradigm of solutions. AWS Lambda was the first Function as a Service (FaaS) technology I was exposed to, and like others, I was highly critical at first. &lt;strong&gt;There are no servers to manage, it auto-scales, has fault tolerance built-in, and is pay per usage&lt;/strong&gt; —all of which sounds like a dream.&lt;/p&gt;

&lt;p&gt;With great power comes great responsibility. &lt;strong&gt;Serverless design requires knowledge of different services and how they interact with each other.&lt;/strong&gt; Just like any other technology, there are some tricky waters to navigate, but they are far outweighed by the power of what serverless has to offer. To stop this dream from turning into a nightmare, here are a few things to keep in mind when designing with AWS Lambda.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;In this two-part series, we’ll be diving into the technical details, like configuration options and any limitations you need to know about. The second part will focus on how to use the technical considerations we cover in part one to effectively design serverless and Lambda systems. At the end of it all, you should have a clearer understanding of the key considerations you need to bear in mind when designing around AWS Lambda.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is an article written for &lt;a href="https://www.jeffersonfrank.com/"&gt;Jefferson Frank&lt;/a&gt;, it can be found &lt;a href="https://www.jeffersonfrank.com/aws-blog/aws-lambda-design-considerations-part-1/"&gt;here&lt;/a&gt; or on my &lt;a href="https://www.rehanvdm.com/uncategorized/13-aws-lambda-design-considerations-you-need-to-know-about-part-1/index.html"&gt;blog&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Technical considerations
&lt;/h1&gt;

&lt;h2&gt;
  
  
  1) Function Memory
&lt;/h2&gt;

&lt;p&gt;The memory setting of your Lambda determines both the amount of power and unit of billing. There are 44 options to choose from between the slowest or lowest 128 MB, and the largest, 3,008 MB. This gives you quite a variety to choose from! &lt;strong&gt;If you allocate too little memory, your program might take longer to execute and might even exceed the time limit, which stands at 15 minutes.&lt;/strong&gt; On the other hand, if you assign &lt;em&gt;too much&lt;/em&gt; memory, your function might not even use a quarter of all that power and end up costing you a fortune.&lt;/p&gt;

&lt;p&gt;It’s crucial to find your function’s sweet spot. AWS states that if you assign 1,792 MB, you get the equivalent of 1 vCPU, which is a thread of either an Intel Xeon core or an AMD EPYC core. That’s about as much as they say about the relationship between the memory setting and CPU power. There are a few people who’ve experimented and come to the conclusion that after 1,792 MB of memory, you &lt;em&gt;do&lt;/em&gt; indeed get a second CPU core and so on, however the utilization of these cores can’t be determined.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2019/08/LambdaConsiderations_1-1_Memory.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sEBcUdW8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2019/08/LambdaConsiderations_1-1_Memory.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cheaper isn’t always better&lt;/strong&gt; —sometimes choosing a higher memory option that is more expensive upfront can reduce the overall execution time. This means that the same amount of work can be done within a smaller time period, so &lt;strong&gt;by fine-tuning the memory settings and finding the optimal point, you can make your functions execute faster as opposed to the same low memory setting.&lt;/strong&gt; You may end up paying the same—or even less—for your function than with the lower alternative.&lt;/p&gt;

&lt;p&gt;The bottom line is that &lt;strong&gt;CPU and memory should not be high on your design consideration list&lt;/strong&gt;. AWS Lambda, just like other serverless technologies, is meant to scale horizontally. Breaking the problem into smaller, more manageable pieces and processing them in parallel is faster than many vertically scaled applications. &lt;strong&gt;Design the function and then fine-tune the memory setting&lt;/strong&gt;  &lt;strong&gt;later&lt;/strong&gt; as needed.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Breaking the problem into smaller manageable piecesand processing them in parallel is faster than many vertically scaled applications.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  2) Invocation
&lt;/h2&gt;

&lt;p&gt;AWS Lambda has two invocation models and three invocation types. What this means is that there are two methods of acquiring the data and three methods through which the data is sent to the Lambda function. The invocation model and type determine the characteristics behind how the function responds to things like failures, retries and scaling that we’ll use later on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Invocation models:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Push: when another service sends information to Lambda.&lt;/li&gt;
&lt;li&gt;Pull: an AWS managed Lambda polls data from another service and sends the information to Lambda.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The sending part can then be done in one of three ways, and is known as the &lt;strong&gt;invocation type&lt;/strong&gt; :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Request Response: this is a &lt;strong&gt;synchronous&lt;/strong&gt; action; meaning that the request will be sent and the response will be waited on. This way, the caller can receive the status for the processing of the data.&lt;/li&gt;
&lt;li&gt;Event: this an &lt;strong&gt;asynchronous&lt;/strong&gt; action; the request data will be sent and the Lambda only acknowledges that it received the event. In this case, the caller doesn’t care about the success of processing that particular event. Its only job was to deliver the data.&lt;/li&gt;
&lt;li&gt;Dry Run: this is just a testing function to check that the caller is permitted to invoke the function.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Below are a few examples that showcase the different models and invocation types available:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;API Gateway Request&lt;/strong&gt; is a &lt;strong&gt;Push&lt;/strong&gt;  &lt;strong&gt;model&lt;/strong&gt; and by default has a &lt;strong&gt;Request Response&lt;/strong&gt;  &lt;strong&gt;invocation&lt;/strong&gt; The HTTP request is sent through to the Lambda function, the API gateway then waits for the Lambda function to return the response.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;S3 Events notifications, SNS Message, Cloudwatch Events&lt;/strong&gt; is a &lt;strong&gt;Push model&lt;/strong&gt; and &lt;strong&gt;Event invocation&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQS Message&lt;/strong&gt; is a &lt;strong&gt;Pull&lt;/strong&gt;  &lt;strong&gt;model&lt;/strong&gt; and a &lt;strong&gt;Request Response&lt;/strong&gt;  &lt;strong&gt;invocation&lt;/strong&gt; AWS has a Lambda function that pulls data from the Queue and then send it to your Lambda function. If it returns successfully, the AWS-managed polling Lambda will remove it from the queue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DynamoDB Streams and Kinesis Streams&lt;/strong&gt; are &lt;strong&gt;Pull&lt;/strong&gt;  &lt;strong&gt;models&lt;/strong&gt; and have a &lt;strong&gt;Request Response&lt;/strong&gt;  &lt;strong&gt;invocation&lt;/strong&gt;. This one is particularly interesting as it pulls data from the stream and then invokes &lt;em&gt;our&lt;/em&gt; Lambda synchronously. Later, you’ll be see that if the Lambda fails it will try and process that message indefinitely (or until it expires), keeping other messages from being processed as a result.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2019/08/LambdaConsiderations_1-2_Invocation.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jwVzFz-b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2019/08/LambdaConsiderations_1-2_Invocation.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To my knowledge, there are no Pull models that do Event type invocations. Pull models are further divided into two sections, stream-based and non-stream based. Also, note that the API Gateway invocation type can be changed to Event (async) by adding a header before sending the data to the Lambda.&lt;/p&gt;

&lt;h2&gt;
  
  
  3) Failure and retry behavior
&lt;/h2&gt;

&lt;p&gt;This is most probably one of the most important considerations: how a Lambda fails and retries is based on the invocation type. For all &lt;strong&gt;&lt;em&gt;Event&lt;/em&gt;&lt;/strong&gt; -based invocations, if Lambda throws an error it will be invoked two more times—so three times in total, separated by a delay. If a Dead Letter Queue (DLQ) is configured, the message will be sent to the configured SQS or SNS topic, or the error will just be sent to CloudWatch.&lt;/p&gt;

&lt;p&gt;With the &lt;strong&gt;&lt;em&gt;RequestResponse&lt;/em&gt;&lt;/strong&gt; invocation type, the caller needs to act on the error returned. For API Gateway (Push + Request Response) the caller can maybe log the failure, then retry again. When it comes to Kinesis Streams (Pull stream-based + Request Response) it acts as a FIFO queue/stream. Which means if the first message is processed in error by the Lambda, it will block the whole stream from being processed until that message either expires or is processed successfully.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Idempotent system: A system will always output the same result given the same input.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It’s important to &lt;strong&gt;understand the failure and retry behavior of each invocation type&lt;/strong&gt; , as a general rule of thumb, design all your functions to be idempotent. This basically just means that if the function is invoked multiple times with the same input data then the output will/must always be the same. When you design like this, the retry behavior will not be a problem in your system.&lt;/p&gt;

&lt;h2&gt;
  
  
  4) Versions and Aliases
&lt;/h2&gt;

&lt;p&gt;AWS provides Versions and Aliases out of the box for your Lambda code. This might not be as straightforward and useful as you would think. A few things to keep in mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Versioning only applies to the Lambda code, not to the Infrastructure that it uses and depends on.&lt;/li&gt;
&lt;li&gt;Once a version is published, it basically becomes read-only.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;There are three ways in which you can use versioning and aliases&lt;/strong&gt;. A single Lambda function that gets a new version number whenever there is a change to code or configuration. The alias will be used as the stage and pointed to the correct version of the Lambda function.&lt;/p&gt;

&lt;p&gt;Again, it’s imperative to note that if something for the older versions, for example, version 3 (now the Live alias/stage) needs to change it cannot, so you can’t even quickly increase the timeout setting. In order to change it, you would need to redeploy version 3 as version 5 with the new setting and then point the Live alias to version 5. Then keeping in mind that Version 5 is actually older than version 4, this gets unnecessarily complex very quickly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2019/08/LambdaConsiderations_1-3_VersionAliases_1.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--X0Ie0V3A--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2019/08/LambdaConsiderations_1-3_VersionAliases_1.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The second method that comes to mind is a blue green deployment. Which is a little less complex where you would have three different Lambdas, one for each stage—blue being the old version and green being the new version. Just like before each new deployment of a Lambda is versioned. Then when you are ready to make the new code changes live, you create an alias that specifies, for example, 90% of traffic uses the old version and then 10% of the requests go to the new version. This is called Canary Deployments, although AWS doesn’t label it as such, it allows you to gradually shift traffic to the new version.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2019/08/LambdaConsiderations_1-3_VersionAliases_2.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SwmhMllW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2019/08/LambdaConsiderations_1-3_VersionAliases_2.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The third method is the simplest and plays nicely with IaC (Infrastructure as Code) tools like CloudFormation, SAM and CICD (Continuous Integration Continuous Deployment) pipelines. It’s based on the principle that &lt;strong&gt;each Lambda is “tightly” coupled with its environment/infrastructure.&lt;/strong&gt; The whole environment and Lambda are deployed together, any rollback will mean that a previous version of the infrastructure and Lambda needs to be deployed again. This offloads the responsibility of versioning to the IaC tool being used. Each Lambda function name includes the stage and is deployed as a whole, with the infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2019/08/LambdaConsiderations_1-3_VersionAliases_3.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--p6qkQSZI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2019/08/LambdaConsiderations_1-3_VersionAliases_3-1024x232.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  5) VPC
&lt;/h2&gt;

&lt;p&gt;The main reason to place a Lambda inside a VPC is so that it can access other AWS resources inside the VPC on their internal IP addresses/endpoints. If the function does not need to access any resources inside the VPC, it is strongly advised to leave it outside the VPC. &lt;del&gt;The reason being that inside the VPC each Lambda container will create a new Elastic Network Interface (ENI) and IP address. Your Lambda will be constrained by how fast this can scale and the amount of IP addresses and ENIs you have.&lt;/del&gt; &lt;strong&gt;[UPDATE: See end for: Improved VPC networking]&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;As soon as you place the Lambda inside the VPC, it loses all connectivity to the public internet.&lt;/strong&gt; This is because the ENIs attached to the Lambdas only have private IP addresses. So it is best practice to assign the Lambda to three private subnets inside the VPC, then connect the private subnets to go through a NAT in one of the public subnets. The NAT will then have a public IP and send all traffic to the Internet Gateway. This also has a benefit that the egress traffic from all Lambdas will come from a single IP address, but it introduces a single point of failure, this is of course mitigated by using the NAT Gateway over the NAT instance.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2019/08/LambdaConsiderations_1-4_VPC.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--myucBR2Z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2019/08/LambdaConsiderations_1-4_VPC.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  6) Security
&lt;/h2&gt;

&lt;p&gt;As with all AWS services, the principle of least privilege should be applied to the IAM Roles of Lambda functions. When creating IAM Roles, don’t set the Resource to all (*), set the specific resource. Setting and assigning IAM roles this way can be annoying, but is worth the effort in the end. By glancing at the IAM Role you will then be able to know what resources are being accessed by the Lambda and then also how they are being used (from the Action attribute). It can also be used for discovering service dependencies at a glance.&lt;/p&gt;

&lt;h2&gt;
  
  
  7) Concurrency and scaling
&lt;/h2&gt;

&lt;p&gt;&lt;del&gt;If your function is inside a VPC, there must be enough IP addresses and ENIs for scaling. A Lambda can potentially scale to such an extent that it depletes all the IPs and/or ENIs for the subnets/VPC it is placed in.&lt;/del&gt; &lt;strong&gt;[UPDATE: See end for: Improved VPC networking]&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To prevent this, set the concurrency of the Lambda to something reasonable. By default, AWS sets a limit of 1000 concurrent executions for all the Lambdas combined in your account, of which you can assign 900 and the other 100 is reserved for functions with no limits.&lt;/p&gt;

&lt;p&gt;For Push model invocations (ex: S3 Events), Lambda scales with the number of incoming requests until concurrency or account limit is reached. For all Pull model invocation types, scaling is not instant. For the stream-based Pull model with Request Response invocation types (ex: DynamoDB Streams and Kinesis) the amount of concurrent Lambdas running will be the same as the amount of shards for the stream.&lt;/p&gt;

&lt;p&gt;As opposed to the non-stream based Pull model with Request Response invocation types (ex: SQS), Lambdas will be gradually spun up to clear the Queue as quick as possible. Starting with five concurrent Lambdas, then increasing with 60 per minute up to 1000 in total, or until the limits are reached again.&lt;/p&gt;

&lt;h2&gt;
  
  
  8) Cold starts
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Each Lambda is an actual container&lt;/strong&gt; on a server. When your Lambda is invoked it will try to send the data to a warm Lambda, a Lambda container that is already started and just sitting there waiting for event data. If it does not find any warm Lambda containers, it will start/launch a new Lambda container, wait for it to be ready and then send the event data. This wait time can be significant in certain cases.&lt;/p&gt;

&lt;p&gt;Take note that if a container does not receive event data for a certain period it will be destroyed, reducing the number of warm Lambdas for your function. &lt;strong&gt;Certain compiled type languages like Java can take many seconds for a cold start,&lt;/strong&gt; whereas interpreted languages like JavaScript and Python usually takes milliseconds. When your Lambda is inside a VPC, the cold start time increases even more as it needs to wait for an ENI (private IP address) before being ready.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Even milliseconds can be significant in certain environments.&lt;/strong&gt; The only method to keep a container warm is to manually ping it. This is usually done with a Cloudwatch Event Rule (cron) and another Lambda, the cron can be set for five minutes. The CloudWatch rule will invoke the Lambda that will ping the function that you want to keep warm, keep in mind that one ping will only keep one warm Lambda container alive. If you want to keep three Lambda containers warm, then the ping Lambda must concurrently invoke the function three times in parallel.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rehanvdm.com/contents/data/2019/08/LambdaConsiderations_1-5_ColdStart_Ping.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ISg5yeXG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rehanvdm.com/contents/data/2019/08/LambdaConsiderations_1-5_ColdStart_Ping-1024x206.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[UPDATE: Improved VPC networking]&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Hyperplane now creates a shared network interface when your Lambda function is first created or when its VPC settings are updated, improving function setup performance and scalability. This one-time setup can take up to 90 seconds to complete.&lt;/li&gt;
&lt;li&gt;Functions in the same account that share the same security &lt;strong&gt;group:subnet pairing&lt;/strong&gt; use the same network interfaces. This means that there is not longer a direct correlation between Concurrent Lambdas and ENIs. &lt;/li&gt;
&lt;/ul&gt;

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