<?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: Charles Allison</title>
    <description>The latest articles on DEV Community by Charles Allison (@charlallison).</description>
    <link>https://dev.to/charlallison</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%2F143921%2Fa1763827-98d1-4b99-a142-890acf8a5343.jpeg</url>
      <title>DEV Community: Charles Allison</title>
      <link>https://dev.to/charlallison</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/charlallison"/>
    <language>en</language>
    <item>
      <title>Beyond Lambda Functions: Mastering Serverless Development</title>
      <dc:creator>Charles Allison</dc:creator>
      <pubDate>Tue, 04 Feb 2025 08:17:59 +0000</pubDate>
      <link>https://dev.to/aws-builders/beyond-lambda-functions-mastering-serverless-development-1ffb</link>
      <guid>https://dev.to/aws-builders/beyond-lambda-functions-mastering-serverless-development-1ffb</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftb2qil8l3r7k7yn5hg15.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftb2qil8l3r7k7yn5hg15.jpg" alt="Photo by Buse Doga Ay on Unsplash" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;AWS Lambda service is a critical component of serverless applications. It is one of the compute services for FaaS on AWS. When you deploy your functions to the cloud, AWS Lambda service is responsible for the execution. Knowing how to write and deploy functions is but a small percentage of what is required to build scalable serverless applications on AWS; it is a critical part not the only part. It is important to get ideas that can help when developing serverless applications.&lt;br&gt;
This article contains some of the lessons learned in my 5 years of writing serverless applications that go beyond just knowing about AWS Lambda.&lt;br&gt;
Let's dive right in!&lt;/p&gt;
&lt;h2&gt;
  
  
  Use Diagrams as a Guide
&lt;/h2&gt;

&lt;p&gt;Diagrams have been a great tool for software engineers for a long time and it did not change with serverless development. I've worked with engineering teams that start working on large-scale systems without a simple diagram and what you hear is: "As you use the application you will get to know what it does and what happens when request 'x' is made". Maybe because of some requirements or something I'm not aware of, there is no simple documentation of the system's architecture. I have used diagrams in most of my large-scale architectures and they have been helpful. Some reasons for having a diagram as a guide include:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuia1fktejms9kdlybe5a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuia1fktejms9kdlybe5a.png" alt="Sample Serverless Architecture" width="800" height="460"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Birds-eye view of required components
&lt;/h3&gt;

&lt;p&gt;In developing micro-services, it's safe to say that the unit of deployment is a service - a service is self-contained and has all the required functions it needs to carry out its operation. For serverless APIs, the unit of deployment is a function. Yes!, a function. However, in most, if not all cases, this function is not the only piece of your API. The function may need to read or write data from a data store, interact with data in an SQS queue or EventBridge, and many other components. The fact is there are a lot of moving parts that need to be effectively coordinated. With a diagram, you can see those parts and how they interact with each other to bring a feature to life.&lt;/p&gt;
&lt;h3&gt;
  
  
  Similar component concerns
&lt;/h3&gt;

&lt;p&gt;The cloud offers similar products to achieving a result, of course with subtle differences and it is important to pick the right service given the right context to enable us to achieve our goal efficiently. After a system has been designed, with a diagram, you can quickly and easily depict if a service in use is the most efficient given the context of the requirement - you must however understand how each service operates in detail and avoid assumptions. Sometimes, you may have designed your architecture based on some limited requirement or even on assumptions. It is also possible that some parts of the system were rushed to quickly come up with a POC - that is fine. Having your solution in a diagram helps you understand what changes need to be done and how they affect the overall architectural solution. Diagrams also help in cases where there's a change to the requirement; it becomes easy to spot the service that should be replaced if needed and where.&lt;/p&gt;
&lt;h3&gt;
  
  
  Review solution across development life cycle
&lt;/h3&gt;

&lt;p&gt;Software development is likened to the Civil Engineering discipline where in most cases, a building plan is drawn before actual construction begins. If this happens, then having a diagram allows you the opportunity to have your thoughts on paper(on screen) which effectively helps you review your solutions before implementation. There could be a better solution than what you originally documented. Let me also advocate that the software architecture diagram be seen as a "living document" in the sense that, it could change or be updated as the actual development progresses. This happens as you learn more about a domain or realise a better service for the job. Hence, you do not need a 100% complete diagram before you commence development. Having a visual representation of your solution, helps you make adjustments even faster as you implement your solution.&lt;/p&gt;
&lt;h3&gt;
  
  
  Faster development experience
&lt;/h3&gt;

&lt;p&gt;I have noticed I go faster if I have a diagram that I can refer to as I implement a solution. This can be likened to goal setting and working backwards to implement the goal. It gives a feeling of having done it before. When you know the requirements and have documented the necessary services required to fulfil them, it's only a matter of your knowledge of the implementation. You really can go faster with a prepared diagram versus having to plan, think and develop almost simultaneously. It is useful here to remember that your code is not the solution - it is only an implementation of a solution.&lt;/p&gt;
&lt;h2&gt;
  
  
  Understand details of a service
&lt;/h2&gt;

&lt;p&gt;It is typical in software engineering to start with a "hello world" type example when trying to learn a technology. It is ok and I am not against it - it's ok to get started with an example of how to use a service; it helps you quickly see requirements as well as simple outputs. However, to get the best and customise to your requirements, you will need more than a hello-world type experience. To become really good at using a service, you need to get practical with the service with as many use cases as you need to. Some of the ways to get started with a service include reviewing the documentation and reading an in-depth blog or an open-source repository with examples. However, please note that while those ways of learning are really good, they could focus on certain aspects of services, not the full capabilities. For example, it's general knowledge that Single Table Design in DynamoDB is very good. However, may not be suitable for all types of operations. Articles, blog posts, etc. have a focus - to get you to see a few things. Aside from all these, a progressive hands-on on the service is by far the best way to get a very good understanding of how the service operates, including its limitations - this helps you decide when to pick 'service a' over 'service b' when solving your requirements challenge.&lt;/p&gt;
&lt;h2&gt;
  
  
  Adopt EDA Early - For Efficient Scaling needs
&lt;/h2&gt;

&lt;p&gt;One of the frequently talked about features of Lambda is that; it scales according to the number of requests. This means that a new instance of your lambda function is created to handle requests as they come in especially if a previous instance is "still busy". It is true that lambda scales. However, understanding its limitations is key to its efficient use and mitigation of unexpected behaviours.&lt;/p&gt;

&lt;p&gt;Lambda does not scale infinitely, it has certain limits relating to the region of deployment and the number of functions that can be executed per time. Lambda's concurrency limit causes requests to be throttled when the limit is reached. Therefore, to build efficient systems with efficient scaling capabilities, you need to consider Event-Driven Architectures. It is very easy to postpone learning about EDAs in Serverless Architecture to a later time but I recommend that it is brought forward because it is very important and fundamental to your learning and understanding as a serverless developer and in developing efficient and scalable serverless architectures.&lt;/p&gt;

&lt;p&gt;Two very fundamental aspects of EDAs are Queues and EventBuses. Fundamental knowledge of Amazon SQS and Amazon EventBridge can help you build efficient and scalable solutions.&lt;/p&gt;
&lt;h2&gt;
  
  
  What does that framework do? You may need to isolate it's orchestration
&lt;/h2&gt;

&lt;p&gt;Frameworks help develop serverless applications because they provide some level of abstraction and structure as to how the application is written coupled with the ease of deployment.&lt;/p&gt;

&lt;p&gt;Having developed several serverless applications using frameworks like AWS SAM and Serverless Framework, it is important to note that some aspects of the service or component are automated for the engineer. A good example is when AWS SAM or Serverless Framework automatically creates one of the critical components in the serverless API: the API Gateway.&lt;/p&gt;

&lt;p&gt;In creating a custom API Gateway resource, the following resources are needed: &lt;code&gt;RestApi&lt;/code&gt;, &lt;code&gt;Resource&lt;/code&gt;, &lt;code&gt;Stage&lt;/code&gt;, &lt;code&gt;Method&lt;/code&gt;, &lt;code&gt;Deployment&lt;/code&gt; and if you are using a custom domain, you will need both a &lt;code&gt;BasePathMapping&lt;/code&gt; and &lt;code&gt;DomainName&lt;/code&gt; resource.&lt;/p&gt;

&lt;p&gt;In the SAM snippet below, just by specifying the Api type in the event source of the function, AWS SAM automatically creates those components for you - (talk about making your life easy) - and after deployment is done, you're greeted with a nicely formatted API Gateway URL with which you can access your APIs. A similar orchestration happens with the Serverless Framework.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;CheckoutFunction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless::Function&lt;/span&gt;
&lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;src/Checkout/index.handler&lt;/span&gt;
  &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Performs order checkout&lt;/span&gt;
  &lt;span class="na"&gt;MemorySize&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;128&lt;/span&gt;
  &lt;span class="na"&gt;Events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Api&lt;/span&gt;
      &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/checkout&lt;/span&gt;
          &lt;span class="s"&gt;Method&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POST&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is useful as necessary - however, there will be times when you will need to orchestrate an API Gateway - hence, will need to define custom resources as listed above. This means you will need to know how to build an API Gateway resource just like the framework does so that you can decompose, isolate and build up as necessary. The lesson here is defaults are good, however, custom resources will need more than defaults.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Comfortable with IaC - Infrastructure as Code
&lt;/h2&gt;

&lt;p&gt;I've been involved in projects where some resources like custom domain names are created via the AWS console. You soon find out that as the project progresses, such resources created become a breaking point for a smooth CI/CD process because they have been manually created and it is possible to forget that such resources were created (this happens a lot with S3 buckets).&lt;/p&gt;

&lt;p&gt;IaC becomes critical when you work in a multi-account setup and you have to deploy services or environments across accounts in an efficient way - most times, offloaded to a CI/CD process - which is the best practice.&lt;/p&gt;

&lt;p&gt;It is possible to become overly focused on functions. However, Infrastructure as Code is critical to your success in developing serverless applications. This includes defining resources like Buckets, Queues, Event Bridges, and others using code that can be checked into source control and integrated with a CI/CD process for an efficient and fast integration and deployment loop. This ensures consistency across environments, as multi-accounts are becoming common. You need to get comfortable with IaC.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;This article shares insight from my journey in developing Serverless Applications on AWS. It looks beyond Lambda functions as the focus of Serverless Applications and shares ideas that I can help an engineer become better at building serverless applications including using flow diagrams for architecture visualisation, understanding the limitations of Lambda and the need for event-drive architectures. It also talks about understanding cloud service detail to make the right judgement as to what service to incorporate in architecture, with a note on the usefulness of IaC.&lt;/p&gt;

&lt;p&gt;Some common tools used include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Diagramming&lt;/li&gt;
&lt;li&gt;Lucid Chart&lt;/li&gt;
&lt;li&gt;Draw.io&lt;/li&gt;
&lt;li&gt;ExcaliDraw&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;IaC/Frameworks&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pulumi&lt;/li&gt;
&lt;li&gt;Terraform&lt;/li&gt;
&lt;li&gt;AWS CDK&lt;/li&gt;
&lt;li&gt;AWS SAM&lt;/li&gt;
&lt;li&gt;Serverless Framework&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I hope this helps. Let me know your thoughts in the comment section.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>aws</category>
      <category>awslambda</category>
    </item>
    <item>
      <title>Serverless API Development on AWS with TypeScript - Part 3</title>
      <dc:creator>Charles Allison</dc:creator>
      <pubDate>Fri, 11 Aug 2023 13:39:18 +0000</pubDate>
      <link>https://dev.to/aws-builders/serverless-api-development-on-aws-with-typescript-part-3-47ao</link>
      <guid>https://dev.to/aws-builders/serverless-api-development-on-aws-with-typescript-part-3-47ao</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In the last part of this series, we will go deeper into Data Access patterns, how they not only determine how we query a DynamoDB table but how they also determine the result of a query. We will also look at a fantastic feature of DynamoDB called &lt;strong&gt;Streams&lt;/strong&gt;. DynamoDB Streams enables event notification for changes in a DynamoDB table. We will also look at Lambda events that have a different event source from API Gateway and how to filter them so that our handler function gets only what our business logic required. Finally, we will be looking at Amazon SNS and how we can use it to send SMS to a customer. All of this is within the context of the Tenant Service Project.&lt;/p&gt;

