Connecting AWS Lambda Function .NET Container to Amazon DynamoDB
Tags: aws, docker, serverless, dotnet
Published: 2022-07-17 15:07:29

Hello, everyone! This week, I was learning AWS Lambda Function, especially using .NET Container. I will continue my post and extends more use cases. I will connect the previous Lambda Function to Amazon DynamoDB.

## Start Project

Please continue the project from [this commit](https://github.com/bervProject/lambda-sharp/tree/5b600608bb70a55426dab29c8d26443a4d40f634). In short, you may use [this project](https://github.com/bervProject/lambda-sharp/tree/cabb9263592c531ad0bbc5bc41004e97b295886c) directly to explore. If you want to check what's the changes, please visit [here](https://github.com/bervProject/lambda-sharp/commit/cabb9263592c531ad0bbc5bc41004e97b295886c).

## Update our Lambda Functions Code

### Install Dependencies to Existing Project

You will need install Amazon DynamoDB dependencies before you use it. Please use this command: `dotnet add src/SimpleAPI/ package AWSSDK.DynamoDBv2 --version 3.7.3.57`. I use version `3.7.3.57`.

### Add Model

Please add this model file to `src/SimpleAPI/Models/Note.cs`. We will use a simple model in this case just for proof of the concept.

```csharp
namespace SimpleAPI.Models;


using Amazon.DynamoDBv2.DataModel;

[DynamoDBTable("Notes")]
public class Note
{
    [DynamoDBHashKey]
    public Guid Id {get; set;}
    public String Message {get; set;}
} 
```

### Add Notes Controller

We will need add new controller to handle the API.

```csharp
using Microsoft.AspNetCore.Mvc;
using Amazon.DynamoDBv2.DataModel;
using SimpleAPI.Models;
using Amazon.DynamoDBv2;

namespace SimpleAPI.Controllers;

[Route("api/[controller]")]
public class NotesController : ControllerBase
{
    private readonly AmazonDynamoDBClient _dynamoDBClient;
    private readonly DynamoDBContext _dbContext;
    public NotesController()
    {
        AmazonDynamoDBConfig clientConfig = new AmazonDynamoDBConfig();
        _dynamoDBClient = new AmazonDynamoDBClient(clientConfig);
        _dbContext = new DynamoDBContext(_dynamoDBClient);
    }

    [HttpGet]
    public async Task<IEnumerable<Note>> Get()
    {
        var notes = _dbContext.ScanAsync<Note>(new List<ScanCondition>());
        var results = await notes.GetRemainingAsync();
        return results;
    }

    [HttpGet("{id}")]
    public async Task<Note?> Get(Guid id)
    {
        var notes = _dbContext.QueryAsync<Note>(id);
        var results = await notes.GetRemainingAsync();
        return results.FirstOrDefault();
    }

    [HttpPost]
    public async Task<Note> Post([FromBody] Note value)
    {
        var batch = _dbContext.CreateBatchWrite<Note>();
        value.Id = Guid.NewGuid();
        batch.AddPutItem(value);
        await batch.ExecuteAsync();
        return value;
    }

    [HttpPut("{id}")]
    public async Task<IResult> Put(Guid id, [FromBody] Note value)
    {
        var notes = _dbContext.QueryAsync<Note>(id);
        var results = await notes.GetRemainingAsync();
        var first = results.FirstOrDefault();
        if (first == null)
        {
            return Results.NotFound();
        }
        first.Message = value.Message;
        var batch = _dbContext.CreateBatchWrite<Note>();
        batch.AddPutItem(first);
        await batch.ExecuteAsync();
        return Results.Ok(first);
    }

    [HttpDelete("{id}")]
    public async Task<IResult> Delete(Guid id)
    {
        var notes = _dbContext.QueryAsync<Note>(id);
        var results = await notes.GetRemainingAsync();
        var first = results.FirstOrDefault(); if (first == null)
        {
            return Results.NotFound();
        }
        var batch = _dbContext.CreateBatchWrite<Note>();
        batch.AddDeleteItem(first);
        await batch.ExecuteAsync();
        return Results.Ok();
    }
} 
```

## Update our Terraform Code

* You will need to add this permission to the execution role of the lambda:

```terraform
inline_policy {
    name = "lambda-custom"
    policy = jsonencode({
      Version = "2012-10-17"
      Statement = [{
        Effect = "Allow"
        Action = [
          "logs:CreateLogGroup",
          "logs:CreateLogStream",
          "logs:PutLogEvents"
        ]
        Resource = "*"
        }, {
        Effect = "Allow",
        Action = [
          "dynamodb:DescribeTable",
          "dynamodb:GetRecords",
          "dynamodb:GetShardIterator",
          "dynamodb:ListTables",
          "dynamodb:Scan",
          "dynamodb:Query",
          "dynamodb:BatchWriteItem",
          "dynamodb:PutItem",
          "dynamodb:UpdateItem",
          "dynamodb:DeleteItem"
        ]
        Resource = ["*"]
    }] })
  }
```

* I also extend the timeout to make sure it's not terminated when I call the API.

```terraform
  tracing_config {
    mode = "Active"
  }

  timeout = 60
```

* Please make sure your `aws_lambda_function_url` already allows all methods.

```terraform
allow_methods = ["*"]
```

* Add Amazon DynamoDB resource.

```terraform
resource "aws_dynamodb_table" "lambda_container_demo_dev" {
  name           = "Notes"
  billing_mode   = "PROVISIONED"
  read_capacity  = 20
  write_capacity = 20
  hash_key       = "Id"

  attribute {
    name = "Id"
    type = "S"
  }

  attribute {
    name = "Message"
    type = "S"
  }

  global_secondary_index {
    name               = "NoteMessageIndex"
    hash_key           = "Message"
    write_capacity     = 10
    read_capacity      = 10
    projection_type    = "INCLUDE"
    non_key_attributes = ["Id"]
  }

  ttl {
    attribute_name = "TimeToExist"
    enabled        = true
  }

  tags = {
    Name        = "dynamodb-table-1"
    Environment = "dev"
  }
} 
```

## Done!

{% github  bervProject/lambda-sharp  %}

Time to deploy your code to Amazon ECR and run `terraform apply` so your resources are provisioned.

You may look at these screenshots for my testing the CRUD of Notes Functions.

* Get All Notes

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

* Create a Note

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

* Update a Note

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

* Get a Note by Id

![Get By Id](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rllvfyv70r6k4lfasoty.png)
 
* Delete

![Delete](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/81hq1qysfziqcwvggake.png)

## Thank you!

So, what do you think? It's quite simple, right? Let's share what's in your mind!

![GO GIF](https://media.giphy.com/media/dY1DoxjYYtvXjRelGG/giphy.gif)