Sunday, 8 March 2020

Building Serverless CRUD services in Go with DynamoDB - Part 2

In the previous post, we've created createHandler. In this post, we'll create listHandler.

Getting started

First, let's add the config under functions in serverless.yml

list:
    handler: bin/handlers/listHandler
    package:
        include:
        - ./bin/handlers/listHandler
    events:
        - http:
            path: iam
            method: get
            cors: true

Create a file listHandler.go under src/handlers

Similarly, we have the below structure.

package main

import (
    "context"
    "encoding/json"
    "fmt"
    "os"

    "github.com/aws/aws-lambda-go/events"
    "github.com/aws/aws-lambda-go/lambda"
    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/aws/aws-sdk-go/service/dynamodb"
    "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
)

// TODO1: Define User Struct

// TODO2: Define User Response Struct

var svc *dynamodb.DynamoDB

func init() {
    region := os.Getenv("AWS_REGION")
    // Initialize a session
    if session, err := session.NewSession(&aws.Config{
        Region: ®ion,
    }); err != nil {
        fmt.Println(fmt.Sprintf("Failed to initialize a session to AWS: %s", err.Error()))
    } else {
        // Create DynamoDB client
        svc = dynamodb.New(session)
    }
}

func List(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
    var (
        tableName = aws.String(os.Getenv("IAM_TABLE_NAME"))
    )

    // TODO3: Add DynamoDB retrival logic 
}

func main() {
    lambda.Start(List)
}

For TODO1, this time we don't need omitempty tags because we want to retrieve every field. Ths struct would be

type User struct {
    ID            string `json:"id"`
    UserName      string `json:"user_name"`
    FirstName     string `json:"first_name"`
    LastName      string `json:"last_name"`
    Age           int    `json:"age"`
    Phone         string `json:"phone"`
    Password      string `json:"password"`
    Email         string `json:"email"`
    Role          string `json:"role"`
    IsActive      bool   `json:"is_active"`
    CreatedAt     string `json:"created_at"`
    ModifiedAt    string `json:"modified_at"`
    DeactivatedAt string `json:"deactivated_at"`
}

We also need another struct for holding our user response. Let's create a new one and remove TODO2 comment.

type Response struct {
    Response []User `json:"response"`
}

By doing so, our response should look like

{
  "response": [
    {
     // user record #1
    },
    {
     // user record #2
    }
    // and so on
  ]
}

When I was coding the response part, one mistake I made was I accidentally added an extra space after json: like

type Response struct {
    Response []User `json: "response"`
}

and I got the below result

{
  "Response": [
    {
     // user record #1
    },
    {
     // user record #2
    }
    // and so on
  ]
}

If there is no json tag or the tag cannot be read, it will reflect the json field name instead.

This time we need to use svc to retrieve the users from DynamoDB. First of all, we need to build the query input parameters

params := &dynamodb.ScanInput{
    TableName: tableName,
}

Make the DynamoDB Query API call

result, err := svc.Scan(params)
if err != nil {
    fmt.Println("Query API call failed:")
    fmt.Println((err.Error()))
    // Status Bad Request
    return events.APIGatewayProxyResponse{
        Body:       err.Error(),
        StatusCode: 400,
    }, nil
}

Construct users from response

var users []User
for _, i := range result.Items {
    user := User{}
    if err := dynamodbattribute.UnmarshalMap(i, &user); err != nil {
        fmt.Println("Got error unmarshalling:")
        fmt.Println(err.Error())
        return events.APIGatewayProxyResponse{
            Body:       err.Error(),
            StatusCode: 400,
        }, nil
    }
    users = append(users, user)
}

Marshal the user response and return APIGatewayProxyResponse

body, _ := json.Marshal(&Response{
    Response: users,
})
return events.APIGatewayProxyResponse{
    Body:       string(body),
    StatusCode: 200,
}, nil

Let's deploy and test it

./scripts/deploy.sh

Testing

If you go to AWS Lambda Console, you should see there is a function called serverless-iam-dynamodb-dev-get

image

You can test your code either in Lambda or API Gateway.

Since this is a GET method, a request body is not supported. The response should look like

{
  "response": [
    {
      "id": "bd6fde14-3f6a-4551-95f3-349077a5501f",
      "user_name": "wingkwong",
      "first_name": null,
      "last_name": null,
      "age": null,
      "phone": null,
      "password": "$2a$14$iwyLz8DOnbcolxXezZGXG.uXN9kCxJ8aYzMFftYZ06j1Ybb4uThC2",
      "email": "wingkwong@gmail.com",
      "role": "user",
      "is_active": true,
      "created_at": "2019-12-28 13:16:41.09607401 +0000 UTC m=+0.077451001",
      "modified_at": "2019-12-28 13:16:41.096188175 +0000 UTC m=+0.077565137",
      "deactivated_at": null
    }
  ]
}

That's it for part 2. In the next post, we'll create updateHandler.go.

No comments:

Post a Comment

A Fun Problem - Math

# Problem Statement JATC's math teacher always gives the class some interesting math problems so that they don't get bored. Today t...