&lt;p&gt;For a quick recap, in &lt;a href="https://dev.to/aws-builders/serverless-api-development-on-aws-with-typescript-part-1-16h5"&gt;Part 1&lt;/a&gt; we saw the setting up of the project and the necessary configurations when working with the &lt;code&gt;Serverless Framework&lt;/code&gt;. &lt;a href="https://dev.to/aws-builders/serverless-api-development-on-aws-with-typescript-part-2-44hc"&gt;Part 2&lt;/a&gt; discusses the entities in the project with a focus on the Tenant entity. It also discusses the setup of the DynamoDB table which is akin to the &lt;code&gt;Create Table&lt;/code&gt; statement along with its Global Secondary Indexes.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/charlallison" rel="noopener noreferrer"&gt;
        charlallison
      &lt;/a&gt; / &lt;a href="https://github.com/charlallison/tenant-service" rel="noopener noreferrer"&gt;
        tenant-service
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Simple example of building Serverless API on AWS using TypeScript
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Tenant Service&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;The Tenant Service is a serverless application built on AWS Lambda and other services using the Serverless Framework. It provides management of tenants and payments made for a property, as well as basic CRUD operations for properties.&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://private-user-images.githubusercontent.com/2844422/239017010-01d78232-97f4-4ab5-bc1f-c064fa0fa899.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3Mjg0ODQ5NzksIm5iZiI6MTcyODQ4NDY3OSwicGF0aCI6Ii8yODQ0NDIyLzIzOTAxNzAxMC0wMWQ3ODIzMi05N2Y0LTRhYjUtYmMxZi1jMDY0ZmEwZmE4OTkucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MTAwOSUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDEwMDlUMTQzNzU5WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9ZjdjM2VjMDI4NjI1ZTg4YWJmNTg5MDhjNzBhYzk5MWNmNDQ4OTFiZDg2ZWM5ZTRjM2YwZjMzOTRlOTIyYzI4ZCZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.qihNaE-UZqDhZfHk9NoffGI_2yVrEOwfpj4ezhOwkuQ"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fprivate-user-images.githubusercontent.com%2F2844422%2F239017010-01d78232-97f4-4ab5-bc1f-c064fa0fa899.png%3Fjwt%3DeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3Mjg0ODQ5NzksIm5iZiI6MTcyODQ4NDY3OSwicGF0aCI6Ii8yODQ0NDIyLzIzOTAxNzAxMC0wMWQ3ODIzMi05N2Y0LTRhYjUtYmMxZi1jMDY0ZmEwZmE4OTkucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MTAwOSUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDEwMDlUMTQzNzU5WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9ZjdjM2VjMDI4NjI1ZTg4YWJmNTg5MDhjNzBhYzk5MWNmNDQ4OTFiZDg2ZWM5ZTRjM2YwZjMzOTRlOTIyYzI4ZCZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.qihNaE-UZqDhZfHk9NoffGI_2yVrEOwfpj4ezhOwkuQ" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Tenant Management: Create, update, and delete tenant information, including name, phone, and property allocation.&lt;/li&gt;
&lt;li&gt;Payment Management: Record tenant payments, including payment amount and date.&lt;/li&gt;
&lt;li&gt;Property CRUD: Perform basic CRUD operations (Create, Read, Update) on property information, such as property details, address, and rental rates.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Architecture&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;The Tenant Service utilizes the following AWS services:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AWS Lambda:&lt;/strong&gt; Handles the serverless functions for processing tenant, payment and property operations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API Gateway:&lt;/strong&gt; Provides a RESTful API to interact with the service.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DynamoDB:&lt;/strong&gt; Stores tenant and property data, as well as payment records.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EventBridge (CloudWatch Events):&lt;/strong&gt; Triggers lambda function on schedule.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS CloudFormation:&lt;/strong&gt; Automates the provisioning and management of AWS resources.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amazon SNS&lt;/strong&gt; Sends SMS…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/charlallison/tenant-service" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Data Access Patterns
&lt;/h2&gt;

&lt;p&gt;As already established in Part 2, the primary keys in DynamoDB are the basis for Data Access patterns; how data is accessed. These patterns do not only affect &lt;strong&gt;&lt;em&gt;how&lt;/em&gt;&lt;/strong&gt; data is retrieved from a DynamoDB table but also determine &lt;strong&gt;&lt;em&gt;what&lt;/em&gt;&lt;/strong&gt; data is retrieved.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sample Keys in Tenant Service
&lt;/h3&gt;

&lt;p&gt;The code snippet below shows different keys that form the &lt;code&gt;Data Access Patterns&lt;/code&gt; in the &lt;code&gt;Tenant Service&lt;/code&gt;. Recall that in Part 2 also, the default &lt;code&gt;primary key&lt;/code&gt; returns a &lt;em&gt;&lt;strong&gt;tenant&lt;/strong&gt;&lt;/em&gt; record only because of how it was designed - it queries the table using two primary key attributes; &lt;code&gt;PK&lt;/code&gt; and &lt;code&gt;SK&lt;/code&gt;. When we use either &lt;code&gt;GSI1PK&lt;/code&gt; or &lt;code&gt;GSI2PK&lt;/code&gt; , we are making use of a different primary key which is a different access pattern from the default primary key. This access pattern certainly has a different primary hence the difference in the resultset.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tenant default primary key - composite primary key&lt;/span&gt;
&lt;span class="c1"&gt;// Retrieves a Tenant record ONLY&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;PK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`tenant#id=724dab0a-6adb-4d64-91fb-a95b492d9120`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;SK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`profile#id=724dab0a-6adb-4d64-91fb-a95b492d9120`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Tenant GSI keys&lt;/span&gt;
&lt;span class="c1"&gt;// GSI1PK: Retrieves tenant and corresponding record with same GSI1PK&lt;/span&gt;
&lt;span class="c1"&gt;// GSI2PK: Retrieves all tenants&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;GSI1PK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`tenant#id=724dab0a-6adb-4d64-91fb-a95b492d9120`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;GSI2PK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`type#Tenant`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Payment GSI1 key&lt;/span&gt;
&lt;span class="c1"&gt;// Retrieve all payments for a tenant&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;GSI1PK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`tenant#id=724dab0a-6adb-4d64-91fb-a95b492d9120`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Property default primary key - composite primary key&lt;/span&gt;
&lt;span class="c1"&gt;// Retrieves a Property record ONLY&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;PK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`property#id=463fb471-e199-4375-b745-1c17cbbc1931`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;SK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`property#id=463fb471-e199-4375-b745-1c17cbbc1931`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Property GSI1 key&lt;/span&gt;
&lt;span class="c1"&gt;// Retrieves all properties&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;GSI1PK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`type#Property`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A fundamental concept that forms the basis of the &lt;code&gt;single-table&lt;/code&gt; design is what can be described as a &lt;strong&gt;&lt;em&gt;hierarchical&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;relationship&lt;/em&gt; between records. A quick example of this concept in the Tenant Service is that every &lt;code&gt;payment&lt;/code&gt; record must have a tenant record as its root. This means we can get a tenant and all &lt;code&gt;payment&lt;/code&gt; records for that tenant in one call, depending on how we query the table. When a table is queried, you can imagine the resultset as having to use a &lt;code&gt;GROUP BY&lt;/code&gt; clause so that you can see all records irrespective of the entity type, that have the same value for the attribute you are querying by - in this case, a &lt;code&gt;GSI&lt;/code&gt; key. To illustrate this, take a look at the snapshot below.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4kuld1kewd87xehwifyq.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4kuld1kewd87xehwifyq.png" alt="Grouping By GSI1PK"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The snapshot above shows the &lt;code&gt;GSI1&lt;/code&gt; tab selected which displays the records available for the GSI index: &lt;code&gt;GSI1&lt;/code&gt;. Every record that has the &lt;code&gt;GSI1PK&lt;/code&gt; attribute is listed as well. We can see that under the attributes, we have entries for bothtenant and payment entities. Recall that for every GSI, there has to be a corresponding primary key. We have in this case, GSI1PK as theprimary key - it is the partition key as well. The records displayed have a similar GSI1PK value and are therefore grouped  on this value. See below how the GSI1PK keys are formed - they surely have a similar definition.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// payment.ts&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;BuildGSIKeys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;GSI1PK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`tenant#id=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// tenant.ts&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;BuildGSIKeys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prop&lt;/span&gt;&lt;span class="p"&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="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="c1"&gt;// given that id is not undefined&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;GSI1PK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`tenant#id=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;prop&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="s2"&gt;`&lt;/span&gt;
    &lt;span class="c1"&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;This, therefore, means that if the index &lt;code&gt;GSI1&lt;/code&gt; is queried using a &lt;code&gt;GSI1PK&lt;/code&gt; that matches a &lt;code&gt;GSI1PK&lt;/code&gt; entry in the table, we will get the records for the tenant with that &lt;code&gt;GSI1PK&lt;/code&gt; and all corresponding payment records. With this, it becomes very obvious that &lt;code&gt;Data Access Patterns&lt;/code&gt; via GSI is a critical factor which not only determines how a DynamoDB table is queried but also determines the &lt;strong&gt;resultset&lt;/strong&gt; that is returned. The beautiful thing about using DynamoDB here is the fact that a single database call is what is needed to get the records as explained - this is void of a &lt;code&gt;table join&lt;/code&gt; as all the records reside in one table.&lt;/p&gt;

&lt;h2&gt;
  
  
  DynamoDB Streams
&lt;/h2&gt;

&lt;p&gt;When a Tenant pays for a property, it makes sense to update the property's &lt;code&gt;status&lt;/code&gt; as &lt;code&gt;NotAvailable&lt;/code&gt; so that it will not be listed otherwise. This also applies to a tenant such that if the tenant is making payment for the first time, the status attribute will initially be &lt;code&gt;NotActive&lt;/code&gt;. The tenant's &lt;code&gt;status&lt;/code&gt; attribute will need to be updated as well to indicate that this is an active tenant. Given a table design where we have data saved in different tables, after saving the payment record, we will have to ensure that the save-payment operation succeeded before we can go on to update the property or tenant record. Well, DynamoDB has a better way of handling that using &lt;strong&gt;Streams&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;DynamoDB Streams is a feature that generates events when data in a DynamoDB table changes. This could either be an &lt;code&gt;INSERT&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt;, or &lt;code&gt;UPDATE&lt;/code&gt; operations. DynamoDB generates this event in near &lt;strong&gt;real-time&lt;/strong&gt;, containing those changes in the table. To make use of this feature, DynamoDB Streams is enabled in the &lt;strong&gt;Tenant Service&lt;/strong&gt; table resource definition by setting &lt;code&gt;StreamViewType&lt;/code&gt; which is under the &lt;code&gt;StreamSpecification&lt;/code&gt; key, as shown below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;StreamSpecification&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;StreamViewType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NEW_AND_OLD_IMAGES&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can then specify when the event should be triggered given the scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;when a new record is added to the table: &lt;code&gt;NEW_IMAGE&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;when an update is made to an existing record (including deletion): &lt;code&gt;OLD_IMAGE&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;when either a new record is added or an existing record is updated(including deletion): &lt;code&gt;NEW_AND_OLD_IMAGES&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thankfully, &lt;strong&gt;AWS Lambda&lt;/strong&gt; is designed such that this event can be used as a trigger for Lambda functions.&lt;/p&gt;

