[Java] Try writing a Lambda function with proxy integration of Amazon API Gateway in Spring Cloud Function quickly

3 minute read

Introduction

Implementing the Lambda function in Java is a pain. When using Spring Cloud Function, the framework seems to absorb the complexity of the area nicely. So I tried to see if it was absorbed in a really good way and I could write it quickly.

By the way, there are many samples that Spring Cloud Function does not have proxy integration, but I could not find the integrated version easily, so it would be helpful to consider it.

Constitution

Create a Maven project as below.

SpringCloudFunctionTest
├── pom.xml
├── serverless.yml
└── src
 ├── main
 │    ├── java
 │     │   └── com
 │     │   └── springcloudfunctiontest
 │     │    ├── AWSLambdaHandler.java
 │     │    ├── Hello.java
 │     │   └── SpringCloudFunctionExampleApplication.java
 │    └── resources
 │    └── application.properties
   └── test
 └── java
 └── com
 └── springcloudfunctiontest
   └── SpringCloudFunctionExampleApplicationTests.java

pom.xml ↓ Keep it like this.

pom.xml


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.techprimers.serverless</groupId>
    <artifactId>spring-cloud-function-test</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-cloud-function-test</name>
    <description>Demo project for Spring Boot with Spring Cloud Function</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.SR2</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-function-adapter-aws</artifactId>
        </dependency>
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-lambda-java-events</artifactId>
            <version>2.0.2</version>
        </dependency>
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-lambda-java-core</artifactId>
            <version>1.1.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.SR2</version>
                <type>pom</type>
                <scope>import</scope>
                </dependency>
            </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <dependencies>
                    <dependency>
                        <groupId>org.springframework.boot.experimental</groupId>
                        <artifactId>spring-boot-thin-layout</artifactId>
                        <version>1.0.10.RELEASE</version>
                    </dependency>
                </dependencies>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <configuration>
                    <createDependencyReducedPom>false</createDependencyReducedPom>
                    <shadedArtifactAttached>true</shadedArtifactAttached>
                    <shadedClassifierName>aws</shadedClassifierName>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

Handler code

Create a class that does ```SpringApplication.run

, just like a normal Spring Boot application.




#### **`SpringCloudFunctionExampleApplication.java`**
```java

package com.springcloudfunctiontest;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringCloudFunctionExampleApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringCloudFunctionExampleApplication.class, args);
    }

}

Then, create an event handler for Lambda. To be honest, I’m not sure why this class is empty… Since it is placed on the back end of API Gateway, set APIGatewayProxyRequestEvent and ```APIGatewayProxyResponseEvent

respectively for Input and Output.




#### **`AWSLambdaHandler.java`**
```java

package com.springcloudfunctiontest;

import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;import org.springframework.cloud.function.adapter.aws.SpringBootRequestHandler;

public class AWSLambdaHandler extends SpringBootRequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
}

Finally, create a business logic class. The specifications of APIGatewayProxyRequestEvent and APIGatewayProxyResponseEvent` are javadoc.

However, even though Spring Cloud Function is compatible with multiple clouds, it would be a completely AWS-specific implementation with such an interface…

Don’t worry about the final handling of setBody JSON.

Hello.java


package com.springcloudfunctiontest;

import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
import org.springframework.stereotype.Component;

import java.util.function.Function;
import java.util.Map;

@Component
public class Hello implements Function<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
    @Override
    public APIGatewayProxyResponseEvent apply(APIGatewayProxyRequestEvent input) {
        Map<String, String> queryStringParameter = input.getQueryStringParameters();

        String id = queryStringParameter.get("id");
        String name = null;

        if( id.equals("11111")) {
          name = "\"Taro\"";
        } else {
          name = "\"Nanashi\"";
        }

        APIGatewayProxyResponseEvent responseEvent = new APIGatewayProxyResponseEvent();
        responseEvent.setStatusCode(200);
        responseEvent.setBody("{\"name\":" + name + "}");
        return responseEvent;
    }
}

Test code

You can also write test code normally.

SpringCloudFunctionExampleApplicationTests.java


package com.springcloudfunctiontest;

import java.util.HashMap;
import java.util.Map;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import static org.assertj.core.api.Assertions.assertThat;

import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringCloudFunctionExampleApplicationTests {

    @Test
    public void HelloTest1() {
        Hello hello = new Hello();
        APIGatewayProxyRequestEvent request = new APIGatewayProxyRequestEvent();

        Map<String,String> queryStringParameter = new HashMap<>();
        queryStringParameter.put("id", "11111");
        request.setQueryStringParameters(queryStringParameter);

        APIGatewayProxyResponseEvent response = hello.apply(request);

        assertThat(response.getBody()).isEqualTo("{\"name\":\"Taro\"}");
    }

    @Test
    public void HelloTest2() {
        Hello hello = new Hello();
        APIGatewayProxyRequestEvent request = new APIGatewayProxyRequestEvent();

        Map<String,String> queryStringParameter = new HashMap<>();
        queryStringParameter.put("id", "22222");
        request.setQueryStringParameters(queryStringParameter);

        APIGatewayProxyResponseEvent response = hello.apply(request);

        assertThat(response.getBody()).isEqualTo("{\"name\":\"Nanashi\"}");
    }
}

Deploy

Since uploading to S3 by hand and waiting for pipeline builds is troublesome, it is not an official version of the application, so let’s deploy it to the text with Serverless Framework.

serverless.yml


service: test

provider:
  name: aws
  runtime: java8
  timeout: 30

  region: ap-northeast-1

package:
  artifact: target/spring-cloud-function-test-0.0.1-SNAPSHOT-aws.jar

functions:
  hello:
    handler: org.springframework.cloud.function.adapter.aws.SpringBootStreamHandler
    events:
      -http:
          path: testapi
          method: get
          integration: lambda-proxy
          request:
            template:
              application/json:'$input.json("$")'

When deployed with this, the REST API will be returned properly like this!

$ curl -X GET https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/testapi?id=11111; echo
{"name":"Taro"}

Conclusion

Well, I’m not entrusting the benefits of the Spring framework at all, so I feel the same as when I created a normal Lambda event handler. Will you appreciate the more you use the framework’s features abundantly?

This time, I understand that if you write a Lambda function in Java, using APIGatewayProxyRequestEvent is easier to implement, and deploying the Serverless Framework is easier.