Go and DynamoDB

How to prohramatically, from Golang, consume DynamoDB
Share this page:

We need to store Project Info in a DynamoDB database.

If you’ve just landed here, we’re doing a “Become a Cloud Architect” Unicorn Workshop by building a Unicorn Pursuit Web App step by step, and you’re more then welcome to join!

Code

Before we start with the configuration, let’s remember what we’re building. Refer to the diagram below:

DynamoDB Diagram

We first need to establish a Service Session with DynamoDB. If you remember, we need two sessions: one with AWS Region, and another one with the Service, which is DynamoDB in this case. All sessions are created in the routes.go package, because that how we can pass them as parameters to different functions in other packages, when routing the request. This way we’re only establishing the session once, and saving a whole lot of resources and latency.

Let’s start with AWS:

 // Create AWS Session, and store it in "sess"
 conf := &aws.Config{Region: aws.String("eu-west-1")}
 sess, err := session.NewSession(conf)
 if err != nil {
  panic(err)
  }

Now DynamoDB Service Session, and store it in “ddbsvc”. Remember, the type of this variable is *dynamodb.DynamoDB:

ddbsvc := dynamodb.New(sess)

When you call service operations, you pass in input parameters as a struct. A successful call usually results in an output struct that you can use. We already have the ProjectExample struct in the main.go package, which defines all the information we need to store:

type ProjectExample struct {
 ID    int    `json:"id"`
 Title string `json:"title"`
 // Owner is owners email address
 Owner string `json:"owner"`
 Content string `json:"content"`
 // Photo is a string with S3 URL
 Photo   string `json:"photo"`
 Votes   int    `json:"votes"`
 }

We now need to set up different operations we’ll be doing with DynamoDB, such as:

  • Creating a Project = getting all info from HTML, and creatning a DynamoDB Item for each new project.
  • Voting = increasing a Votes value of the Item.
  • Leaderboard = Sort our Projects by number of Votes, and display them.

Create an Item (New Project)

Ok, let’s start with Creating a Project. We are retreiving all the information from the simple form within create-project.html, and preparing to store a DynamoDB Item. “project” variable is where we store the new Item.

// DynamoDB we already created using AWS CDK
var tableName = "UnicornDynamoDBVoting"

  // create a DynamoDB Item. Most information is retrieved from HTML "create-project.html"
  // Owner is the email address of the logged in user [to be implemented].
  // result.Location is a return URL value of S3 Photo Upload function.
  // Votes = 0, because we want all new projects to start with 0 votes.
  project := ProjectExample{
   ID: id,
   Title: title,
   Owner: owner,
   Content: content,
   Photo: result.Location,
   Votes: 0,
    }

Now, let’s marshall a new project to a map of a variable:

  // Marshall new project into a map of AttributeValue objects.
  av, err := dynamodbattribute.MarshalMap(project)
  if err != nil {
   c.HTML(http.StatusBadRequest, "create-project.html", gin.H{
    "ErrorTitle":   "Error Marshalling a new projects",
    "ErrorMessage": err.Error()})
    }

Good, now let’s create and Item, and use PutItem function to put to DynamoDB:

  input := &dynamodb.PutItemInput{
   Item:      av,
   TableName: aws.String(tableName),
  }

 // attempt PutItem with the created project object
  _, err = ddbsvc.PutItem(input)
  if err != nil {
   fmt.Println(err)
   c.HTML(http.StatusBadRequest, "create-project.html", gin.H{
    "ErrorTitle":   "Project Creation Failed",
    "ErrorMessage": err.Error()})
  } else {
   // Success, use loadNewProject to add a new project to projectList slice we're using to display, and redirect to OK
   // CurrentProjectNumber is a local variable we'll later use for additional project number verifications
   CurrentProjectNumber++
   loadNewProject(project.ID,project.Title,project.Owner,project.Content,project.Photo,project.Votes)
   render(c, gin.H{
    "title": "Project Created with Success"},
    "submission-successful.html")
    }

Voting

To Vote, we need to get the value of “points” (1-5) that User wants to assign, and increase the project.Votes field of the item. The value is retrieved from project.html, from the dropdown. We’ll call voteForProject function in the handlers.project.go internal package. We will use the UpdateItemInput function:

// Update Votes in DynamoDB
// We need ID and Owner as Primary Key to identify an Item we want to update
ID := strconv.Itoa(project.ID)
Owner := project.Owner

input := &dynamodb.UpdateItemInput{
    ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
     ":r": {
      N: aws.String(votes),
     },
    },
    TableName: aws.String(tableName),
    Key: map[string]*dynamodb.AttributeValue{
     "id": {
      N: aws.String(ID),
     },
     "owner": {
      S: aws.String(Owner),
     },  
    },
    ReturnValues:     aws.String("UPDATED_NEW"),
    // Using UpdateExpression, we will add the "votes" to the current value of the field (current number of votes)
    UpdateExpression: aws.String("set votes = votes + :r"),
   }

   _, err := ddbsvc.UpdateItem(input)
   if err != nil {
    c.HTML(http.StatusBadRequest, "project.html", gin.H{
     "ErrorTitle":   "Error updating Votes",
     "ErrorMessage": err.Error()})
   }

Since internally we’re loading (scanning) the entire database, to optimize performance, we need to re-load the entire table again, and make sure we’re correctly retrieving all the other votes that might have occurred in the meantime. We also need to retrieve the project info again, before showing to the user the current number of Votes:

projectList = nil
loadProjectsDynamoDB(ddbsvc)

// get project again, to be sure Vote value is updated
project, err := getProjectByID(projectID)

// Redirect to Voting SUccessful
render(c, gin.H{
   "title":   "You've voted",
   "payload": project}, "voting-successful.html")

Where to find more info




Last modified June 8, 2020: AWS Diagrams Added (c98aff0)