&lt;p&gt;With Streams enabled, we can be notified by DynamoDB when a &lt;code&gt;payment&lt;/code&gt; record is saved to the table so that both the tenant and property &lt;code&gt;status&lt;/code&gt; attributes are updated accordingly.&lt;/p&gt;

&lt;p&gt;The Lambda function &lt;code&gt;UpdatePropertyStatus&lt;/code&gt; is the &lt;em&gt;event handler&lt;/em&gt; for updating the property status attribute when a payment is saved. It listens for an event from DynamoDB Streams for new a record entry of a payment entity and then performs the update on the property. How does it know the property that was paid for? You may ask. That is handled given that every payment has an attribute containing the attribute &lt;code&gt;propertyId&lt;/code&gt; against which the payment was made. A similar setup is available for updating the tenant attribute &lt;code&gt;status&lt;/code&gt; - the &lt;code&gt;UpdateTenantStatus&lt;/code&gt; Lambda function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;UpdatePropertyStatus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./lambda/UpdatePropertyStatus/index.main&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Updates the status of a property after a successful payment&lt;/span&gt;
  &lt;span class="na"&gt;memorySize&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;128&lt;/span&gt;
  &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
  &lt;span class="na"&gt;iamRoleStatements&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Effect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Allow&lt;/span&gt;
      &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;dynamodb:UpdateItem&lt;/span&gt;
      &lt;span class="na"&gt;Resource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;${self:custom.DatabaseTable.arn}&lt;/span&gt;
  &lt;span class="na"&gt;events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dynamodb&lt;/span&gt;
        &lt;span class="na"&gt;arn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${self:custom.DatabaseTable.streamArn}&lt;/span&gt;
        &lt;span class="na"&gt;filterPatterns&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;eventName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;INSERT&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
            &lt;span class="na"&gt;dynamodb&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;NewImage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;S&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Payment&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code snippet above shows the configuration for the &lt;code&gt;UpdatePropertyStatus&lt;/code&gt; Lambda function - the configuration is where we set the function trigger. Taking a look at the events key, you will notice the entry is set to stream - this refers to the event source of the function. The type key defines the type of stream which is DynamoDB in this case and arn that defines the source of the DynamoDB Stream - the Tenant service table.&lt;/p&gt;

&lt;p&gt;The Lambda function below is the &lt;code&gt;UpdatePropertyStatus&lt;/code&gt; handler. It takes in a &lt;code&gt;DynamoDBStream&lt;/code&gt; event payload as its argument. This event payload contains record(s) that were published when the corresponding event was raised in the table. In this payload, we then deconstruct the &lt;code&gt;NewImage&lt;/code&gt; which is in the &lt;code&gt;dynamodb&lt;/code&gt; field to get the newly saved &lt;code&gt;payment&lt;/code&gt; record. And because we are certain that it is a &lt;code&gt;payment&lt;/code&gt; record entity, doing a cast with the type-casting construct &lt;code&gt;as Payment&lt;/code&gt; comes without worry. But how is that? By event filtering.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ...imports&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;main&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DynamoDBStreamEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NewImage&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;key&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="kr"&gt;any&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Records&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="nx"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;propertyId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;unmarshall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;NewImage&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;Payment&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ddbDocClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;UpdateCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Property&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BuildPK&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;propertyId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;TableName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TENANT_TABLE_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;UpdateExpression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SET #status = :status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;ExpressionAttributeValues&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PropertyStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NotAvailable&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;ExpressionAttributeNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;status&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  DynamoDB Event Filtering
&lt;/h3&gt;

&lt;p&gt;It is important to note that Streams is enabled on a per-table basis. What this means in effect is that the DynamoDB table will generate and raise an event whenever there is a change in the table. Of course, this is depending on the used &lt;code&gt;StreamViewType&lt;/code&gt;. For example, if a new &lt;code&gt;Tenant&lt;/code&gt; or a &lt;code&gt;Property&lt;/code&gt; is saved, the table will publish that event as a record-added event, not a payment-record-added or property-record-added event. This means, any Lambda functions listening for some kind of record-added event from DynamoDB Streams will be triggered. We can filter DynamoDB events such that a Lambda function will only be triggered when certain criteria are fulfilled. For the Tenant service, we want that the &lt;code&gt;UpdatePropertyStatus&lt;/code&gt; function will only be triggered when a new &lt;code&gt;payment&lt;/code&gt; record is saved to the table. Applying this event filter will help us guard against unwanted concurrency issues where all functions listening for some kind of record-added events will be triggered. This is achieved using the &lt;code&gt;filterPatterns&lt;/code&gt; key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dynamodb&lt;/span&gt;
      &lt;span class="na"&gt;arn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${self:custom.DatabaseTable.streamArn}&lt;/span&gt;
      &lt;span class="na"&gt;filterPatterns&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;eventName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;INSERT&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
          &lt;span class="na"&gt;dynamodb&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;NewImage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;S&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Payment&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The snippet above is a part of the Lambda configuration for &lt;code&gt;UpdatePropertyStatus&lt;/code&gt;. This configuration defines through the &lt;code&gt;filterPatterns&lt;/code&gt; key that the Lambda function should only be triggered if the event via the &lt;code&gt;eventName&lt;/code&gt; is an &lt;code&gt;INSERT&lt;/code&gt; operation. The &lt;code&gt;NewImage&lt;/code&gt; represents the new record that was just saved to the table. The filter expression also defines (in addition to the &lt;code&gt;INSERT&lt;/code&gt; operation) that the &lt;code&gt;Type&lt;/code&gt; attribute of the saved record should be evaluated. If the attribute is of type &lt;code&gt;String&lt;/code&gt; hence the &lt;code&gt;S&lt;/code&gt; and the value is equal to "Payment", then the function can be triggered. With this filter condition in place, we can be sure that the Lambda function will only be triggered when a new &lt;code&gt;payment&lt;/code&gt; record is saved to the table. Note that the &lt;code&gt;Type&lt;/code&gt; attribute was added as part of the entity definition. See &lt;a href="https://dev.to/aws-builders/serverless-api-development-on-aws-with-typescript-part-2-44hc"&gt;Part 2&lt;/a&gt; of this series for details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Time-Based Events with EventBridge
&lt;/h2&gt;

&lt;p&gt;The payment entity has an &lt;code&gt;expiresOn&lt;/code&gt; attribute that holds the value for the expiration of the payment. It is calculated when a payment object is created. After a payment record is saved, the &lt;code&gt;UpdateTenantStatus&lt;/code&gt; function updates the &lt;code&gt;status&lt;/code&gt; attribute of the tenant and sets the value of the attribute &lt;code&gt;notifyOn&lt;/code&gt;. This attribute stores the date when the tenant will be notified about the coming expiration of rent - this is set to 1 month prior. Given this requirement, there needs to be a monthly pooling of the tenants' records that meet the criteria, so that they can be notified of rent expiration in the coming month.&lt;/p&gt;

&lt;p&gt;In the architecture diagram, there is a &lt;code&gt;SendPaymentReminder&lt;/code&gt; function that is triggered by the &lt;strong&gt;Amazon EventBridge&lt;/strong&gt;. EventBridge is  set up to trigger the &lt;code&gt;SendPaymentReminder&lt;/code&gt; function &lt;strong&gt;&lt;em&gt;every month, at a certain time&lt;/em&gt;&lt;/strong&gt; using &lt;strong&gt;&lt;em&gt;cron-style scheduling&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cron Scheduling in Action
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;SendPaymentReminder&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./lambda/SendPaymentReminder/index.main&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Notifies tenants via SMS that their rent will expire in the coming month.&lt;/span&gt;
  &lt;span class="na"&gt;memorySize&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;512&lt;/span&gt;
  &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
  &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;SENDER_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;LandLord'&lt;/span&gt;
  &lt;span class="na"&gt;iamRoleStatements&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Effect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Allow&lt;/span&gt;
      &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;dynamodb:Query&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;sns:Publish&lt;/span&gt;
      &lt;span class="na"&gt;Resource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="kt"&gt;!Join&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${self:custom.DatabaseTable.arn}"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;index"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;&lt;span class="pi"&gt;]]&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*'&lt;/span&gt;
  &lt;span class="na"&gt;events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cron(0 12 1W * ? *)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Above is the configuration for the &lt;code&gt;SendPaymentReminder&lt;/code&gt; function. Our focus here is the &lt;code&gt;events&lt;/code&gt; key. It is set to &lt;code&gt;schedule&lt;/code&gt; and the value uses &lt;code&gt;cron&lt;/code&gt; syntax to schedule the function to run at noon on the &lt;em&gt;1st day of the 1st week of every month&lt;/em&gt;. Let's take a look at the event handler for this EventBridge-triggered event.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ...imports&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;main&lt;/span&gt; &lt;span class="o"&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;month&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="o"&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;utc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;month&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toUnixInteger&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GSI2PK&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Tenant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BuildGSIKeys&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ddbDocClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;QueryCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;IndexName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GSIs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GSI2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;TableName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TENANT_TABLE_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;KeyConditionExpression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#gsi2pk = :gsi2pk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;FilterExpression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#notifyOn = :notifyOn AND #status = :status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;ExpressionAttributeValues&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:gsi2pk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GSI2PK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:notifyOn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TenantStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Active&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;ProjectionExpression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#phone, #name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;ExpressionAttributeNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#phone&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;phone&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#notifyOn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;notifyOn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#gsi2pk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GSI2PK&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}));&lt;/span&gt;

  &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;phone&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Pick&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Tenant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;phone&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;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Hi &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, I hope you're doing great. This is a gentle reminder that your rent will expire next month. Please endeavour to pay in due time`&lt;/span&gt;
    &lt;span class="nf"&gt;sendSMS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SENDER_ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this very simple invocation, there is no need for the event argument hence an empty argument list for the &lt;code&gt;main&lt;/code&gt; function. When this function is triggered, some very simple calculations of the date and building of the &lt;code&gt;GSI2PK&lt;/code&gt; key and then a query to DynamoDB. If we have tenants that meet the criteria, an SMS is forwarded accordingly.&lt;/p&gt;

&lt;h2&gt;
  
  
  SMS with Amazon SNS
&lt;/h2&gt;

&lt;p&gt;Sending SMS is easy using the Amazon SNS API. As with every managed service, we only need to provide the necessary configuration and we're off this using the service. This assumes your account is no longer in sandbox mode else you must have to register and verify any phone number you intend to send an SMS.&lt;/p&gt;

