Wednesday, 11 March 2020
Building Serverless CRUD services in Go with DynamoDB - Part 3
So far we've created ``createHandler.go`` and ``listHandler.go``. In part 3, we will learn how to build ``updateHandler.go``
# Getting started
First, let's add the config under functions in serverless.yml
```
update:
handler: bin/handlers/updateHandler
package:
include:
- ./bin/handlers/updateHandler
events:
- http:
path: iam/{id}
method: patch
cors: true
```
Create a file updateHandler.go under src/handlers
Similarly, we have the below structure.
```
package main
import (
"context"
"encoding/json"
"fmt"
"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/expression"
"gopkg.in/go-playground/validator.v9"
"os"
"reflect"
"strings"
"time"
)
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)
}
}
type User struct {
ID *string `json:"id,omitempty"`
UserName *string `json:"user_name,omitempty" validate:"omitempty,min=4,max=20"`
FirstName *string `json:"first_name,omitempty"`
LastName *string `json:"last_name,omitempty"`
Age *int `json:"age,omitempty"`
Phone *string `json:"phone,omitempty"`
Email *string `json:"email,omitempty" validate:"omitempty,email"`
Role *string `json:"role,omitempty" validate:"omitempty,min=4,max=20"`
IsActive *bool `json:"is_active,omitempty"`
CreatedAt *string `json:"created_at,omitempty"`
ModifiedAt string `json:"modified_at,omitempty"`
DeactivatedAt *string `json:"deactivated_at,omitempty"`
}
func Update(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
var (
tableName = aws.String(os.Getenv("IAM_TABLE_NAME"))
id = aws.String(request.PathParameters["id"])
)
// TODO: Add Update logic
}
func main() {
lambda.Start(Update)
}
```
When we update a record, we need to update the column ``ModifiedAt``.
```
user := &User{
ModifiedAt: time.Now().String(),
}
```
Similar to ``createHandler.go``, we parse the request body and perform validation.
```
// Parse request body
json.Unmarshal([]byte(request.Body), user)
// Validate user struct
var validate *validator.Validate
validate = validator.New()
err := validate.Struct(user)
if err != nil {
// Status Bad Request
return events.APIGatewayProxyResponse{
Body: err.Error(),
StatusCode: 400,
}, nil
}
```
We need to create a ``dynamodb.UpdateItemInput`` for DynamoDB service to update the item. You may see that some people use the following code.
```
input := &dynamodb.UpdateItemInput{
Key: map[string]*dynamodb.AttributeValue{
"id": {
S: aws.String(id),
},
UpdateExpression: aws.String("set #a = :a, #b = :b, #c = :c"),
ExpressionAttributeNames: map[string]*string{
"#a": &a,
"#b": &b,
"#c": &c,
},
ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
":a": {
BOOL: aws.Bool(true),
},
":b": {
BOOL: aws.Bool(true),
},
":c": {
BOOL: aws.Bool(true),
},
},
ReturnValues: aws.String("UPDATED_NEW"),
TableName: "tableName",
}
```
The above example uses ``set`` to update attribute ``a``, ``b``, and ``c`` with mapped attribute values provided in ``ExpressionAttributeValues``.
With such approach, the expression cannot be dynamic as we allow users to update some specific attributes only.
To do that, we use reflect to get the input struct and get the json name without a corresponding tag. Then we append each json field name and its value to UpdateBuilder by using ``UpdateBuilder.Set``.
```
u := reflect.ValueOf(user).Elem()
t := u.Type()
for i := 0; i < u.NumField(); i++ {
f := u.Field(i)
// check if it is empty
if !reflect.DeepEqual(f.Interface(), reflect.Zero(f.Type()).Interface()) {
jsonFieldName := t.Field(i).Name
// get json field name
if jsonTag := t.Field(i).Tag.Get("json"); jsonTag != "" && jsonTag != "-" {
if commaIdx := strings.Index(jsonTag, ","); commaIdx > 0 {
jsonFieldName = jsonTag[:commaIdx]
}
}
// construct update
update = update.Set(expression.Name(jsonFieldName), expression.Value(f.Interface()))
}
}
```
Create a new Builder with Update
```
builder := expression.NewBuilder().WithUpdate(update)
```
Call ``Build()`` to get the expression and error
```
expression, err := builder.Build()
```
Verify if there is an error
```
if err != nil {
// Status Bad Request
return events.APIGatewayProxyResponse{
Body: err.Error(),
StatusCode: 400,
}, nil
}
```
Create ``dynamodb.UpdateItemInput``
```
// Update a record by id
input := &dynamodb.UpdateItemInput{
Key: map[string]*dynamodb.AttributeValue{
"id": {
S: id,
},
},
ExpressionAttributeNames: expression.Names(),
ExpressionAttributeValues: expression.Values(),
UpdateExpression: expression.Update(),
ReturnValues: aws.String("UPDATED_NEW"),
TableName: tableName,
}
```
Feed it into ``UpdateItem``
```
_, err = svc.UpdateItem(input)
```
Check if it can be updated or not
```
if err != nil {
fmt.Println("Got error calling UpdateItem:")
fmt.Println(err.Error())
// Status Internal Server Error
return events.APIGatewayProxyResponse{
Body: err.Error(),
StatusCode: 500,
}, nil
}
// Status OK
return events.APIGatewayProxyResponse{
Body: request.Body,
StatusCode: 200,
}, nil
```
Run the below command to deploy our code
```
./scripts/deploy.sh
```
# Testing
If you go to AWS Lambda Console, you will see there is a function called ``serverless-iam-dynamodb-dev-update``
![image](https://user-images.githubusercontent.com/35857179/76139123-5371b100-6088-11ea-9e74-97ce9aeaeb6d.png)
Go to API Gateway Console to test it, this time we need to set an id.
```json
{
"email": "wingkwong@gmail.com"
}
```
![image](https://user-images.githubusercontent.com/35857179/76139149-aa778600-6088-11ea-8045-26697e65c1ed.png)
If the update returns 200, then go to DynamoDB to verify the result.
![image](https://user-images.githubusercontent.com/35857179/76139160-c24f0a00-6088-11ea-8300-7177bc0647e7.png)
We should see that only the email has been updated.
That's it for part 3. In part 4, we will create ``deleteHandler.go``.
Subscribe to:
Post Comments (Atom)
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...
-
SHA stands for Secure Hashing Algorithm and 2 is just a version number. SHA-2 revises the construction and the big-length of the signature f...
-
Contest Link: [https://www.e-olymp.com/en/contests/19775](https://www.e-olymp.com/en/contests/19775) Full Solution: [https://github.com/...
No comments:
Post a Comment