Creating Serverless Backend using AWS Lambda and DynamoDB

Mon, Oct 29, 2018 3-minute read

A serverless backend lets you store and serve data without ever managing a server, and in this post we’ll build one with a Lambda function that creates and lists items in DynamoDB.

At a high level, the setup looks like this:

Everything starts with the DynamoDB table, which I created using the Java SDK with the code below. The same code can be found in the AWS documentation.

The one change I made was setting the provisioned resources to 1, since we’re just experimenting here and don’t need much capacity.

public void createTable(String name) {
        CreateTableRequest request = new CreateTableRequest()
                .withAttributeDefinitions(new AttributeDefinition(
                        "Name", ScalarAttributeType.S))
                .withKeySchema(new KeySchemaElement("Id", KeyType.HASH))
                .withProvisionedThroughput(new ProvisionedThroughput(
                        new Long(1), new Long(1)))
                .withTableName(name);

        final AmazonDynamoDB ddb = AmazonDynamoDBClientBuilder.defaultClient();

        try {
            CreateTableResult result = ddb.createTable(request);
        } catch (AmazonServiceException e) {
            System.err.println(e.getErrorMessage());
            System.exit(1);
        }

    }

Since DynamoDB is a NoSQL database, I also created a model to shape the items we’ll store. The model is simple: a person’s name, surname and addresses.

package model;

import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable;

import java.util.List;

@DynamoDBTable(tableName = "personTable")
public class Person {
    private String id;
    private String name;
    private String surname;
    private List<String> addresses;

    @DynamoDBHashKey(attributeName = "Id")
    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    @DynamoDBAttribute(attributeName = "name")
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @DynamoDBAttribute(attributeName = "surname")
    public String getSurname() {
        return surname;
    }

    public void setSurname(String surname) {
        this.surname = surname;
    }

    @DynamoDBAttribute(attributeName = "addresses")
    public List<String> getAddresses() {
        return addresses;
    }

    public void setAddresses(List<String> addresses) {
        this.addresses = addresses;
    }
}

Add item function:

import com.amazonaws.AmazonServiceException;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper;
import com.amazonaws.services.dynamodbv2.model.ResourceNotFoundException;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import model.Person;

public class LambdaMain implements RequestHandler<Person,String> {

    public String handleRequest(Person person, Context context) {

        final AmazonDynamoDB ddb = AmazonDynamoDBClientBuilder.defaultClient();
        final DynamoDBMapper mapper = new DynamoDBMapper(ddb);
        try {
            mapper.save(person);
        } catch (ResourceNotFoundException e) {
            System.exit(1);
        } catch (AmazonServiceException e) {
            System.err.println(e.getMessage());
            System.exit(1);
        }
        return "OK";
    }

}

With the function written, let’s invoke it from the Lambda console.

Yes, I know you can’t find James Holden on Earth; maybe we should write Rocinante :).

{
  "id": "f01349dd-332d-4937-b3ae-9b50b995d2ef",
  "name": "James",
  "surname": "Holden",
  "addresses": ["Earth"]
}

Show item function:

import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBScanExpression;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import model.Person;

import java.util.List;
import java.util.Map;

public class LambdaMain implements RequestHandler<Map<String,Object>,List<Person>> {

    public List<Person> handleRequest(Map<String,Object> input, Context context) {

        final AmazonDynamoDB ddb = AmazonDynamoDBClientBuilder.defaultClient();
        final DynamoDBMapper mapper = new DynamoDBMapper(ddb);
        List<Person> personList = null;
        try {
            personList = mapper.scan(Person.class, new DynamoDBScanExpression());
        } catch (Exception e) {
            System.err.println(e.getMessage());
            System.exit(1);

        }
        return personList;
    }
}

Now let’s deploy and test this show method.

To expose it, I created a new stage called prod on API Gateway and published my methods there, and API Gateway handed me a link to invoke them.

webischia@aws:~$ curl https://57l5o766j7.execute-api.us-west-2.amazonaws.com/prod/ | jq
  {
    "id": "1",
    "name": "lambda",
    "surname": "aws"
  },
  {
    "id": "234c1579-6cb4-4488-aa69-961c49a7736b",
    "name": "Fahri",
    "surname": "YARDIMCI",
    "addresses": [
      "Turkey"
    ]
  },
  {
    "id": "4b591dff-7c1d-41c9-8e4a-8d1f57c52cfd",
    "name": "Jeff",
    "surname": "Bezos",
    "addresses": [
      "Earth",
      "Mars",
      "Belt"
    ]
  },
  {
    "id": " f01349dd-332d-4937-b3ae-9b50b995d2ef",
    "name": "James",
    "surname": "Holden",
    "addresses": [
      "Earth"
    ]
  }
]

Conclusion:

Building a serverless backend with Lambda is both fun and fast. You don’t even have to think about servers or the security of the underlying infrastructure.