&lt;p&gt;In the Lambda configuration below, we specify the permissions needed to send SMS with the SNS API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# SendPaymentReminder/config.yml - other portions omitted for brevity&lt;/span&gt;
&lt;span class="na"&gt;iamRoleStatements&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Effect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Allow&lt;/span&gt;
    &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;sns:Publish&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The current implementation of the Tenant service communicates to the tenants on two occasions. The first is after a payment is made, and the second is when a reminder about the rent's expiration is due. At both times, the SNS is utilised to send an SMS to the tenants' phone number. In the simple code below, the &lt;code&gt;sendSMS&lt;/code&gt; function takes in three arguments; &lt;em&gt;message&lt;/em&gt;, &lt;em&gt;phoneNumber&lt;/em&gt; and &lt;em&gt;sender&lt;/em&gt;. These are used by the &lt;strong&gt;SNS API&lt;/strong&gt; to forward the intended message to the tenant.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ...imports&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sendSMS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;phoneNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sender&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;snsClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PublishCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;PhoneNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;phoneNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;MessageAttributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AWS.SNS.SMS.SenderID&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;DataType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;String&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;StringValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;In this article we talked about DynamoDB keys as the basis for Data Access Patterns, and how they can be a factor in determining the resultset of queries.&lt;/p&gt;

&lt;p&gt;We also saw that DynamoDB Streams is a great feature and should be used where necessary. However, it is essential to apply a filter to the events so that the appropriate Lambda functions are triggered instead of having all functions that are listening to a particular source be triggered at the same time. The filter also helps us to avoid having several &lt;code&gt;if&lt;/code&gt; conditions inside Lambda functions.&lt;/p&gt;

&lt;p&gt;Understanding event types and the structure of their payload is key to manipulating the data. It is okay to log the event payload to CloudWatch to get an idea of this structure if the documentation is not clear enough.&lt;/p&gt;

&lt;p&gt;This brings me to the conclusion of the series: &lt;em&gt;&lt;strong&gt;Serverless API Development on AWS with TypeScript&lt;/strong&gt;&lt;/em&gt;. I am glad to have started  this series, and I believe it is presented in a way that is easy to understand.&lt;/p&gt;

&lt;h3&gt;
  
  
  Resources
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://www.dynamodbbook.com/" rel="noopener noreferrer"&gt;The DynamoDB Book&lt;/a&gt; - by &lt;a href="https://www.alexdebrie.com/" rel="noopener noreferrer"&gt;AWS Data Hero Alex Debrie&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://phatrabbitapps.com/dynamodbdemistyfied-chapter-1" rel="noopener noreferrer"&gt;DynamoDB Demystified&lt;/a&gt; - Blog Series by &lt;a href="https://www.linkedin.com/in/rosius/" rel="noopener noreferrer"&gt;AWS Serverless Hero Rosius Ndimofor&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://serverlessland.com/patterns/" rel="noopener noreferrer"&gt;Serverless Patterns&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/sns/latest/dg/welcome.html" rel="noopener noreferrer"&gt;Amazon SNS Developer Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/services-cloudwatchevents.html" rel="noopener noreferrer"&gt;AWS Lambda Developer Guide - EventBridge (CloudWatch Events)&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>serverless</category>
      <category>aws</category>
      <category>typescript</category>
      <category>api</category>
    </item>
    <item>
      <title>Serverless API Development on AWS with TypeScript - Part 2</title>
      <dc:creator>Charles Allison</dc:creator>
      <pubDate>Thu, 10 Aug 2023 07:13:00 +0000</pubDate>
      <link>https://dev.to/aws-builders/serverless-api-development-on-aws-with-typescript-part-2-49o</link>
      <guid>https://dev.to/aws-builders/serverless-api-development-on-aws-with-typescript-part-2-49o</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.to/aws-builders/serverless-api-development-on-aws-with-typescript-part-1-16h5"&gt;Part 1 of this series&lt;/a&gt; was an introduction to the Tenant Service project, the AWS services used and configurations for the Serverless Framework.&lt;/p&gt;

&lt;p&gt;In this article, part 2, we will take a deeper look at the composition of the entities because they play a vital role in how we define our table schema when adopting the &lt;code&gt;Single Table&lt;/code&gt; design strategy. Also contained in this part 2 is Lambda function configuration with the Serverless Framework, configurations for creating DynamoDB table, keys and Global Secondary Index.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/charlallison" rel="noopener noreferrer"&gt;
        charlallison
      &lt;/a&gt; / &lt;a href="https://github.com/charlallison/tenant-service" rel="noopener noreferrer"&gt;
        tenant-service
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Simple example of building Serverless API on AWS using TypeScript
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Tenant Service&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;The Tenant Service is a serverless application built on AWS Lambda and other services using the Serverless Framework. It provides management of tenants and payments made for a property, as well as basic CRUD operations for properties.&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://private-user-images.githubusercontent.com/2844422/239017010-01d78232-97f4-4ab5-bc1f-c064fa0fa899.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3Mjg0ODQ5OTksIm5iZiI6MTcyODQ4NDY5OSwicGF0aCI6Ii8yODQ0NDIyLzIzOTAxNzAxMC0wMWQ3ODIzMi05N2Y0LTRhYjUtYmMxZi1jMDY0ZmEwZmE4OTkucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MTAwOSUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDEwMDlUMTQzODE5WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9ODRiYjA3OTQ5MmY1NzAzYTFlNmFhMjY1Y2Q2MTVmMWU5M2JmMTdmOTRlMGMyZjUyMGE0Mjg0ZDNkNzc1NTdkYyZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.UrHmfRd1pq7ncxJpbk6Cho04O5SL8qxOmswRc-jNjI0"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fprivate-user-images.githubusercontent.com%2F2844422%2F239017010-01d78232-97f4-4ab5-bc1f-c064fa0fa899.png%3Fjwt%3DeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3Mjg0ODQ5OTksIm5iZiI6MTcyODQ4NDY5OSwicGF0aCI6Ii8yODQ0NDIyLzIzOTAxNzAxMC0wMWQ3ODIzMi05N2Y0LTRhYjUtYmMxZi1jMDY0ZmEwZmE4OTkucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MTAwOSUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDEwMDlUMTQzODE5WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9ODRiYjA3OTQ5MmY1NzAzYTFlNmFhMjY1Y2Q2MTVmMWU5M2JmMTdmOTRlMGMyZjUyMGE0Mjg0ZDNkNzc1NTdkYyZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.UrHmfRd1pq7ncxJpbk6Cho04O5SL8qxOmswRc-jNjI0" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Tenant Management: Create, update, and delete tenant information, including name, phone, and property allocation.&lt;/li&gt;
&lt;li&gt;Payment Management: Record tenant payments, including payment amount and date.&lt;/li&gt;
&lt;li&gt;Property CRUD: Perform basic CRUD operations (Create, Read, Update) on property information, such as property details, address, and rental rates.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Architecture&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;The Tenant Service utilizes the following AWS services:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AWS Lambda:&lt;/strong&gt; Handles the serverless functions for processing tenant, payment and property operations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API Gateway:&lt;/strong&gt; Provides a RESTful API to interact with the service.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DynamoDB:&lt;/strong&gt; Stores tenant and property data, as well as payment records.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EventBridge (CloudWatch Events):&lt;/strong&gt; Triggers lambda function on schedule.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS CloudFormation:&lt;/strong&gt; Automates the provisioning and management of AWS resources.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amazon SNS&lt;/strong&gt; Sends SMS…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/charlallison/tenant-service" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Entities
&lt;/h2&gt;

&lt;p&gt;The Tenant Service contains these entities &lt;code&gt;tenant&lt;/code&gt;, &lt;code&gt;payment&lt;/code&gt; and &lt;code&gt;property&lt;/code&gt;. Let's take a look at the Tenant entity.&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;v4&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;uuid&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="kr"&gt;enum&lt;/span&gt; &lt;span class="nx"&gt;TenantStatus&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Active&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;InActive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Inactive&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Tenant&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;PK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;SK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;TenantStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;InActive&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Tenant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;GSI1PK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;GSI2PK&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="nf"&gt;constructor&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="nb"&gt;Partial&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Tenant&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&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;id&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nf"&gt;v4&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="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;phone&lt;/span&gt; &lt;span class="o"&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;phone&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SK&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Tenant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BuildPK&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;id&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;PK&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;PK&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;SK&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SK&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GSI1PK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;GSI2PK&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Tenant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BuildGSIKeys&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GSI1PK&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;GSI1PK&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;GSI2PK&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;GSI2PK&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;BuildPK&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;PK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`tenant#id=&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;SK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`profile#id=&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="s2"&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;static&lt;/span&gt; &lt;span class="nc"&gt;BuildGSIKeys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prop&lt;/span&gt;&lt;span class="p"&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;GSI1PK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`tenant#id=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;prop&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;GSI2PK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`type#&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;Tenant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;In the &lt;code&gt;src/tenant.ts&lt;/code&gt; file, we have pretty self-explanatory fields and two static functions. We also have an enumeration that is used to categorise tenants into;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;those whose payments are still valid and have not expired as &lt;code&gt;Active&lt;/code&gt; and&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;those whose payments have expired and need to be renewed or no longer reside in the property as &lt;code&gt;Inactive&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The static functions, &lt;code&gt;BuildPK&lt;/code&gt; and &lt;code&gt;BuildGSIKeys&lt;/code&gt; are for formatting primary keys for both our default table and for Global Secondary Index (GSIs). These keys help with data manipulation in DynamoDB. These static functions are used as a convention in the Tenant Service such that the &lt;code&gt;BuildPK&lt;/code&gt; is used to format the Primary key and &lt;code&gt;BuildGSIKeys&lt;/code&gt; is used to format GSI keys where applicable. More about keys in the DynamoDB section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lambda Configuration
&lt;/h2&gt;

&lt;p&gt;Lambda function configuration refers to parameters needed for the execution of a lambda function. This includes the function trigger and memory requirements, environment variables, AWS role and other services that the function will need to interact with. The Serverless Framework makes developing and deploying a Lambda function easy. However, it is up to you to organise your code in your preferred way that will be easy to navigate. I have organised the Tenant service in such a way that each lambda function resides in a separate directory to isolate them from the other functions. This provides a clear visual indication that all the required files for a function are located within a single directory.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;./lambda&lt;/code&gt; directory contains all the functions that make our API. I prefer to name a function directory using _verb_s as it is easy to spot where a function resides based on its name and the action it performs. Let's take a look at the &lt;code&gt;CreateTenant&lt;/code&gt; function.&lt;/p&gt;

&lt;h3&gt;
  
  
  CreateTenant Function
&lt;/h3&gt;

