AWS Serverless and the DynamoDB Single Table Design - Hands-on with CDK v2
Tags: aws, node, serverless, javascript
Published: 2021-12-30 00:25:12

Hi, I'm Adriano Sastre Vieira, software developer at Inatel Competence Center. 

In this article I'll talk about **AWS Serverless**, also called **managed services** (because you don't have to manage a server in order to deploy these services), with more focus on **DynamoDB** and the **Single Table Design** concept.

Not recommended, but [click here if you want to skip the theory and go strait to the hands on](#hands-on).

# Serverless!

DynamoDB is a AWS managed database service. When we talk about AWS managed service, we're also talking about Serverless.

**Serverless** is the practice of using managed services with event driven compute functions to avoid or minimize infrastructure management, configuration, operations, and idle capacity.

But it's not just about computing, there is a wide range of things an application architecture may need, e.g. Compute, Storage, Data, Monitoring, Queue, Notification ...

So, it is correct to say that **DynamoDB** is a serverless database, or more specifically, an AWS managed database service.

**Serverless** is a big paradigm shift, potentially even more impactful than the move to the **Cloud** before it!

Check the articles below if you want to go deeper into Serverless:

- [In defense of “Serverless” — the term](https://medium.com/serverless-transformation/in-defence-of-serverless-the-term-764514653ea7)
- [Serverless Best Practices](https://pauldjohnston.medium.com/serverless-best-practices-b3c97d551535)

# Microservices

Yet on the related concepts, we have the **Microservices**. 

The microservice architectural style is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. 

These services are built around business capabilities and independently deployable by fully automated deployment machinery. 

Many people are using Serverless applications to build a microservice architecture!

I believe this is the number one article for those who wants to go deeper on [Microservices](https://martinfowler.com/articles/microservices.html) - from Martin Fowler.

# Serverless and the AWS Cloud

Ok, some important things about AWS before we cut to the chase.

## AWS - Fundamentals (The 5 Pillars, new "mental models")

When starting working with AWS, it's normal to feel quite lost, as it has about 200 services, many of them overlapping others on their responsibilities, doing similar stuff, and the AWS extensive documentation does not seem to help beginners, it's difficult to FOCUS and use what is best for each case.

In addition, when we change from a monolithic architecture to micro services, it's a big shift, it's like changing from classical physics to quantum physics!

So, a very good start point is to understand the **AWS Fundamentals**, which talks about **the 5 pillars** that requires new **mental models**, and summarizes the AWS services and concepts for each one:

- **Operational excellence**: thinking about operations as automation (CloudFormation, CDK ...)
- **Security**: zero trust, the principle of least privilege (IAM, data encryption ...)
- **Reliability**: using fault isolation zones to limit blast radius (regions, quotas, throttling ...)
- **Performance Efficiency**: think of your services as cattle, not pets. (horizontal vs vertical scaling, auto scaling ...)
- **Cost Optimization**: OpEx (pay-as-you-go) instead of CapEx (one-time purchase)

![AWS Fundamentals](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cxzl3febx9mnua7v92ek.png)

So if you didn't yet, now it's a good opportunity to check the [AWS Fundamentals] (https://aws.amazon.com/getting-started/fundamentals-core-concepts/)!

## The AWS Managed Services

When we talk about AWS managed services, these are the main ones:

![AWS Serverless](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qtbz8so5ssvrhm9lit4r.png)

There are many pros on using managed services:

- **Cost reduction**: the initial cost tend to zero (free tier for most of them, after that pay as you use)
- **Security**: e.g. no need to install security patches on servers
- **NoOps**: e.g. no servers / storage to manage or to scale, no need for a infrastructure team
- **More scalable**
- **High performance**
- **Greener**: if you don't need, you're not using the server resources
- **Productivity**: developers focus on delivering business value

Some cool links to learn more about Serverless on AWS:

- [Serverless on AWS](https://aws.amazon.com/serverless/)
- [AWS Deep Dive Serverless](https://aws.amazon.com/getting-started/deep-dive-serverless/)
- [Serverless Land](https://serverlessland.com/)
- [AWS Free Tier](https://aws.amazon.com/free) - in order to check if a AWS service is free or how much it costs

## Minimum Stack: API Gateway + Lambda + DynamoDB

While this article focus is the DynamoDB, in order to talk about it in a more practical way, it's important to think in a minimum sample architecture where it fits.

The classical AWS example is: 

- An API (e.g. some CRUD) implemented via API Gateway;
- This API is accessed by a client (e.g, the Postman, or a mobile/web app);
- Lambda function(s) in order to manage this API and interface with the database
- And the DynamoDB table in order to store data.

![Minimum Stack](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8ll5gg9eb6u9xzcxjq8r.png)

I'll briefly talk about API Gateway and Lambda before going to DynamoDB. Pay attention to the **_italic_** words, they are related to the **Serverless** concepts.

### API Gateway

https://aws.amazon.com/api-gateway

Fully **_managed service_** that makes it easy for developers to create, publish, maintain, monitor, and secure APIs at **_any scale_**.

Handles all the tasks involved in accepting and processing up to hundreds of thousands of concurrent API calls, including traffic management, CORS support, authorization and access control, throttling, monitoring, and API version management.

### Lambda

https://aws.amazon.com/lambda

AWS Lambda is an **_event-driven_**, **_serverless computing platform_** provided by Amazon as a part of Amazon Web Services. 

Runs code in response to events and **_automatically manages the computing resources_** required by that code.

Triggered by events (e.g. HTTP Calls via API Gateway, S3 new objects on a bucket, new SQS in a queue, new item in a DynamoDB table ...)

## DynamoDB

https://aws.amazon.com/dynamodb/

Now we're talking. DynamoDB definitions and main characteristics from AWS:

- _**Fast**_ and **_flexible_** NoSQL database service for **_any scale_**. Key-value and document database that delivers _**single-digit millisecond performance**_ at _**any scale**_.

- _**Fully managed**_, multi-region, multi-active, durable database with built-in security, backup and restore, and in-memory caching for **_internet-scale_** applications.

- _**Free-tier forever**_ applied, up to 25GB storage, 25 read/write provisioned units (about 200M requests/month). After that: cost by storage and requests.

For those who are curious about its roots, read ["The Dynamo Paper"](https://www.dynamodbguide.com/the-dynamo-paper). Summary: it was created by Amazon (2004-2007), public released by AWS in 2012.

### DynamoDB as part of a Serverless Architecture

When we first saw these _propaganda_, it seems all good news alright? 

But wait, it is not that simple.

First, adopting DynamoDB in place of a relational database is just part of moving to a Serverless Architecture, although a very important one.

The idea of using NoSQL for any and all core business requirements is quite new, and this is because the whole system now works at a scale and event-driven nature before inexistent.

_**Bad designed DynamoDB tables has often the major impact on a serverless architectured system, both on performance and costs!**_

Don't get me wrong, DynamoDB is really great when the data is properly modeled! There are lots of benefits on using it, as easily integrating it with other managed services like lambdas, really high performance and scalability, and very low initial cost.

And yes, it is possible to use it correctly on the majority of the systems (remember: Amazon uses it on its shopping cart, so why can't we?); but in summary: **DynamoDB is complex, doing it well even more so!** 

### DynamoDB NoSQL design vs RDBMS

**NoSQL** design requires a different mindset than **RDBMS** design.

With RDBMS, you can go ahead and create a **normalized** data model without thinking about **access patterns**.

By contrast, you shouldn't start designing the schema for DynamoDB until you know the questions that need to be answered. **Understanding the business problems and the application use cases up front is essential!**.

To clarify, it follows some common access patterns examples:

- Get a user's profile data
- List the user's orders
- Get an order and its items
- List the user's orders by status

Of course, on a real system there are lots more.

### DynamoDB design considerations

This AWS documentation "[NoSQL Design](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-general-nosql-design.html#bp-general-nosql-design-concepts)" goes deeper on the concepts I've summarized in this image:

![Design considerations](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j6m98ww7mu014bjysmjk.png)

After you identify specific query requirements, you can organize data according to general principles that govern performance:

- **Keep related data together**.  Keeping related data in close proximity has a major impact on cost and performance. Instead of distributing related data items across multiple tables, you should keep related items in your NoSQL system as close together as possible. 

- **Use sort order**.  Related items can be grouped together and queried efficiently if their key design causes them to sort together. This is an important NoSQL design strategy.

- **Distribute queries**.  It is also important that a high volume of queries not be focused on one part of the database, where they can exceed I/O capacity. Instead, you should design data keys to distribute traffic evenly across partitions as much as possible, avoiding "hot spots".

- **Using indexes**.  By creating specific global secondary indexes, you can enable different queries than your main table can support, and that are still fast and relatively inexpensive.

**These general principles translate into some common design patterns that you can use to model data efficiently in DynamoDB.**

This presentation [Advanced Design Patterns for Amazon DynamoDB](https://d1.awsstatic.com/events/reinvent/2019/REPEAT1_Advanced_design_patterns_for_Amazon_DynamoDB_DAT334-R1.pdf) is great for better understanding it.

### DynamoDB - table capacity

Per DynamoDB table, it is possible to configure the capacity as:

- **OnDemand**: automatic and "infinite" scaling;
- **Provisioned**: possible to define independent read and write unit capacities; also possible to configure auto-scaling rules, e.g. min/max scaling, how fast to scale.

It is very challenging to proper configure the DynamoDB table provisioned capacity, and it can imply on requests being throttled. Totally configurable, we can set different capacity values for reading and writing requests, and we can also configure read and write scaling: from how much percent of use the table starts reacting, what is the max and minimum capacity it can have, and how much time it waits till scaling up or down. All of these configuration can be done for the table and each of its indexes.

When correctly configured, a provisioned capacity leads to less costs than an on demand capacity. And certainly it is less complicated to configure and watch it for one **single table** than for many tables. In addition, when configured as on demand capacity, usually the DynamoDB takes more time to understand a high I/O volume of requests and scaling. During this time, we can have requests being throttled.

It is also possible to change between OnDemand and Provisioned once a day! This is very useful for scenarios where on a specific time range, it is not possible to predict the scaling at all, e.g. e-commerce system on Black Fridays.

### DynamoDB PK, SK, Attributes

As key-value and document database, each DynamoDB table item may have different attributes! 

But it is mandatory to define the primary key. In a nutshell:

**Primary Key**:
**PK** = Partition Key (aka hash key) (mandatory)
**SK** = Sort Key (aka range key) (optional)

**Attributes**: Binary, Number or String
Can be grouped in a JSON-like structure

This image exemplifies the DynamoDB structure:

![DynamoDB structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8dyk07fad0fbfjn4e494.png)

And this shows an example of a populated DynamoDB table:

![Populated Table](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/po9ujdd9hn15ds1oosud.png)

### DynamoDB - How to query data

Querying data is normally the most important consideration when designing DynamoDB schema, as usually there are much more reading than writing operations.

There are basically 3 ways of querying DynamoDB data:

- **Get** = one specific item, by PK or PK/SK
- **Query** = several items, by PK/SK or indexed attributes
- **Scan** = several items, by any table attribute

This is very important: the **get** and **query** operations are much faster comparing to the **scan** operation, which has a poor performance and a high cost.

So it is crucial to model a DynamoDB table in a way that it is possible to query all necessary data, for every access pattern, using get or query operations, and avoiding scan operations.

### DynamoDB indexes

We may define 2 kinds of indexes on DynamoDB:

- GSI = Global Secondary Index = more common, applied to all table items
- LSI = Local Secondary Index = applied to a particular table partition (PK)

It follows the main pros and cons of using DynamoDB indexes:

- Pro: performance: with an index, it is possible to **query** (instead of **scan**) on attributes other than the PK/SK
- Con: behind the scenes, each GSI duplicates the table storage, along with its storage costs

There is also a limit of 20 GSI per table.

The following image provides more details on DynamoDB indexes:

![Indexes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lnkf9hwx5u47ho1y43o4.png)

### DynamoDB TTL (time to live)

Optionally, a **time to live** attribute can be defined on DynamoDB tables, and it is very useful on scenarios where items have to be deleted after certain time is reached.

Another interesting behavior, each table item may have a different time to live value, or no TTL value at all!

## DynamoDB - Single Table Design

Different from relational databases where we have to **normalize** the data, it is an AWS recommendation to maintain as few tables as possible when modeling with DynamoDB, as stated on [NoSQL Design for DynamoDB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-general-nosql-design.html) documentation.

When we normalize data, we make the data access very flexible, but it reduces the scalability, due to the high cost of the joins operations.

But DynamoDB was built for enormous, high-velocity use cases, such as the Amazon.com shopping cart. Rather than working to make joins scale better, DynamoDB sidesteps the problem by removing the ability to use joins at all!

Again, DynamoDB was build with web scale in mind. It can grow almost infinitely without degrading performance. In order to achieve this DynamoDB removed joins completely.

You have to model the data in such a way that you can read the data, ideally, in a single request by **denormalizing** the data.

The main reason for using a DynamoDB single table is to retrieve multiple, heterogenous item types using a single request.

The following links are great to understand more about the single table design concepts:

- [The What, Why, and When of Single-Table Design with DynamoDB](https://www.alexdebrie.com/posts/dynamodb-single-table/)
- [Comparing multi and single table approaches to designing a DynamoDB data model](https://serverlessfirst.com/dynamodb-modelling-single-vs-multi-table/)
- [Microservices with DynamoDB: should you use a single table or use one table per microservice?](https://cbannes.medium.com/microservices-with-dynamodb-should-you-use-a-single-table-or-use-one-table-per-microservice-25f54cf610d9)

### Single Table Design - Pros and Cons

Summary of the advantages and disadvantages of applying the single table design:

![pros and cons](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xaynq0coiix67qy42bsx.png)

## DynamoDB - When and how (not) to use

I summarize in the following images when and how to use and not to use DynamoDB:

![how to use](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/60acpgq407ynp4iripts.png)

More about [the true microservice](https://www.alexdebrie.com/posts/dynamodb-patterns-serverless/#the-true-microservice) here. In a nutshell, it favors using a DynamoDB single table per microservice, but not per the whole system.

![how not to use](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fp09xftsvftoe3hphrd7.png)

Indeed the **Faux SQL** is a common mistake when starting using a NoSQL database as DynamoDB or MongoDB. More about the [Faux SQL](https://www.alexdebrie.com/posts/dynamodb-patterns-serverless/#faux-sql) here.

# DynamoDB - Hands on! <a name="hands-on"></a>

## From Relational to NoSQL

In order to get the hands dirty on the DynamoDB single table design, let's imagine a simple system where we have users and orders.

### RDBMS:

For this example, in the relational world we could have the following model:

![relational model](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p5jry0v006xlk1212dww.png)

### DynamoDB

When we model it with DynamoDB, the first and more important question is: **What are the access patterns for my system?** 
In other words, how the system will query the data in the database?

For this example we can think about the following access patterns:

- List all users
- Get user profile
- Add user
- Edit user
- Delete User
- List all users orders
- List users orders by status
- List users order items
- Add user order
- Edit user order
- Delete user order

With that in mind, first we can model the user and order documents with the information we would need for all access patterns.

User example:

```json
{
  "username": "adrianosastre",
  "fullName": "Adriano Sastre Vieira",
  "email": "adrianosastre@gmail.com",
  "addresses": [
    {
      "home": "My first address"
    },
    {
      "home": "My second address"
    }
  ]
}
```

Order example:

```json
{
  "id": "1e499793-1084-4c34-9d4c-bbb9df88d2de",
  "username": "adrianosastre",
  "fullName": "Adriano Sastre Vieira",
  "address": 
  {
    "home": "My first address"
  },
  "items": [
    {
      "name": "iPhone",
      "value": 999.90,
      "quantity": 1
    }
  ]
}
```

Those "schemas" result in the following sample data for a DynamoDB single table:

![single table](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1i18u3q8zz7xm7kvm7qz.png)

Important things to note:

- We have in the same table, different items for users and orders information. They even have different attributes. But the important is that they have different patterns in their PK and SK values, and these patterns are used in order to query data for all access patterns.

- The "orderStatus" attribute, used only on order items, needs to be used on the "List users orders by status" access pattern. As it is not the PK or SK, we need to create an **index** in order to query data, not scan. More on that later.

- We "denormalize" data, repeating user information inside order information. That's a common practice on DynamoDB and NoSQL modeling in general. 

Again, there are more detailed articles explaining how to go from a relational to a NoSQL database, if you wanna go further:

- [From relational DB to single DynamoDB table: a step-by-step exploration](https://www.trek10.com/blog/dynamodb-single-table-relational-modeling/)
- [How to switch from RDBMS to DynamoDB in 20 easy steps](https://www.jeremydaly.com/how-to-switch-from-rdbms-to-dynamodb-in-20-easy-steps/)

## IaC - the Infrastructure as Code

Before configuring the AWS services and coding the business logic, let's do it right!

Having the infrastructure as code is essential in order to code versioning (ex: git) all the AWS services configuration, instead of making it through the AWS Console (and totally losing control when something goes wrong!).

IaC is also critical to implement CI/CD pipelines.

### CloudFormation

https://aws.amazon.com/cloudformation/

When it comes to the AWS IaC, the most basic level is the CloudFormation. Basically, it consists on yaml or json templates that describes your resources and its dependencies so you can launch and configure them together as a stack. 

In other words, it is an automated process for resources creation. But it is still quite hard and error-prone to program IaC with CloudFormation.

### CDK - The Cloud Development Kit

https://docs.aws.amazon.com/cdk/api/v2/

[Released on 2019](https://www.youtube.com/watch?v=bz4jTx4v-l8), we now have the **AWS CDK**: the official AWS open source software development framework to define your cloud application resources using familiar programming languages. And now in the end of 2021, we have CDK v2 released.

With CDK, you can model the infrastructure resources in high-level languages like Java, Typescript, Python or C#.

Behind the scenes, the CDK will generate the CloudFormation templates and deploy them as AWS CloudFormation Stacks.

It is much safer, easier and more fun to program AWS IaC code with **CDK** that with CloudFormation! Also, when compared to other IaC frameworks (e.g. serverless,  terraform, etc), it has the following advantages:
 
- Implemented and maintained by AWS 
- Easy integration with AWS services
- More secure (e.g. roles automatically generated for services, from read/write permissions)

## Step by step

Let's get the hands dirty!

### Prerequisites

If not yet, create / install / configure the following:

**1 - Create an AWS Account** if you don't have yet.

**2 - After logged into the AWS Console, add an IAM user** with "Access type: Programatic access" and for the sake of this example, add the "Administrator Access" policy to this user. Copy its "Access key ID" and "Secret access key", we'll use them soon.

**3 - Download and install VS Code**: https://code.visualstudio.com/

**4 - Install Node and NPM**: https://nodejs.org/en/download/

After installing, check their version. At the moment of this writing I have node v16.13.1 and npm 8.3.0
```
node -v
npm -v
``` 
**5 - Download and install AWS cli**: https://aws.amazon.com/cli/

After installing, check its version and configure it to your AWS account IAM user created on step 2:
```
aws --version
aws configure
``` 

**6 - Install AWS CDK**: 

Install CDK via NPM and check its version. At the moment I have 2.3.0 (build beaa5b2)
```
npm install -g aws-cdk
cdk --version
``` 

**7 - Download and install Docker**: https://www.docker.com/products/docker-desktop

Docker is only used in order to deploy the CDK project into AWS, we do not have containerized AWS services in this example.

**8 - Download and install Postman**: https://www.postman.com/ (used in order to test the APIs)

### Architecture

![architecture](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t8e4mihxdoy2uhjw1j20.png)

As per the above architecture, this project consists on:

- API Gateway, with the /users and /products RESTful resources
- Lambdas: functions for users and orders, they handle the APIs and DynamoDB data
- DynamoDB: one single table to store users and orders data

### Open project on VS Code

This project was implemented with **CDK v2** with **Typescript**, and is public available on github:
[github.com/adrianosastre/DynamoDB-CDK-Hands-On](https://github.com/adrianosastre/DynamoDB-CDK-Hands-On)

Please clone and open it on VS Code, and look at these important chunks of code:

**1 - Resources application stack**

The **lib/resources-application-stack.ts** file is **IaC** code, it creates the DynamoDB table and the users and orders Lambdas. 

Important things to note:

- The "singleTable" constant contains all DynamoDB table configuration: the PK, the SK, the GSI, the table capacity and scaling configuration on a few lines of code with CDK. In other words, modeling the data is complex, but implementing its infrastructure with CDK is simple.

- The "grantReadWrite" method that manages the lambda permissions on the DynamoDB table. Again, CDK takes care of services permissions in a very easy and efficient way.

- The lambdas were implemented as class readonly public attributes, so they can be accessed later on the API stack.

**2 - API stack**

The **lib/api-gateway-stack.ts** file, also an **IaC** code, creates the API Gateway with our users and orders resources. 

Note that it uses the lambdas exposed on the **lib/resources-application-stack.ts** file in order to integrate its resources with the lambdas.

**3 - The CDK main file**

The file under the bin directory, in this case the **bin/dynamoDB-CDK-Hands-On-Project.ts** file, is the main file in the CDK structure. 

It instantiates the stacks and care about its dependencies.

**4 - Lambdas code**

The **users.js** and **orders.js** files under **lambda** directory are not IaC code. 

Instead, they are the "core" of the system, containing the business logic code behind the lambdas, and are executed every time they are triggered.

### Deploying on AWS

_Note: AWS cli must be correctly configured as explained in the Prerequisites session._

Only once, it is necessary to execute the following command so the CDK will deploy into your AWS account the required resources for it to deploy projects:

```
cdk bootstrap
```

After that, you can deploy the project to your AWS account via the following command:

```
cdk deploy --all
```

Every time you change the IaC or lambda code, you can use this command to redeploy, and it does not impact the services usage!

After the project is deployed, you can check in your AWS account the following resources:

- [**CloudFormation**](https://console.aws.amazon.com/cloudformation): the stacks with the resources programmed via CDK were deployed here:

![cloudformation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p45fqn28ip2iov24o69q.png)

- [**API Gateway**](https://console.aws.amazon.com/apigateway/main/apis): the DynamoDB-CDK-Hands-On-API API is deployed and public available:

![api gateway](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4k4rvgkjclg5hf1mzvzy.png)

And the API expanded showing its resources:

![api gateway expanded](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hevimw31qqjj6yz8zwtl.png)

In addition, the Stages > prod > **Invoke URL** shows the base URL for this API, so you can use it, for example, with Postman:

![base url](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qxpk4c75kb3vt2ia7urd.png)

- [**Lambda**](https://console.aws.amazon.com/lambda): the functions to handle users and orders are deployed:

![lambdas](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b8r7tluc2wgxe0yvfnpc.png)

- [**DynamoDB**](https://console.aws.amazon.com/dynamodb): The DynamoDB-CDK-Hands-On-Single-Table table was also created and deployed:

![dynamodb](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jad3asec8fxtgysipxez.png)

Also, at this moment you can see that the table have no items, and you can also double-check its capacity configuration:

![dynamodb capacity](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g3jiz91zeywt0ph5d7uf.png)

### Testing with Postman

After deployed, it is possible to test all the project URLs with Postman. Take the following images as references and use Postman with your API base URL.

- Adding users:

![add user](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qirc5ppk4001jufwk9ri.png)

In the lambda/users.js code, the **put** method from the DDB client was used:

![add user lambda](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0d7mtdvkfyjysr9c0nqi.png)

- Listing all users:

![list all users](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hohlmzvjmmaspiusl8iu.png)

In the lambda/users.js code, the **query** method from the DDB client was used, and only querying the "USER#" value in the PK returned the results we want:

![list users lambda](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pxo9odc8j89457lfawt6.png)

- Getting a user profile data:

![get user profile](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9ofhj6e9bh97m6kk8xr2.png)

In the lambda/users.js code, the **get** method from the DDB client was used, in this case we need to provide values for the PK and SK to retrieve a specific item:

![get user profile lambda](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ws5c7t1n93znp62ai2z1.png)

- Editing a user:

![edit user](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fs7h5lrp1diav16t2r5t.png)

In the lambda/users.js code, the **update** method from the DDB client was used, and we provided values for the PK and SK to update a specific item:

![edit user lambda](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gin76tgtrujoq41zreyt.png)

- Deleting a user:

![delete user](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/avpah36hcn6vu0atghyx.png)

In the lambda/users.js code, the **delete** method from the DDB client was used, and again we provided values for the PK and SK to delete a specific item:

![delete user lambda](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1glmzzgctwsqtehyrz75.png)

- Adding an order for a user:

![add order](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6hrfe3bury6z5wur334w.png)

- Listing all orders for each user:

![list user orders 1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vifc21u1n9uh5omczmqk.png)

![list user orders 2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3puz4fuq1053b513hj3j.png)

In the lambda/orders.js code, the **query** method from the DDB client was used, and querying the "ORDER#${username}" value in the PK returned the results we want: _(note that we have one order partition per username)_

![list user orders lambda](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7olvdjmmi132kh38nyrf.png)

- Listing users orders by status:

![orders by status](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/khz8urbt8pcega1k6y8i.png)

In the lambda/orders.js code, the **query** method from the DDB client was used, and here we used the **index** value so we can search for attribute values **without using scan** in order to get the results we want:

![orders by status lambda](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xdqsdnqy1cep4xx6tpwk.png)

- Listing a user specific order items:

![order items](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ap388sg5lsrgs6qvh0rx.png)

- Editing user order:

![edit user order](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xiiua9ymqm48m9j7mov0.png)

- Deleting user order:

![delete order](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1a5c6odsdcu3rnznb3zr.png)

### AWS troubleshooting

Things does not always go right from the first time. 

In order to troubleshoot AWS services, e.g. the lambdas code, their logs results that can be double-checked on [AWS CloudWatch Log Groups](https://console.aws.amazon.com/cloudwatch/home#logsV2:log-groups). Just click on the respective log group:

![cloudwatch log group](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/djqw75125emqrgr6hm4g.png)

And after that, in the log stream messages:

![log streams](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ks7i8k48iborilvj3ga3.png)

Remember, logs are our best friends! As stated on this great old [**Optimal Logging**](https://testing.googleblog.com/2013/06/optimal-logging.html) article, _"With optimal logging, you can even eliminate the necessity for debuggers!"_

### Tracking performance

Last but not least, in order to track performance, this project lambdas also have **X-Ray** enabled so you can verify X-Ray traces in [**ServiceLens**](https://console.aws.amazon.com/cloudwatch/home#servicelens:traces).

It is so cool and useful to graphically see the services flow and how much time was spent in each service or function!

![x-ray 1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jdbamd0e8psfxf1gavlb.png)

![x-ray 2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vi5b16q9336qzt2c9ijs.png)

You can perform load tests with Postman, for example configuring it to send several user or order edit operations, and monitor the performance on AWS Console as per the above images.

# That's all folks!

I hope this article was helpful, and I'm available for any comments or questions here or via adrianosastre@gmail.com

Thanks and have a nice 2022 :)