Going Serverless with your API’s using Java, Part 1

Going Serverless with your API’s using Java, Part 1

Robbert Stevens

Robbert Stevens

In this blog post we will be looking at Serverless and how to set up a Java project using Serverless. Serverless is a framework that helps the user to quickly deploy small functions to the cloud. This could be AWS, Azure, Google Cloud, etc.

These small functions in AWS are called AWS Lambda. Lambda is a serverless compute service that lets you run code without provisioning or managing servers.

The beauty of using Serverless framework is that a large part of the cloud setup will be handled by the project; no need to manually create an API Gateway with custom path’s and opening up a firewall point for the outside world to connect to; all this info is available in the project itself. Some parts of AWS service you will need to add manually.

So why are we using Java here? Well because Java is one of the most common coding languages used for custom rest endpoints and it’s supported by all cloud providers.

All information that will be handled in this blog is available in a git repo that you can find here: github

Setting up Serverless locally

Find your OS systems way of installing Serverless on the official website of Serverless here. After you have installed Serverless, you should be able to run the following command at the folder you want your project to be in serverless create --template aws-java-maven --name my-project-name. To see a full list of all available templates run: serverless create --help

After this has been done a boilerplate project will be created at the folder you had run this command in. This should have resulted in the following folder structure:

├── pom.xml
├── serverless.yml
└── src
    └── main
        ├── java
        │   └── com
        │       └── serverless
        │           ├── ApiGatewayResponse.java
        │           ├── Handler.java
        │           └── Response.java
        └── resources
            └── log4j2.xml

Open up the serverless.yml file, here you will find your service name, what provider you will be using, and the available functions. The default boiler plate generates a basic handler that is not open for the world to connect to, but only by using Serverless or the cloud interface.

To be able to deploy this project to the cloud we would first need a package to upload. So let’s run the following command: mvn clean install, this will create the necessary packages for Serverless. If you would run the deploy command serverless deploy now, And you haven’t configured your AWS credentials in terminal yet, an error will show with the following text “The security token included in the request is invalid.”
This is due to missing cloud credentials (in our case for AWS); we will fix this in the next part.

Setting up AWS for Serverless

Let’s setup the necessary credentials using the AWS CLI. For this you will need to setup a user with enough permissions to be able to use the CLI programmatically and to be able to deploy using CloudFormation. CloudFormation is an AWS service to create blueprints for projects. This way no matter where the project is deployed it will adhere to the blueprint provided. For now, you could give this user the policy administratorAccess (never do this for production environment) to save some time and headache. Save the AWS credentials as .csv, we will need them soon. Open a terminal inside the project folder and run aws configure now you will need to add the AWS Access Key ID, AWS Secret Access Key, default region and default output format the first two are available in the .csv from earlier.

Let’s make this user a “named profile”, this is a collection of settings and credentials that you can apply to an AWS CLI command. Doing so we can separate the AWS accounts on profile level for each environment (dev, test, acc, prd). The credentials are stored in INI format in ~/.aws/credentials change the [default] to [dev] and save.

Now you should be able to run serverless deploy --aws-profile dev

But this still a bit crude, as for each stage we have to manage the variables in a command, so let’s add the profile inside the project. In this way the project contains all the references to the profiles to be used in the future by a CI/CD pipeline. By opening serverless.yml and under provider add stage and profile you should end with something like this:

provider:
  name: aws
  runtime: java8
  lambdaHashingVersion: 20201221
  stage: dev
  profile: dev

If you want a “per stage” profile setup you need to change the profile to use variables, here is a small example snippet:

provider:
  name: aws
  runtime: java8
  lambdaHashingVersion: 20201221
  profile: ${self:custom.profiles.${opt:stage, self:provider.stage, 'dev'}}
custom:
  profiles:
    dev: dev
    test: testProfile
    acc: accProfile
    prod: prodProfile

This snippet will load the profile depending upon the stage specified in the command line options (or default to ‘dev’ if unspecified) so in your ci/cd pipeline you could specify the different stages like so:
serverless deploy -s dev
serverless deploy -s testProfile
serverless deploy -s accProfile
serverless deploy -s prodProfile

Creating a basic Get request

If you deploy the project as is and call the function already provided by the boilerplate with the following command serverless invoke --function hello it will give you a body containing the following:

{
    "statusCode": 200,
    "body": "{\"message\":\"Go Serverless v1.x! Your function executed successfully!\",\"input\":{}}",
    "headers": {
        "X-Powered-By": "AWS Lambda & serverless"
    },
    "isBase64Encoded": false
}

The above function is very basic, and more importantly, it is not available for the outside world, due to having no API path and method associated with it.

So, let’s create a new handler, that for this example gives the time at Amsterdam and that is available to the outside world using the path /time and a GET method.

Start by creating a new Java class called TimeHandler
This class needs to implement RequestHandler<Map<String, Object>, ApiGatewayResponse>

Copy this snippet of code:

@Override
public ApiGatewayResponse handleRequest(Map<String, Object> input, Context context) {
	DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
	String time = ZonedDateTime.now(ZoneId.of( "Europe/Amsterdam" )).format(formatter);

	Response responseBody = new Response("Hello, the current time is: " + time);
	Map<String, String> headers = new HashMap<>();
	headers.put("X-Powered-By", "AWS Lambda & Serverless");
	headers.put("Content-Type", "application/json");
	return ApiGatewayResponse.builder()
			.setStatusCode(200)
			.setObjectBody(responseBody)
			.setHeaders(headers)
			.build();
}

Next add the following inside the serverless.yml under functions:

functions:
  currentTime:
    handler: com.serverless.TimeHandler
    events:
      - http:
          path: time
          method: get

The Java class we just created is a placeholder for the current day real life scenario api, where an application performs a get request and gets some date. The addition to the serverless.yml is needed to notify Serverless of the existence of this class and how to call it. Because our provider is AWS, Serverless will use CloudFormation for this.

Deploying to AWS

After all this setup it is time to deploy towards AWS. This is the time to run mvn clean install (if you haven’t this already) to create the package that will be uploaded to AWS. After this has been done run serverless deploy -s dev the -s gives Serverless the info on what stage to deploy in this case dev. After running the above command, you will be greeted with rundown of what Serverless just deployed on your AWS account:

serverless deploy -s dev
Serverless: Packaging service...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service hello-dev.jar file to S3 (3.22 MB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
.................................
Serverless: Stack update finished...
Service Information
service: example-serverless
stage: dev
region: us-east-1
stack: example-serverless-dev
resources: 14
api keys:
  None
endpoints:
  GET - https://{uniqueurl}.execute-api.us-east-1.amazonaws.com/dev/time
functions:
  hello: example-serverless-dev-hello
  currentTime: example-serverless-dev-currentTime
layers:
  None

You can see that the region is us-east-1, there is 1 endpoint available using GET and 2 functions available. To call the functions internally you can use the following command serverless invoke --function <name of function> This is usefull for debugging. But please use the https://{uniqueurl}.execute-api.us-east-1.amazonaws.com/dev/time to verify that it’s really available for the whole world.

As we haven’t defined a region in serverless.yml, it chooses the default region. In this case us-east-1. Lets change this to something else, but first let’s clean up using serverless remove. This will remove all the project info from our aws account on us-east-1.

Inside serverless.yml under provider add region: eu-west-1 and run the deploy command again serverless deploy -s dev, there is no need to run mvn clean install again as we haven’t changed any Java code. Now the Lambda should be deployed in eu-west-1 (better known as Ireland)

In the next blog I will be going in-depth on SQS, S3 and decoupling services using those two, using serverless.

All information in this blog is available in a git repo that you can find here: github