&lt;p&gt;The CreateTenant directory contains three (3) files; &lt;code&gt;config.yml&lt;/code&gt;, &lt;code&gt;schema.ts&lt;/code&gt; and &lt;code&gt;index.ts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Config.yml&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The config.yml file contains necessary function configurations, description, deployment and invocation mechanism using the Serverless Framework. Every necessary configuration as per application requirement for this function has to be declared here. The important keys in this file are described below:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;handler&lt;/code&gt; - points to the TypeScript function to be invoked when the corresponding event is triggered - in this case, it is the &lt;code&gt;main&lt;/code&gt; function.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;memorySize&lt;/code&gt; and &lt;code&gt;timeOut&lt;/code&gt; - has to do with the amount of memory needed and execution duration of the function&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;environment&lt;/code&gt;: key for specifying environment variables if needed&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;iamRoleStatements&lt;/code&gt;: lists permissions and the object of action in the AWS ecosystem. In the configuration below we have the &lt;code&gt;dynamodb:PutItem&lt;/code&gt; action with permission set to &lt;code&gt;allow&lt;/code&gt; on a database table resource via its resource name.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;events&lt;/code&gt;: lists the triggers for the function - in this case, it is &lt;code&gt;http&lt;/code&gt; which refers to an invocation from API Gateway. It also specifies the request method as &lt;code&gt;POST&lt;/code&gt; and its path as &lt;code&gt;/tenants&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;schema.ts&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
This file contains the JSON schema for validating the request payload for the CreateProperty function. This JSON schema is used by middy middleware to validate the request payload before the lambda function is executed. An error is thrown if the shape of the payload does not conform to the schema.&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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&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="s1"&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;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="s1"&gt;body&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;body&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="s1"&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;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="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;phone&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;name&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="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;phone&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="s1"&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="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;em&gt;index.ts&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
contains the code that will be executed when the function is triggered&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;handler&lt;/code&gt; is a function of type &lt;code&gt;ValidatedEventAPIGatewayProxyEvent&amp;lt;T&amp;gt;&lt;/code&gt;. It receives an argument name &lt;code&gt;event&lt;/code&gt; that has been formatted to match a specified schema via the generic &lt;code&gt;&amp;lt;typeof S&amp;gt;&lt;/code&gt; construct. The event argument contains event-related fields which include &lt;code&gt;body&lt;/code&gt;, &lt;code&gt;queryStringParameters&lt;/code&gt; and &lt;code&gt;pathParameters&lt;/code&gt; fields from where the user data is gotten.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The exported &lt;code&gt;main&lt;/code&gt; function is lambda's entry point. It takes in the handler function and schema field via the &lt;code&gt;middyfy&lt;/code&gt; function and chains &lt;code&gt;jsonBodyParser&lt;/code&gt;, &lt;code&gt;validator&lt;/code&gt; and &lt;code&gt;httpErrorHandler&lt;/code&gt; middlewares together before finally calling the &lt;code&gt;handler&lt;/code&gt; function.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Type definition function
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;ValidatedAPIGatewayProxyEvent&lt;/code&gt; is the type definition for the &lt;code&gt;handler&lt;/code&gt; function used in &lt;code&gt;index.ts&lt;/code&gt;. This function sets the shape of the &lt;code&gt;body&lt;/code&gt; and &lt;code&gt;queryStringParameters&lt;/code&gt; fields in the request payload proxied by API Gateway.&lt;/p&gt;

&lt;p&gt;This &lt;code&gt;src/libs/api-gateway.ts&lt;/code&gt; file also contains a function &lt;code&gt;formatJSONResponse&lt;/code&gt; that formats response for API Gateway.&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="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;APIGatewayProxyEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;APIGatewayProxyResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Handler&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aws-lambda&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;FromSchema&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;json-schema-to-ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ValidatedAPIGatewayProxyEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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;APIGatewayProxyEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;body&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;queryStringParameters&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FromSchema&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;queryStringParameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FromSchema&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&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;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ValidatedEventAPIGatewayProxyEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Handler&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ValidatedAPIGatewayProxyEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&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;APIGatewayProxyResult&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formatJSONResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  DynamoDB Configuration
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Single-Table approach...certainly not for the faint-hearted.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Paul Swali&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Designing for a relational database software system sometimes requires that one achieves normalization - a 3NF, by splitting data into separate tables where each table represents an entity. You could have a &lt;code&gt;join table&lt;/code&gt; as needed. The Single Table design requires a different approach which could be challenging at least for the first time and in some complex scenarios.&lt;/p&gt;

&lt;p&gt;It is important to note that DynamoDB does not constrain you to the use of only the Single table design. You are welcome to design your DynamoDB tables as though you were working on a relational database system with several tables in place. With that said, let's dive into the Tenant service table &lt;em&gt;schema&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The configuration for the DynamoDB resource is found in &lt;code&gt;./resource/database-table.yml&lt;/code&gt; which contains some sort of &lt;code&gt;DDL&lt;/code&gt; statement. Given the fact that all entities will be stored in a single table, we have to define generic &lt;code&gt;keys&lt;/code&gt; that will be meaningful across all entities. In other words, the attributes selected to serve as keys and ensure data integrity must apply to &lt;code&gt;Tenant&lt;/code&gt;, &lt;code&gt;Payment&lt;/code&gt; and &lt;code&gt;Property&lt;/code&gt; entities. To make that generic and easy to manipulate, it is best to name them by their function. For the Tenant service, &lt;code&gt;PK&lt;/code&gt; refers to the &lt;strong&gt;partition&lt;/strong&gt; key and &lt;code&gt;SK&lt;/code&gt;, &lt;strong&gt;sort&lt;/strong&gt; key.&lt;/p&gt;

&lt;p&gt;The DynamoDB resource file &lt;code&gt;database-table.yml&lt;/code&gt; reveals self-descriptive fields except for a few. The table is designed to have a &lt;code&gt;composite primary key&lt;/code&gt; and two &lt;code&gt;Global Secondary Indexes&lt;/code&gt; that have their respective primary keys; &lt;code&gt;GSI1PK&lt;/code&gt; and &lt;code&gt;GSI2PK&lt;/code&gt;. The table also enables &lt;code&gt;DynamoDB Streaming&lt;/code&gt; via the &lt;code&gt;StreamSpecification&lt;/code&gt; key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;TenantServiceTable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::DynamoDB::Table&lt;/span&gt;
  &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;TableName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${self:service}-${self:provider.stage}-Table&lt;/span&gt;
    &lt;span class="na"&gt;BillingMode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PAY_PER_REQUEST&lt;/span&gt;
    &lt;span class="na"&gt;AttributeDefinitions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;AttributeName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PK&lt;/span&gt;
        &lt;span class="na"&gt;AttributeType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;S&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;AttributeName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SK&lt;/span&gt;
        &lt;span class="na"&gt;AttributeType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;S&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;AttributeName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GSI1PK&lt;/span&gt;
        &lt;span class="na"&gt;AttributeType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;S&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;AttributeName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GSI2PK&lt;/span&gt;
        &lt;span class="na"&gt;AttributeType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;S&lt;/span&gt;
    &lt;span class="na"&gt;KeySchema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;AttributeName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PK&lt;/span&gt;
        &lt;span class="na"&gt;KeyType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HASH&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;AttributeName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SK&lt;/span&gt;
        &lt;span class="na"&gt;KeyType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RANGE&lt;/span&gt;
    &lt;span class="na"&gt;StreamSpecification&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;StreamViewType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NEW_IMAGE&lt;/span&gt;
    &lt;span class="na"&gt;GlobalSecondaryIndexes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;IndexName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GSI1&lt;/span&gt;
        &lt;span class="na"&gt;KeySchema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;AttributeName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GSI1PK&lt;/span&gt;
            &lt;span class="na"&gt;KeyType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HASH&lt;/span&gt;
        &lt;span class="na"&gt;Projection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;ProjectionType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ALL&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;IndexName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GSI2&lt;/span&gt;
        &lt;span class="na"&gt;KeySchema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;AttributeName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GSI2PK&lt;/span&gt;
            &lt;span class="na"&gt;KeyType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HASH&lt;/span&gt;
        &lt;span class="na"&gt;Projection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;ProjectionType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ALL&lt;/span&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  DynamoDB Keys - PK &amp;amp; SK
&lt;/h3&gt;

