The Serverless Framework for an AWS serverless Java application

Rostyslav Myronenko
8 min readSep 13, 2021

--

Table of contents

Introduction

In my previous blog posts about AWS Proton and AWS SAM, I reviewed these tools for purposes of defining an Infrastructure as A Code and CI/CD pipeline for a simple serverless Java application that implements CRUD API for some items. In this post, I will consider one more tool — Serverless Framework, and try to build, deploy and manage a simple serverless AWS application written in Java.

What is the Serverless Framework?

The Serverless Framework is a framework to simplify the development process and the whole DevOps pipeline for serverless applications. It supports different Public Cloud vendors and allows the definition of an Infrastructure As A Code.

The difference with other tools is that the applications developed with the serverless.com framework require management in the Serverless Framework web portal. The portal provides centralized management and single monitoring of all the serverless applications of an organization.

The entry point of a Serverless Framework application is the serverless.yml file in a source root that describes the serverless application in its own syntax. Just like an AWS SAM template, it allows configuring the whole serverless application.

Serverless Framework has its own blueprints that can be used to bootstrap your own application. The main of them can be observed at your Serverless Framework app dashboard at https://app.serverless.com/ when you click the “Create new” button.

Serverless application example

As an example, I will use the same application as in previous posts. It is a CRUD API with Lambda handlers implemented with Java. The structure of the application is given in the picture below.

As the data model for DynamoDB, book objects are chosen with the attributes given below:

  • isbn (String) — primary key
  • author (String) — book author
  • name (String) — book name

Prerequisites

Implementation

The final version of the code is available in the GitHub repository:

https://github.com/rimironenko/aws-serverless-framework-app

To bootstrap the initial application, let’s use the “aws-java-maven” Serverless Framework template.

1. Generate the application by command:

serverless create --template aws-java-maven --name items-api -p aws-java-serverless-api

“Name” parameter specifies the name of the application for Serverless Framework. The “-p” parameter specifies the name of the project root folder.

2. Create the application in the Serverless Framework web portal.

  • Go to https://app.serverless.com/
  • Click “Create app” and choose the “serverless framework” option.
  • Type in “app” and “service” parameters as specified in the serverless.yml file of the app that was just generated. If “app” is missed, just add it manually. I used the name of the project folder.
app: aws-serverless-framework-app
service: items-api

3. Configure an AWS provider in the Serverless Framework web portal settings.

For the AWS provider, I used the AWS Access key generated for my AWS user with admin (non-root) access.

4. Configure the Provider in the serverless.yml file:

provider:
name: aws
runtime: java8
lambdaHashingVersion: 20201221
environment:
TABLE: ${self:service}-${sls:stage}-BooksDynamoDbTable-${sls:instanceId}
iamRoleStatements:
- Effect: Allow
Action:
- dynamodb:DescribeTable
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:BatchGetItem
Resource: "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/${self:provider.environment.TABLE}"

Highlights:

  • Java 8 runtime is defined by default for all the Lambda functions (but can be overwritten for a particular Lambda function with a different runtime).
  • The TABLE environment variable is defined as a String with Serverless Framework variables to create a unique table name. To get more information about the usage of variables, see https://www.serverless.com/framework/docs/providers/aws/guide/variables.
  • iamRoleStatements defines the default IAM policy for Lambda functions.

For a detailed description of Providers, please see the official documentation at https://www.serverless.com/framework/docs/guides/providers

5. Describe the DynamoDB table in the serverless.yml file:

resources:
Resources:
BooksDynamoDbTable:
Type: 'AWS::DynamoDB::Table'
Properties:
AttributeDefinitions:
- AttributeName: isbn
AttributeType: S
KeySchema:
- AttributeName: isbn
KeyType: HASH
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
SSESpecification:
SSEEnabled: true
TableName: ${self:provider.environment.TABLE}

Highlights:

  • The definition is written in CloudFormation syntax.
  • For the table name, the environment variable from the Provider configuration is used.

6. Describe the Lambda function in the serverless.yml file:

functions:
getItem:
handler: com.home.amazon.serverless.lambda.GetItemFunction
events:
- http:
path: /books/{isbn}
method: get

Highlights:

  • The function is mapped to an API gateway method.
  • The API gateway will be created automatically by Serverless Framework.
  • The environment variable from the Provider configuration will be added to the function to reference the DynamoDB table.

7. Implement a POJO class for our data model:

package com.home.amazon.serverless.model;

import com.fasterxml.jackson.annotation.JsonProperty;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;

@DynamoDbBean
public class Book {

public static final String PARTITION_KEY = "isbn";

@JsonProperty(PARTITION_KEY)
private String isbn;

@JsonProperty("author")
private String author;

@JsonProperty("name")
private String name;

@DynamoDbPartitionKey
public String getIsbn() {
return isbn;
}

public void setIsbn(String isbn) {
this.isbn = isbn;
}

public String getAuthor() {
return author;
}

public void setAuthor(String author) {
this.author = author;
}

public String getName() {
return name;
}

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

}

Highlights:

  • Enhanced DynamoDB client library was used to simplify work with the DynamoDB.
  • Jackson library was used for the JSON serialization and deserialization.

8. Implement the Lambda functions:

An example of a Get-Item Lambda function is given in the code snippet below.