&lt;p&gt;A composite primary key is a primary key that consists of more than one attribute - which is used to uniquely identify a record in a table. Leveraging the composite key concept, we have the &lt;code&gt;PK&lt;/code&gt; and &lt;code&gt;SK&lt;/code&gt; defined in the &lt;code&gt;KeySchema&lt;/code&gt; field. The primary key determines the &lt;code&gt;Data Access Patterns&lt;/code&gt; in DynamoDB which is how data is accessed within a table. Care has to be taken when deciding on how a primary key is constructed. In the snippet below, we see how the Tenant's entity primary key is constructed; an id is passed in as an argument to the &lt;code&gt;BuildPK&lt;/code&gt; function and we have an object with two attributes as the primary key - this primary key has to match the requirement of the primary key definition in the &lt;code&gt;./resource/database-table.yml&lt;/code&gt; file.&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;static&lt;/span&gt; &lt;span class="nc"&gt;BuildPK&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;PK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`tenant#id=&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="s2"&gt;`&lt;/span&gt;
    &lt;span class="na"&gt;SK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`profile#id=&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="s2"&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;To retrieve a tenant's record from the table, this primary key is used. By using the primary key, a &lt;code&gt;full table scan&lt;/code&gt; is avoided because of its performance and cost implications. We may need to retrieve a tenant's record by a different attribute other than the initial primary key. When this happens we then need to create an alternative primary key typically seen as another &lt;code&gt;Data Access Pattern&lt;/code&gt;. In doing so, a tenant record can be retrieved based on that attribute that now forms the primary key. How can this alternative primary key be created?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Given the needs of an application, multiple Data Access Patterns would need to be utilized for effective data retrieval&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Global Secondary Index
&lt;/h3&gt;

&lt;p&gt;To keep it simple, a &lt;code&gt;Global Secondary Index - (GSI)&lt;/code&gt; enables you to create an alternative primary key using a different attribute. This ultimately provides more options with which we can query a DynamoDB table.&lt;/p&gt;

&lt;p&gt;The Tenant entity has two fields; &lt;code&gt;GSI1PK&lt;/code&gt; and &lt;code&gt;GSI2PK&lt;/code&gt; that serve as primary keys to the GSIs defined in the &lt;code&gt;GlobalSecondaryIndexes&lt;/code&gt; key. There are two indexes; &lt;code&gt;GSI1&lt;/code&gt; and &lt;code&gt;GSI2&lt;/code&gt;, the key type and projection which specifies the attributes you want to return when that index is used.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Payment&lt;/code&gt; and &lt;code&gt;Property&lt;/code&gt; entities have just one GSI key which is the &lt;code&gt;GSI1PK&lt;/code&gt; . In Part 3 of this series, we will look at the usefulness of having a generic key such that entities share similar keys but with different functionality. It is important to note that GSIs and their corresponding keys are added on a per-need basis - the needed Data Access Pattern should determine what keys are created and their composition.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keys In Action
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ...imports&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ValidatedEventAPIGatewayProxyEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{[&lt;/span&gt;&lt;span class="na"&gt;key&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="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queryStringParameters&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GSI2PK&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Tenant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BuildGSIKeys&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ddbDocClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;QueryCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;TableName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TENANT_TABLE_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;IndexName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GSIs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GSI2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;KeyConditionExpression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GSI2PK = :gsi2pk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;FilterExpression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#status = :status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;ExpressionAttributeValues&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:gsi2pk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GSI2PK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;ExpressionAttributeNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;ProjectionExpression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id, #name&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;tenants&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Pick&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Tenant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&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;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;formatJSONResponse&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tenants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;tenants&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 snippet above contains a DynamoDB Query command operation where an Index and its corresponding primary key are used to fetch one or more records from a DynamoDB table. This is achieved using the &lt;code&gt;IndexName&lt;/code&gt; and &lt;code&gt;KeyConditionExpression&lt;/code&gt; keys. You can also specify filter conditions using the &lt;code&gt;FilterExpression&lt;/code&gt; key as necessary.&lt;/p&gt;

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

&lt;p&gt;In this article we took a step further into entities and their composition, using the &lt;code&gt;tenant&lt;/code&gt; entity as a case study. We also looked at keys, how they were constructed and their usefulness in index operations.&lt;/p&gt;

&lt;p&gt;Lambda configuration came next as we looked at how the Serverless Framework applies our configuration for AWS Lambda. The configuration also affects how functions are written as we have to specify the services, interactions and the actual operations (permissions) that can be carried out by the function.&lt;/p&gt;

&lt;p&gt;A major section of this article looked at the DynamoDB table service. While this isn't an in-depth review of the DynamoDB service and features, we saw the usefulness of primary key - composite and non-composite types, Global Secondary Index and how you can select what Index you intend to use for a query operation.&lt;/p&gt;

&lt;p&gt;In Part 3 of this article, we will go deeper into how primary keys determine what data is returned. We will also see DynamoDB streams in action - with this, you can track changes to your table immediately after a DML operation without issuing another query.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>aws</category>
      <category>typescript</category>
      <category>api</category>
    </item>
    <item>
      <title>Serverless API Development on AWS with TypeScript - Part 1</title>
      <dc:creator>Charles Allison</dc:creator>
      <pubDate>Tue, 23 May 2023 08:57:01 +0000</pubDate>
      <link>https://dev.to/aws-builders/serverless-api-development-on-aws-with-typescript-part-1-16h5</link>
      <guid>https://dev.to/aws-builders/serverless-api-development-on-aws-with-typescript-part-1-16h5</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xIzOiCKd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bji0u8yxetk3tbimch2i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xIzOiCKd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bji0u8yxetk3tbimch2i.png" alt="Tenant service architecture" width="800" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;This article is written to help anyone who needs a guide in building their first serverless API on AWS using the Serverless Framework and TypeScript. If you have never built an API using the Serverless Framework, you would need to set up your development environment. Check out my post on &lt;a href="https://dev.to/aws-builders/setting-up-for-serverless-development-with-aws-28gf"&gt;Setting up for Serverless Development on AWS&lt;/a&gt;, follow the steps to download and install the components needed to complete this project.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you are familiar with a concept or service go ahead and skip it to save yourself some time.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  About this Project
&lt;/h2&gt;

&lt;p&gt;This Tenant-service project represents a property-rental scenario where a tenant rents an available property and receives an SMS notification for payment. After that happens, the tenant becomes active and the property becomes unavailable to be rented by another tenant. Subsequently, an active tenant can renew the rent for their current property. Finally, an SMS notification is sent to tenants 1 month before the expiration of the rent. The operations include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a tenant&lt;/li&gt;
&lt;li&gt;Update tenant record&lt;/li&gt;
&lt;li&gt;List all tenants by status&lt;/li&gt;
&lt;li&gt;Delete a tenant&lt;/li&gt;
&lt;li&gt;Record a payment transaction&lt;/li&gt;
&lt;li&gt;Send an SMS after payment&lt;/li&gt;
&lt;li&gt;Send a reminder via SMS, one month before the expiration&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Integrated AWS Services
&lt;/h2&gt;

&lt;p&gt;The Tenant service project is built on AWS as a serverless API. I have included a brief description of the AWS services used in this project and how they work. As we go on you will see how they are used in this project. These five (5) services are:&lt;/p&gt;

&lt;h3&gt;
  
  
  Amazon API Gateway
&lt;/h3&gt;

&lt;p&gt;Amazon API Gateway receives and directs traffic to the appropriate service or handler that is responsible for that API request. Of course, such interactions must have been linked. Users can access whatever backend services you provide via the API Gateway. After the request is handled, the response is forwarded back to the API Gateway and finally, to the user. Amazon API Gateway is completely managed hence you do not need to install any server or component to start using it. It supports REST APIs as well as WebSocket APIs and also integrates easily with other supported AWS services.&lt;/p&gt;

&lt;h3&gt;
  
  
  AWS Lambda
&lt;/h3&gt;

&lt;p&gt;AWS Lambda is one of the compute services for Serverless Computing on AWS. You can deploy a function - yes! just a function - even if the function only returns the traditional "Hello World" string, you can deploy that function to AWS Lambda and Lambda will invoke your function when it is triggered. AWS Lambda supports many programming languages but this project uses TypeScript.&lt;/p&gt;

&lt;h3&gt;
  
  
  Amazon DynamoDB
&lt;/h3&gt;

&lt;p&gt;Amazon DynamoDB is a NoSQL, Key-Valued based Serverless database by AWS. It is highly efficient and can power high-performance applications at scale. Given it is serverless, there's no need for provisioning - you just need to configure your table and get started using it. We will be using this database to create the table used in this project.&lt;/p&gt;

&lt;h4&gt;
  
  
  DynamoDB Stream
&lt;/h4&gt;

&lt;p&gt;Dynamodb Streams is one of the cool features of the Dynamodb Store. A stream is created when an &lt;code&gt;INSERT&lt;/code&gt;, &lt;code&gt;MODIFY&lt;/code&gt; or &lt;code&gt;DELETE&lt;/code&gt; action is carried out on a DynamoDB table. Streaming by DynamoDB has to be enabled - it is not automated enabled. We will be using the streams to initiate checks and perform more logic based on an event in the table.&lt;/p&gt;

&lt;h3&gt;
  
  
  Amazon EventBridge
&lt;/h3&gt;

&lt;p&gt;At the foundation of AWS Lambda is the concept of a trigger. A trigger is an event that causes your lambda function to be invoked or executed. An example of a trigger is a user request - when it hits Amazon API Gateway and the endpoint references a Lambda function, that function is invoked. Another way a Lambda function can be invoked is by a time-based event, similar to a cron job schedule. Amongst several uses of EventBridge, it also keeps track of schedules and triggers functions as necessary based on such schedules.&lt;/p&gt;

&lt;h3&gt;
  
  
  Amazon SNS
&lt;/h3&gt;

&lt;p&gt;SNS is short for Simple Notification Service. It is a pub/sub for application-to-application and application-to-person messaging systems. It is serverless hence no need for installation of any kind. You only need to configure how you want to use it. We will be using it to send SMS to the users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bootstrapping the Project
&lt;/h2&gt;

&lt;p&gt;Execute the following command in a console to clone the project and get started.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/charlallison/tenant-service.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the clone operation is completed, you should have a directory containing files as rendered in the image below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iwrQBWm5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bhqu434hkyt7j9hp1p31.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iwrQBWm5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bhqu434hkyt7j9hp1p31.png" alt="Tenant service project structure" width="458" height="888"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Files, Folder Structure and Project components
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;serverless.yml&lt;/code&gt; - the single most important file in the project. This file contains the configuration that the Serverless Framework uses to interact with and deploy our project to our AWS platform via our account.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;package.json&lt;/code&gt;, &lt;code&gt;package-lock.json&lt;/code&gt; - contains information about dependencies for our project. These include the necessary AWS dependencies, TypeScript or Node-project-specific packages used.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;tsconfig.json&lt;/code&gt;, &lt;code&gt;tsconfig.paths.json&lt;/code&gt; - contains configurations for the TypeScript compiler - remember, the project is written using TypeScript.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;src&lt;/code&gt; - short for Source, refers to a folder containing &lt;code&gt;source code&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;lambda&lt;/code&gt; - contains lambda functions and related configurations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;resource&lt;/code&gt; - contains code (in yml) used to set up our table in DynamoDB and other configurations like streams and indexes.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The serverless.yml file
&lt;/h3&gt;

&lt;p&gt;I stated that the &lt;code&gt;serverless.yml&lt;/code&gt; file is the most important file in the project. If that is the case, it is worth reviewing to understand its content.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aY1j3ywe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ucarecdn.com/b706a7bf-6e5d-4e2c-86d8-5c4d03a03286/" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aY1j3ywe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ucarecdn.com/b706a7bf-6e5d-4e2c-86d8-5c4d03a03286/" alt="serverless-file.png" width="743" height="1409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Some important keys to note:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;service&lt;/code&gt;: sets the name of the service - tenant-service here&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;package&lt;/code&gt;: packages function individually with their dependencies. However, it excludes dev dependencies.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;provider&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;name&lt;/code&gt;: specifies the name of the cloud provider - aws in this case&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;profile&lt;/code&gt;: specifies the profile with which to interact with the AWS platform - the profile contains credentials for authentication and authorization.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;tracing&lt;/code&gt;: enables distributed tracing for all lambda functions in the project and the API Gateway&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;functions&lt;/code&gt;: specifies lambda function config files. We will see more of this in the next sessions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;resources&lt;/code&gt;: used to specify resources used in the project. We only have the DynamoDB table as our resource in this project. Its definition and configuration are located in the &lt;code&gt;database-table.yml&lt;/code&gt; file with the &lt;code&gt;TenantServiceTable&lt;/code&gt; key&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;

&lt;p&gt;&lt;code&gt;plugins&lt;/code&gt;: you can call these helper function for the serverless.yml file -&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;esbuild&lt;/code&gt;: used to build the functions&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;serverelss-iam-roles-per-functions&lt;/code&gt;: used to indicate that each function can have its permission or role instead of grouping all under one.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;custom&lt;/code&gt;: All the keys mentioned are defined by the serverless framework but there could be cases where you want a user-defined key. The custom key is where you can define a user-defined key that can be used in the other parts of the file.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Entities
&lt;/h3&gt;

&lt;p&gt;Entities are a representation of real-world objects as used in source code relating to the context. Of course, there is a relationship with the database in which they are stored. For the tenant service, we have three entities that we will be working with. They contain fields that hold data and that can be used in operations.&lt;/p&gt;

&lt;p&gt;When working with DynamoDB it is a good practice to include fields or attributes that will be used for indexing - more on this later. The following are entities in the tenant service project and their respective fields. It is worth mentioning that the indexing attributes are made up of the application attributes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Tenant&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;application attributes: &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;phone&lt;/code&gt;, &lt;code&gt;status&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;primary key: &lt;code&gt;PK&lt;/code&gt;, &lt;code&gt;SK&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;index attributes: &lt;code&gt;GSI1PK&lt;/code&gt;, &lt;code&gt;GSI2PK&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Property&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;application attributes: &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;city&lt;/code&gt;, &lt;code&gt;state&lt;/code&gt;, &lt;code&gt;address&lt;/code&gt;, &lt;code&gt;cost&lt;/code&gt;, &lt;code&gt;rooms&lt;/code&gt;, &lt;code&gt;status&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;primary key: &lt;code&gt;PK&lt;/code&gt;, &lt;code&gt;SK&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;index attributes: &lt;code&gt;GSI1PK&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Payment&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;application attributes: &lt;code&gt;propertyId&lt;/code&gt;, &lt;code&gt;tenantId&lt;/code&gt;, &lt;code&gt;amount&lt;/code&gt;, &lt;code&gt;paidOn&lt;/code&gt;, &lt;code&gt;expiresOn&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;primary key: &lt;code&gt;PK&lt;/code&gt;, &lt;code&gt;SK&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;index attributes: &lt;code&gt;GSI1PK&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is okay at this point to say we will be leveraging a strategy known as &lt;strong&gt;Single table design&lt;/strong&gt;. This is a table design strategy where all of your data is saved to one table with no joins — more on this in subsequent parts of this article.&lt;/p&gt;

&lt;p&gt;Three other files that contain very useful functions are the files in the &lt;code&gt;/src/libs&lt;/code&gt; directory. These functions are used across the service and it makes sense to have a single reference point.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;api-gateway.ts&lt;/code&gt;: has two-fold usage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;enables schema validation with middy&lt;/li&gt;
&lt;li&gt;formats the response message for API gateway service.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;aws-client.ts&lt;/code&gt;: contains initialization code for AWS clients&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;lambda.ts&lt;/code&gt;: contains a middy function that chains body-parser, validator and error-handler middlewares.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;We have looked at the project structure, some important files and AWS services used in this project. We now have an idea of its setup. In the next article of this series, we will look at the lambda functions and the necessary configurations needed to them deploy on AWS. Comments are certainly appreciated. If there are questions, I will try my best to answer them. I hope this was informative and thank you for sticking right on till the end.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Setting up for Serverless Development with AWS</title>
      <dc:creator>Charles Allison</dc:creator>
      <pubDate>Mon, 30 Jan 2023 13:57:33 +0000</pubDate>
      <link>https://dev.to/aws-builders/setting-up-for-serverless-development-with-aws-28gf</link>
      <guid>https://dev.to/aws-builders/setting-up-for-serverless-development-with-aws-28gf</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhpft94q8zvv3ojw3ttnq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhpft94q8zvv3ojw3ttnq.png" alt="article header" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
   &lt;/p&gt;
&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Beginning serverless development requires that you have certain packages or software installed on your computer that make it easy to develop applications using the Serverless Framework. This article outlines the process of downloading and installing the various packages needed. You should already have an AWS account to continue with the steps below.&lt;/p&gt;

&lt;p&gt;   &lt;/p&gt;
&lt;h2&gt;
  
  
  Step 1: AWS IAM - Programmatic Access
&lt;/h2&gt;

&lt;p&gt;AWS IAM (Identity and Access Management) is an access and permissions management service provided by AWS with fine-grained control over what services and usability boundaries apply to an IAM account. One great feature of an IAM user account is that it is different from your root account therefore you can easily revoke permissions whenever you feel the need to. It is advisable to set up an IAM user account for use in your development environment instead of using your root account.&lt;/p&gt;

&lt;p&gt;We are going to set up an IAM account with &lt;em&gt;programmatic access&lt;/em&gt; only - such that this IAM user will be able to access AWS resources via API calls only.&lt;/p&gt;

&lt;p&gt;Launch your browser and navigate to the &lt;a href="https://aws.amazon.com" rel="noopener noreferrer"&gt;AWS Management Console&lt;/a&gt;. Enter the word &lt;em&gt;users&lt;/em&gt; in the search bar and then click on the &lt;em&gt;Users with IAM Feature&lt;/em&gt;.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0yeobjr8as6naykpbq73.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0yeobjr8as6naykpbq73.png" alt="AWS management console" width="800" height="594"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt; &lt;br&gt;&lt;br&gt;
On the Users page, click on the &lt;em&gt;Add users&lt;/em&gt; button - you will then be shown a page where you will enter a username.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd0jmo6u3hmb7yyc72r1w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd0jmo6u3hmb7yyc72r1w.png" alt="AWS Management Console users page" width="800" height="220"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt; &lt;br&gt;&lt;br&gt;
After entering a user name, click on &lt;em&gt;Next&lt;/em&gt; to set the permission for the user you are creating. Select the "&lt;em&gt;Attach policies directly"&lt;/em&gt; option and check the "&lt;em&gt;AdministratorAccess"&lt;/em&gt; policy.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftfr3gtm5f35bf9se998r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftfr3gtm5f35bf9se998r.png" alt="AWS Management Console attach policy" width="800" height="459"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt; &lt;br&gt;&lt;br&gt;
At the bottom of the page, click on "&lt;em&gt;Next&lt;/em&gt;" to review your settings and finally, click on the "&lt;em&gt;Create User"&lt;/em&gt; button.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fut065jctkj2cgkebge1y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fut065jctkj2cgkebge1y.png" alt="AWS Management Console create user page" width="800" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt; &lt;br&gt;&lt;br&gt;
Click the newly created username and then select the "&lt;em&gt;Security Credentials&lt;/em&gt;" tab. Locate the "&lt;em&gt;Access keys"&lt;/em&gt; section on the same page and click on the "&lt;em&gt;Create access key"&lt;/em&gt; button&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpb6wv3p37srswgrs7gbq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpb6wv3p37srswgrs7gbq.png" alt="AWS Management Console security credentials" width="800" height="198"&gt;&lt;/a&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa1dpwhghc5pkd7128glc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa1dpwhghc5pkd7128glc.png" alt="AWS Management Console create access keys" width="800" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt; &lt;br&gt;&lt;br&gt;
To create your access keys, select the "&lt;em&gt;Local code"&lt;/em&gt; option and agree by checking the proceed checkbox then click "&lt;em&gt;Next&lt;/em&gt;".&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fub3rnf6dsylqcjjioetc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fub3rnf6dsylqcjjioetc.png" alt="AWS Management Console Access Keys" width="800" height="490"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt; &lt;br&gt;&lt;br&gt;
Congratulations, your access keys have been generated. Copy these keys to a safe place because you will be using them in the next section.&lt;/p&gt;

&lt;p&gt;   &lt;/p&gt;
&lt;h2&gt;
  
  
  Step 2: AWS CLI
&lt;/h2&gt;

&lt;p&gt;CLI is an acronym for &lt;code&gt;Command Line Interface&lt;/code&gt; and is typically a lightweight and text-based application that runs via the command line, where it draws its name from. You interact with a CLI using commands as specified by the developers of the CLI. One of the features of AWS CLI is creating a connection between your local development environment and your AWS account such that you can issue commands via the CLI that will be received and responded to by the corresponding AWS service through your AWS account. This typically means you don't need to visit the management console to perform these actions.&lt;/p&gt;

&lt;p&gt;You can install the AWS CLI by following the prompts for your specific OS below.&lt;br&gt;
   &lt;/p&gt;
&lt;h3&gt;
  
  
  Linux
&lt;/h3&gt;

&lt;p&gt;Download the package as a compressed file - currently comes as a &lt;code&gt;.zip&lt;/code&gt; extension and unpack the contents to an &lt;code&gt;aws&lt;/code&gt; directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"awscliv2.zip"&lt;/span&gt; unzip awscliv2.zip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For users with ARM-based OS, you have to substitute the curl request above, for the one below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"awscliv2.zip"&lt;/span&gt;
unzip awscliv2.zip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install by running the install executable file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; ./aws/install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;   &lt;/p&gt;

&lt;h3&gt;
  
  
  macOS
&lt;/h3&gt;

&lt;p&gt;Download and install using &lt;code&gt;curl&lt;/code&gt; command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"https://awscli.amazonaws.com/AWSCLIV2.pkg"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"AWSCLIV2.pkg"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install using the command below - this installs the AWS CLI package to your root directory&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;installer &lt;span class="nt"&gt;-pkg&lt;/span&gt; ./AWSCLIV2.pkg &lt;span class="nt"&gt;-target&lt;/span&gt; /
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Windows
&lt;/h3&gt;

&lt;p&gt;Download and execute using the command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; msiexec.exe /i https://awscli.amazonaws.com/AWSCLIV2.msi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;   &lt;/p&gt;

&lt;h3&gt;
  
  
  AWS CLI Version
&lt;/h3&gt;

&lt;p&gt;After the installation has been completed, you can verify that it was done correctly by running the command &lt;code&gt;aws --version&lt;/code&gt;. This will display an output of the current AWS CLI and its dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxrfvypaleu3xeymwella.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxrfvypaleu3xeymwella.png" alt="AWS CLI version" width="800" height="191"&gt;&lt;/a&gt;&lt;br&gt;
   &lt;/p&gt;
&lt;h3&gt;
  
  
  Configure AWS CLI with Access Keys
&lt;/h3&gt;

&lt;p&gt;You will now configure your AWS CLI with the access keys in Step 1. To do this you have to run the command &lt;code&gt;aws configure --profile serverless-apps&lt;/code&gt; Place the respective keys when prompted by the CLI. You can allow the default entry for Default region name and format.&lt;/p&gt;

&lt;p&gt;   &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You should never share your access keys. You can quickly delete access keys that you think have been compromised in the Access Keys section and create a new one&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;   &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5i2xzu8hyj2n8i0dmn2w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5i2xzu8hyj2n8i0dmn2w.png" alt="AWS CLI profile configuration" width="800" height="280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;   &lt;/p&gt;
&lt;h2&gt;
  
  
  Step 3: Node.js
&lt;/h2&gt;

&lt;p&gt;Node.js is a runtime environment for executing JavaScript and TypeScript languages. You will be writing your applications in JavaScript or TypeScript therefore you have to download and install Node.js runtime. To download Node.js, I recommend downloading via NVM.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;
&lt;h3&gt;
  
  
  NVM
&lt;/h3&gt;

&lt;p&gt;NVM is short for Node Version Manager. It is a tool that manages different versions of the Node.js runtime. With NVM, you can switch between versions of Node.js when necessary. For example, if Project A requires Node.js 14 and Project B requires Node.js 16, with NVM, you simply need to issue the command &lt;code&gt;nvm use &amp;lt;version&amp;gt;&lt;/code&gt; to switch between Node.js versions for different projects seamlessly. See the commands below to download NVM.&lt;br&gt;
   &lt;/p&gt;
&lt;h3&gt;
  
  
  Installing NVM
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-o-&lt;/span&gt; https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;OR&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wget &lt;span class="nt"&gt;-qO-&lt;/span&gt; https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When either of the commands above is run via the Command Line, it downloads and runs a script that clones the nvm repository to a &lt;code&gt;.nvm&lt;/code&gt; folder and thereafter attempts to add the line of code below to one of the profile files: [&lt;code&gt;~/.bash_profile&lt;/code&gt;, &lt;code&gt;~/.zshrc&lt;/code&gt;, &lt;code&gt;~/.profile&lt;/code&gt;, or &lt;code&gt;~/.bashrc&lt;/code&gt;]&lt;br&gt;
   &lt;/p&gt;
&lt;h3&gt;
  
  
  Profile file
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;NVM_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;XDG_CONFIG_HOME&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;printf&lt;/span&gt; %s &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/.nvm"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;printf&lt;/span&gt; %s &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;XDG_CONFIG_HOME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/nvm"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NVM_DIR&lt;/span&gt;&lt;span class="s2"&gt;/nvm.sh"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NVM_DIR&lt;/span&gt;&lt;span class="s2"&gt;/nvm.sh"&lt;/span&gt; &lt;span class="c"&gt;# This loads nvm&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;After that is done and you have verified that the file was updated, you should close the Command Line and launch it. This is a way to get NVM to startup with its config. To verify that it was successfully done, you should enter the command &lt;code&gt;nvm --version&lt;/code&gt; which will return the current version of NVM installed. I have included a snapshot of this command and the output. Ensure you have the same version number or higher.&lt;/p&gt;

&lt;p&gt;   &lt;/p&gt;
&lt;h3&gt;
  
  
  NVM Version
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nvm &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhgrvg9dq5s4tgj8x9pud.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhgrvg9dq5s4tgj8x9pud.png" alt="nvm version command and output" width="800" height="279"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you face any issues with this installation, please refer to &lt;a href="https://github.com/nvm-sh/nvm#installing-and-updating" rel="noopener noreferrer"&gt;NVM README&lt;/a&gt; on Github.&lt;/p&gt;

&lt;p&gt;   &lt;/p&gt;
&lt;h3&gt;
  
  
  Installing Node.js
&lt;/h3&gt;

&lt;p&gt;After you're done installing NVM, you can then install Node.js via NPM. You can do so using the command below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nvm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--lts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this command, the NVM that's currently installed goes to work by downloading and installing the latest Node.js LTS (Long Term Support) version to your computer. After this is done, you can as well, run a command to ensure you have Node.js installed. This is done via the command &lt;code&gt;node --version&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;   &lt;/p&gt;

&lt;h3&gt;
  
  
  Node.js Version
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After issuing this command you should get an output that tells you the current version of Node.js installed on your computer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyegfshcp0pv0inhk98p4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyegfshcp0pv0inhk98p4.png" alt="node version command and output" width="800" height="279"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;   &lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Serverless Framework
&lt;/h2&gt;

&lt;p&gt;Serverless Framework helps you build, deploy and manage Lambda functions easily which in turn makes up your serverless application. To get started building a serverless application, you need to install this framework which is easily done by running &lt;code&gt;npm install -g serverless&lt;/code&gt;.&lt;br&gt;
   &lt;/p&gt;
&lt;h3&gt;
  
  
  Installing Serverless Framework
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install -g serverless
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;To verify you have correctly installed the Serverless Framework, you should be able to successfully run the &lt;code&gt;serverless --version&lt;/code&gt; or &lt;code&gt;sls -v&lt;/code&gt; command. The output you get should be similar to the snapshot below on successful installation.&lt;br&gt;
 &lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;serverless &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
   &lt;/p&gt;

&lt;h3&gt;
  
  
  Serverless Version
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frc2h6hcw1n0lel4qlt94.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frc2h6hcw1n0lel4qlt94.png" alt="serverless framework version output" width="800" height="370"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With this, you have successfully installed the Serverless Framework. You can now start creating serverless applications using the Serverless Framework.&lt;br&gt;
   &lt;/p&gt;

&lt;h2&gt;
  
  
  Code Editor
&lt;/h2&gt;

&lt;p&gt;In developing serverless applications, you will be writing code. To aid in this writing, you need a code editor. A code editor is simply a text editor that's made specifically for authoring code. Many code editors support working with the Serverless Framework. Below is a list of some of the very popular ones you can choose from.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://code.visualstudio.com/download" rel="noopener noreferrer"&gt;VS Code&lt;/a&gt; - Free&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.sublimetext.com/3" rel="noopener noreferrer"&gt;Sublime Text&lt;/a&gt; - Free/Paid&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.jetbrains.com/webstorm/download/" rel="noopener noreferrer"&gt;Jetbrains Webstorm&lt;/a&gt; - Paid&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.jetbrains.com/fleet/" rel="noopener noreferrer"&gt;Jetbrains Fleet&lt;/a&gt; - Free&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://notepad-plus-plus.org/downloads/" rel="noopener noreferrer"&gt;Notepad++&lt;/a&gt; - Free&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://brackets.io" rel="noopener noreferrer"&gt;Bracket&lt;/a&gt; - Free&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Select the one you are most comfortable with, paid or free and get cracking!&lt;br&gt;
   &lt;/p&gt;

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

&lt;p&gt;Congratulations! on getting to the end of this guide. Hopefully, you got similar prompts when verifying that you correctly installed any of the tools. There may be version number differences but that's perfect; it's proof you have a better version than I had when writing this article. You are now ready to get busy, developing those great serverless apps.&lt;/p&gt;

&lt;p&gt;Happy serverless coding!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>cloud</category>
      <category>serverlessframework</category>
    </item>
    <item>
      <title>Here's Why You Need a Mentor</title>
      <dc:creator>Charles Allison</dc:creator>
      <pubDate>Wed, 04 Jan 2023 13:00:13 +0000</pubDate>
      <link>https://dev.to/aws-builders/heres-why-you-need-a-mentor-5hmp</link>
      <guid>https://dev.to/aws-builders/heres-why-you-need-a-mentor-5hmp</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff6i2za7cddsrr31zdzw9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff6i2za7cddsrr31zdzw9.png" alt="why-you-need-a-mentor-image" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This blog was first published in 2019, this is an updated version.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Self-taught Ninjas
&lt;/h2&gt;

&lt;p&gt;Learning new software engineering concepts has never been easier. Someone new to Software engineering or any other skill can pick a course and in a few hours gain a good understanding of the concept. This has promoted lots of self-taught developers and indeed is a good thing as it shows a high level of commitment and self-motivation.&lt;/p&gt;

&lt;p&gt;We all have learned a thing or two by ourselves and deserve applause 👏 for the commitment during the learning period and we must help others develop such attitude and tenacity towards learning because it is needed at different points in our careers.&lt;/p&gt;

&lt;p&gt;However, as software engineers looking back at our careers, one would agree that having a mentor at some point would have been amazing. The learning or experience would have been super smooth if we had someone to guide us, share lessons learned, effectively make learning faster, someone who can help unblock us towards a goal. This is similar to a scenario where a junior engineer asks a senior a question and the response from the senior engineer is worth more (given the context) than searching online. While the junior could still motivate himself through this phase, it is essentially smarter to consult a senior who has been &lt;em&gt;through the same phase&lt;/em&gt; to guide him accordingly.&lt;/p&gt;

&lt;p&gt;Much learning is involved in the Tech industry and knowledge gained lasts as long as there are no updates which is barely the case🌚. Hence quick and effective learning becomes a necessary skill to have and one of the ways of achieving that is by having a mentor.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Want to get great at something? Get a coach - ATUL GAWANDE&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I opted for professional coaching/mentoring some years back and it fundamentally changed my outlook on software engineering positively. Sometimes we feel some questions are irrelevant or mundane but a good mentor never misses a chance to explain or give guidance where it seems necessary. Given what I know now, I only wish I had it a lot earlier in my career; hence the reason for this blog post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits of Having a Mentor
&lt;/h2&gt;

&lt;p&gt;Some of the benefits of having a mentor include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Learnings are Faster&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Focus is Improved&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Hidden ideas become Obvious&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Improve Your Skill&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;General Guidance&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  💨Learnings are Faster
&lt;/h3&gt;

&lt;p&gt;A mentor with experience in an area provides an opportunity to rely on their wealth of knowledge. Consider learning a new framework all by yourself for the first time. Then compare that to learning under someone who is already an expert in the given framework. You will agree the latter will be faster and straight to the point. Learning anything Tech related can get overwhelming. However, with a mentor, you can gain insight worth several years of experience in a relatively short time than would have been possible. In this case, the mentor knows the various information you need per context and can bring it all together in one simple yet profound instruction or comment.&lt;/p&gt;

&lt;h3&gt;
  
  
  🎯Focus is Improved
&lt;/h3&gt;

&lt;p&gt;We would agree that there are lots of information sources: articles, blogs, online courses, etc. that are available in the tech industry. The irony about this is; it makes it a bit difficult to pick one that solves your exact need. This could cause derailing from the focal point of your study. With a help of a mentor, you can pick the exact resource that helps you learn better. This works like a 2-edged knife; the first is that the mentor is well-aware of the resource and can guide you through the seemingly tough areas and the second is that you go all in with a razor-sharp focus, clearing out the ignorance.&lt;/p&gt;

&lt;h3&gt;
  
  
  💡Hidden Concepts Become Obvious
&lt;/h3&gt;

&lt;p&gt;When studying a concept for the first time, there is a tendency to ignore some parts, especially one that does not readily appeal to your understanding immediately or seems to not be so useful in the long run. When this happens, there is the likelihood of missing the lessons to be learned especially when they could be vital much later. A mentor having gone through the same subject can call your attention to that and cause you to spend some more time to gain that necessary understanding. This helps you realize the importance of the concept in your study and how it impacts your overall understanding. This also applies to important ideas that are not readily seen. Having a mentor can help you avoid such mistakes and greatly improve your career.&lt;/p&gt;

&lt;h3&gt;
  
  
  🎉 Improve Your Skill
&lt;/h3&gt;

&lt;p&gt;A mentor can notice a missing skill in a mentee and thereby help the mentee to improve in that area. For example, a back-end developer without a practical knowledge of Docker or a Cloud Infrastructure engineer that provisions infrastructure via the management console only, repeatedly without the use of IaC tools like Terraform. Whatever the reason may be for the lack of skills, a mentor can call the attention of the mentee and this can cause improvement in that area.&lt;/p&gt;

&lt;h3&gt;
  
  
  General Guidance
&lt;/h3&gt;

&lt;p&gt;Beyond technical guidance, a mentor can help in several other areas of your career; if you should take on a specific role, if you need to develop your soft skills - this is a very important skill to gain. If you need to be the change in a company or plan an exit. Whatever the reason, we would agree that getting help is better than spending all the time or being stuck.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;This is by no means an exhaustive discussion of why you need a mentor. Please share your reason, if you will, why having a mentor can boost your career or how having a mentor has boosted your career.&lt;/p&gt;

</description>
      <category>mentoring</category>
      <category>careergrowth</category>
    </item>
    <item>
      <title>My AWSome journey to the Cloud: the Cloud Resume Challenge</title>
      <dc:creator>Charles Allison</dc:creator>
      <pubDate>Fri, 31 Jul 2020 20:38:31 +0000</pubDate>
      <link>https://dev.to/charlallison/my-awsome-journey-to-the-cloud-the-cloud-resume-challenge-3n8a</link>
      <guid>https://dev.to/charlallison/my-awsome-journey-to-the-cloud-the-cloud-resume-challenge-3n8a</guid>
      <description>&lt;p&gt;Before the lock-down, I led a team of software developers in a project to be deployed in a private cloud. I had always handed over a project after I was done writing code because I have not been part of an IT team, so I really did not bother about deployment. That took a different turn in this project because not only was I responsible for code review, implementation strategy and software architecture, but for deployment and infrastructure (to some extent). I had successfully led the project to testing phase when the lockdowns came.&lt;/p&gt;

&lt;p&gt;Well, I decided to solidify my knowledge of Kubernetes and to get on with one of the cloud providers; AWS it was and &lt;a href="http://acloud.guru"&gt;A Cloud Guru&lt;/a&gt; was my choice cloud trainer. As I learnt more about AWS cloud I became fascinated with how easy it was to put services together and get your app running in a short period of time given I had just worked on a private cloud project. I thought that was all the goodness the cloud could offer not until I met with &lt;a href="https://en.wikipedia.org/wiki/Serverless_computing"&gt;serverless&lt;/a&gt;. With this new knowledge of serverless and AWS Lambdas, it was a fresh love for my career as a software developer.&lt;/p&gt;

&lt;p&gt;I kept learning and decided to write AWS Cloud Practitioner exam in July. I was studying for my exams but just couldn't let go of the serverless concepts. I took the &lt;a href="https://linuxacademy.com/course/serverless-concepts/"&gt;Serverless Concepts course&lt;/a&gt; on Linux Academy and several other serverless introductory courses on A Cloud Guru. I just couldn't get enough of serverless - I even nicknamed myself, &lt;em&gt;Serverless Junkie&lt;/em&gt; - and with the goal of becoming a serverless Hero someday. I also connected with some AWS serverless Heroes.&lt;/p&gt;

&lt;p&gt;I saw a post on LinkedIn that talked about one &lt;a href="https://cloudresumechallenge.dev/instructions/"&gt;cloud resume challenge&lt;/a&gt; and its deadline was for July 31, 2020. After reading through the requirements, I decided for it. And, yes, I had to pass my earlier scheduled CLP exam - that was the first requirement.&lt;/p&gt;

&lt;p&gt;It's a long read already so won't go into every aspect of the challenge but I won't forget my late nights with &lt;a href="http://docs.getmoto.org/en/latest/"&gt;moto&lt;/a&gt; - the very beautiful Python mocking framework for AWS infrastructure and the one and only AWS &lt;a href="https://aws.amazon.com/serverless/sam/"&gt;SAM (Serverless Application Model)&lt;/a&gt; - sweet nights indeed. These tools are great because they make the work really easy...erm, after you understand how to use them. Yeah, I read lots and lots of blogs, documentation, github samples and in the end, I successfully completed the Cloud Resume Challenge.&lt;/p&gt;

&lt;p&gt;I'm so thankful to &lt;a href="https://dev.to/forrestbrazeal"&gt;Forrest Brazeal&lt;/a&gt; for putting up the challenge and causing many to learn and improve themselves. I am really grateful. Hopefully, I can work hard enough to becoming an AWS Serverless Hero. Until next time, it is bye from me and get on learning severless.&lt;/p&gt;

&lt;p&gt;Here's the &lt;a href="http://www.charlesallison.info"&gt;finished project&lt;/a&gt; for the challenge&lt;/p&gt;

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