package com.home.amazon.serverless.lambda;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.LambdaLogger;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.home.amazon.serverless.model.Book;
import com.home.amazon.serverless.utils.DependencyFactory;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable;
import software.amazon.awssdk.enhanced.dynamodb.Key;
import software.amazon.awssdk.enhanced.dynamodb.TableSchema;

import java.util.Collections;
import java.util.Map;

public class GetItemFunction implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {

private final DynamoDbEnhancedClient dbClient;
private final String tableName;
private final TableSchema<Book> bookTableSchema;

public GetItemFunction() {
dbClient = DependencyFactory.dynamoDbEnhancedClient();
tableName = DependencyFactory.tableName();
bookTableSchema = TableSchema.fromBean(Book.class);
}

@Override
public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) {
String response = "";
LambdaLogger logger = context.getLogger();
DynamoDbTable<Book> booksTable = dbClient.table(tableName, bookTableSchema);
Map<String, String> pathParameters = input.getPathParameters();
if (pathParameters != null) {
String itemPartitionKey = pathParameters.get(Book.PARTITION_KEY);
Book item = booksTable.getItem(Key.builder().partitionValue(itemPartitionKey).build());
if (item != null) {
try {
response = new ObjectMapper().writeValueAsString(item);
} catch (JsonProcessingException e) {
logger.log("Failed create a JSON response: " + e);
}
}
}

return new APIGatewayProxyResponseEvent().withStatusCode(200)
.withIsBase64Encoded(Boolean.FALSE)
.withHeaders(Collections.emptyMap())
.withBody(response);
}
}

The DependencyFactory helping class:

package com.home.amazon.serverless.utils;

import software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider;
import software.amazon.awssdk.core.SdkSystemSetting;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient;
import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;

public class DependencyFactory {

public static final String ENV_VARIABLE_TABLE = "TABLE";

private DependencyFactory() {
}

/**
*
@return an instance of DynamoDbClient
*/

public static DynamoDbEnhancedClient dynamoDbEnhancedClient() {
return DynamoDbEnhancedClient.builder()
.dynamoDbClient(DynamoDbClient.builder()
.credentialsProvider(EnvironmentVariableCredentialsProvider.create())
.region(Region.of(System.getenv(SdkSystemSetting.AWS_REGION.environmentVariable())))
.httpClientBuilder(UrlConnectionHttpClient.builder())
.build())
.build();
}

public static String tableName() {
return System.getenv(ENV_VARIABLE_TABLE);
}
}

Highlights:

  • AWS SDK Version 2 for Java was used.
  • Enhanced DynamoDB client library was used to simplify work with the DynamoDB.
  • Jackson library was used for the JSON serialization and deserialization.
  • AWS Lambda Events library was used to simplify work with API gateway requests and responses.

9. Specify the packaging in the serverless.yml file:

package:
artifact: target/aws-serverless-framework-app-dev.jar

10. Build and deploy the application:

  • build the application with Maven by the following command:mvn clean install
  • Log in to the application at the Serverless Framework web portal by the following command: serverless login. The Serverless Framework CLI will open your default browser and you will be prompted to authenticate on the Serverless Framework web portal. Once the login is successful, you will see the message as on the picture given below:
  • Deploy the application to AWS by the following command: serverless deploy
  • Wait until the deployment is finished.

The application is deployed as the CloudFormation stack that can be observed in the AWS management console.

What about CI/CD?

Serverless Framework allows configuring a CI/CD at the web portal.

To configure the CI/CD pipeline for the application, we need to open the app settings and configure the build information as in the picture given below.

Once the CI/CD is configured, the build and deployment are automatically triggered when the changes in the VCS repository occur.

Once the deployment is finished, information about it together with the build log is available on the portal.

The interesting thing is that Serverless Framework knows how to run build with the configured runtime and for the Java application executes the commands given below:

  • mvn -ntp -B clean package
  • mvn -ntp -B test
  • serverless deploy (with the application-specific parameters)

Concerns

While the Serverless Framework tool is pretty nice, there are several concerns to consider its usage with Java.

Conclusion

Advantages of Serverless Framework

  1. Suits best for small serverless applications and startups.
  2. Has several built-in templates that simplify the creation of a serverless application.
  3. Full control over the developers’ code.
  4. A serverless application can be deployed across different Public Clouds.

Disadvantages of Serverless Framework

  1. Cloud Formation is fully re-creating some resources (e.g., DynamoDB tables) from the stack after serverless deploy command even when these resources have not been changed. This is just completely wrong: to solve this issue you need to make the configuration more complex, and the Serverless Framework rather becomes an overhead.
  2. Configuration at the web portal is required to make everything working. The standalone Serverless CLI does not work.
  3. Cloud Formation is fully re-creating the stack after serverless deploy command even when some resources have not been changed.
  4. CI/CD pipeline should be configured on the web portal.
  5. The framework does not provide configuration for custom build and deployment pipelines. It can be done via AWS CodeBuild, CodeDeploy, and CodePipeline, but it may be a complex task for teams that have chosen the framework

Constraints of Serverless Framework

  1. Cloud Formation stack is re-created under the hood from a basic configuration unless you manage this by complex customization.
  2. Registration at the web portal is required.
  3. Uploading of a custom access key is required at the web portal.
  4. Works best for Node.js, Python, and Go.

What next?

In my next post, I am going to consider Edge Computing of a serverless application with AWS Green Grass / AWS Outposts.

--

--