CLOVER🍀

That was when it all began.

LocalStackにJavaで作成したAWS Lambda関数をデプロイしてみる(Mavenアーキタイプ、AWS SAM)

これは、なにをしたくて書いたもの?

Javaのアプリケーションフレームワークを使って、LocalStackにAmazon API GatewayAWS Lambdaな環境を作って
みようとしていたのですが、ちょっとハマったのでひとつずつ順番に見ていこうかなと。

今回は、LocalStack上にAWSのツールを使って作成したAWS Lambda関数をデプロイしてみたいと思います。

方法は2つ、MavenアーキタイプAWS SAMですね。

まあ、Mavenアーキタイプを使っても、デプロイにはSAMを求められるようですが。

AWS Lambda用のMavenアーキタイプ

AWS Lambda関数については、最初はシンプルにこちらのドキュメントを見て作ろうかと思っていたのですが。

Java の AWS Lambda 関数ハンドラー - AWS Lambda

少し調べてみると、AWS Lambda用のMavenアーキタイプが出ていることがわかったので、こちらを使ってみることに
しました。

Bootstrapping a Java Lambda application with minimal AWS Java SDK startup time using Maven | AWS Developer Tools Blog

READMEは、GitHub上のものを読んだ方がよさそうですね。

https://github.com/aws/aws-sdk-java-v2/blob/2.17.46/archetypes/archetype-lambda/README.md

環境

今回の環境は、こちらです。

LocalStack。

$ localstack --version
0.12.18

こちらのコマンドで起動しておきます。

$ LAMBDA_EXECUTOR=docker-reuse localstack start

AWS CLIやSAMは、LocalStackが提供しているローカル用のものを使います。

$ awslocal --version
aws-cli/2.2.41 Python/3.8.8 Linux/5.4.0-86-generic exe/x86_64.ubuntu.20 prompt/off


$ samlocal --version
SAM CLI, version 1.31.0

Javaに関するもの。

$ java --version
openjdk 11.0.11 2021-04-20
OpenJDK Runtime Environment (build 11.0.11+9-Ubuntu-0ubuntu2.20.04)
OpenJDK 64-Bit Server VM (build 11.0.11+9-Ubuntu-0ubuntu2.20.04, mixed mode, sharing)


$ mvn --version
Apache Maven 3.8.2 (ea98e05a04480131370aa0c110b8c54cf726c06f)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 11.0.11, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.4.0-86-generic", arch: "amd64", family: "unix"

AWS Lambda関数用のMavenアーキタイプを使って、プロジェクトを作成する

では、最初にMavenアーキタイプを使ってAWS Lambda用のプロジェクトを作成してみます。

こちらのブログを見ながら

Bootstrapping a Java Lambda application with minimal AWS Java SDK startup time using Maven | AWS Developer Tools Blog

コマンド実行。

$ mvn archetype:generate \
    -DarchetypeGroupId=software.amazon.awssdk \
    -DarchetypeArtifactId=archetype-lambda \
    -DarchetypeVersion=2.17.46 \
    -DgroupId=org.littlewings \
    -DartifactId=hello-java-lambda \
    -Dversion=0.0.1-SNAPSHOT \
    -Dservice=s3 \
    -DinteractiveMode=false

指定するパラメーターは、こちらを参照。

Maven Archetype for lambda function using AWS SDK for Java 2.x / Parameters

serviceというパラメーターに使いたいAWSサービス名を指定することで、依存関係にそのサービスを使うための
ライブラリと、クライアントコードを生成してくれるようなのですが。

これ、必須になっています。今回はとりあえず動かしてみたいだけなので要らないのですが…。仕方ないので、今回は
s3を指定して後で削除することにしました。

指定できるサービス名は、ここから選びます。

https://github.com/aws/aws-sdk-java-v2/tree/2.17.46/services

存在しないものを指定しても、怒られたりはしないみたいです(変なpom.xmlができあがる)。

作成されたプロジェクト内に移動して

$ cd hello-java-lambda

どのようなものができあがったか、見てみましょう。

$ tree
.
├── README.md
├── pom.xml
├── src
│   ├── main
│   │   └── java
│   │       └── org
│   │           └── littlewings
│   │               ├── App.java
│   │               └── DependencyFactory.java
│   └── test
│       └── java
│           └── org
│               └── littlewings
│                   └── AppTest.java
└── template.yaml

9 directories, 6 files

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>

    <groupId>org.littlewings</groupId>
    <artifactId>hello-java-lambda</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <maven.shade.plugin.version>3.2.1</maven.shade.plugin.version>
        <maven.compiler.plugin.version>3.6.1</maven.compiler.plugin.version>
        <exec-maven-plugin.version>1.6.0</exec-maven-plugin.version>
        <aws.java.sdk.version>2.17.46</aws.java.sdk.version>
        <aws.lambda.java.version>1.2.0</aws.lambda.java.version>
        <junit5.version>5.4.2</junit5.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>software.amazon.awssdk</groupId>
                <artifactId>bom</artifactId>
                <version>${aws.java.sdk.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>software.amazon.awssdk</groupId>
            <artifactId>s3</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>software.amazon.awssdk</groupId>
                    <artifactId>netty-nio-client</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>software.amazon.awssdk</groupId>
                    <artifactId>apache-client</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>software.amazon.awssdk</groupId>
            <artifactId>url-connection-client</artifactId>
        </dependency>

        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-lambda-java-core</artifactId>
            <version>${aws.lambda.java.version}</version>
        </dependency>

        <!-- Test Dependencies -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>${junit5.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${maven.compiler.plugin.version}</version>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>${maven.shade.plugin.version}</version>
                <configuration>
                    <createDependencyReducedPom>false</createDependencyReducedPom>
                    <finalName>hello-java-lambda</finalName>
                    <filters>
                        <filter>
                            <artifact>*:*</artifact>
                            <excludes>
                                <!-- Suppress module-info.class warning-->
                                <exclude>module-info.class</exclude>
                            </excludes>
                        </filter>
                    </filters>
                </configuration>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

依存関係は、こちら。最初に定義されているのが、serviceに指定したものですね。

    <dependencies>
        <dependency>
            <groupId>software.amazon.awssdk</groupId>
            <artifactId>s3</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>software.amazon.awssdk</groupId>
                    <artifactId>netty-nio-client</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>software.amazon.awssdk</groupId>
                    <artifactId>apache-client</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>software.amazon.awssdk</groupId>
            <artifactId>url-connection-client</artifactId>
        </dependency>

        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-lambda-java-core</artifactId>
            <version>${aws.lambda.java.version}</version>
        </dependency>

        <!-- Test Dependencies -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>${junit5.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

Maven Shade Pluginの設定も入っています。

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>${maven.shade.plugin.version}</version>
                <configuration>
                    <createDependencyReducedPom>false</createDependencyReducedPom>
                    <finalName>hello-java-lambda</finalName>
                    <filters>
                        <filter>
                            <artifact>*:*</artifact>
                            <excludes>
                                <!-- Suppress module-info.class warning-->
                                <exclude>module-info.class</exclude>
                            </excludes>
                        </filter>
                    </filters>
                </configuration>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

ケルトンのコード。

src/main/java/org/littlewings/App.java

package org.littlewings;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import software.amazon.awssdk.services.s3.S3Client;

/**
 * Lambda function entry point. You can change to use other pojo type or implement
 * a different RequestHandler.
 *
 * @see <a href=https://docs.aws.amazon.com/lambda/latest/dg/java-handler.html>Lambda Java Handler</a> for more information
 */
public class App implements RequestHandler<Object, Object> {
    private final S3Client s3Client;

    public App() {
        // Initialize the SDK client outside of the handler method so that it can be reused for subsequent invocations.
        // It is initialized when the class is loaded.
        s3Client = DependencyFactory.s3Client();
        // Consider invoking a simple api here to pre-warm up the application, eg: dynamodb#listTables
    }

    @Override
    public Object handleRequest(final Object input, final Context context) {
        // TODO: invoking the api call using s3Client.
        return input;
    }
}

serviceで指定したサービスに対する、クライアントを作成するコードもついてくるようです。

src/main/java/org/littlewings/DependencyFactory.java

package org.littlewings;

import software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider;
import software.amazon.awssdk.core.SdkSystemSetting;
import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;

/**
 * The module containing all dependencies required by the {@link App}.
 */
public class DependencyFactory {

    private DependencyFactory() {}

    /**
     * @return an instance of S3Client
     */
    public static S3Client s3Client() {
        return S3Client.builder()
                       .credentialsProvider(EnvironmentVariableCredentialsProvider.create())
                       .region(Region.of(System.getenv(SdkSystemSetting.AWS_REGION.environmentVariable())))
                       .httpClientBuilder(UrlConnectionHttpClient.builder())
                       .build();
    }
}

テストコード。

src/test/java/org/littlewings/AppTest.java

package org.littlewings;

import static org.junit.jupiter.api.Assertions.assertEquals;

import org.junit.jupiter.api.Test;

public class AppTest {

    @Test
    public void handleRequest_shouldReturnConstantValue() {
        App function = new App();
        Object result = function.handleRequest("echo", null);
        assertEquals("echo", result);
    }
}

AWS SAMでデプロイするためのYAMLテンプレートもついてきます。

template.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
  # See https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html
  # for more info to see how to tune the lambda function configs based on your use case.
  AppFunction:
    Type: AWS::Serverless::Function
    Properties:
      Runtime: java8
      Handler: org.littlewings.App::handleRequest
      Timeout: 60
      MemorySize: 512
      CodeUri: ./target/hello-java-lambda.jar
      # Attach policies here to give the function permission to access other AWS resources if needed
      # See: https://github.com/awslabs/serverless-application-model/blob/master/docs/policy_templates.rst
      # eg:
      #Policies:
      # - S3ReadPolicy:
      #     BucketName: test-bucket

README.mdも作成され、こちらには事前にインストールしておくものが書かれていたり(AWS SAM CLIが入って
いますね)

## Prerequisites
- Java 1.8+
- Apache Maven
- [AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html)
- Docker

ビルド方法やローカルでの動かし方

#### Building the project

mvn clean install


#### Testing it locally

sam local invoke

デプロイ方法が書かれています。完全にAWS SAMを使う形です。

## Deployment

The generated project contains a default [SAM template](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html) file `template.yaml` where you can 
configure different properties of your lambda function such as memory size and timeout. You might also need to add specific policies to the lambda function
so that it can access other AWS resources.

To deploy the application, you can run the following command:

sam deploy --guided

AWS Lambda関数を作成してLocalStackにデプロイする

では、生成されたプロジェクトを変更してみましょう。今回は、あくまでデプロイと動作確認が目的なので、
リクエストで受け取ったパラメーターを使った簡単な処理を書くことにします。

まずは、プロジェクトで使用するJavaのバージョンを11にしておきます。

        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>

Amazon S3へのアクセスは行わないので、依存関係から削除。

        <!--
        <dependency>
            <groupId>software.amazon.awssdk</groupId>
            <artifactId>s3</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>software.amazon.awssdk</groupId>
                    <artifactId>netty-nio-client</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>software.amazon.awssdk</groupId>
                    <artifactId>apache-client</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        -->

既存のソースコードも要らないので、削除。

$ rm src/main/java/org/littlewings/* src/test/java/org/littlewings/AppTest.java

作成したコードは、こちら。

src/main/java/org/littlewings/lambda/App.java

package org.littlewings.lambda;

import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.LambdaLogger;
import com.amazonaws.services.lambda.runtime.RequestHandler;

public class App implements RequestHandler<Map<String, Object>, Map<String, Object>> {
    @Override
    public Map<String, Object> handleRequest(Map<String, Object> input, Context context) {
        LambdaLogger logger = context.getLogger();

        logger.log(String.format("Request: %s", input));

        String message = (String) input.get("message");
        int repeat = (Integer) input.get("repeat");

        return Map.of(
                "result",
                IntStream.rangeClosed(1, repeat).mapToObj(i -> message).collect(Collectors.toList())
        );
    }
}

リクエストにはメッセージと回数を受け取るものとし、指定された回数だけメッセージを返す関数にしています。

こちらをパッケージングして

$ mvn package

LocalStackにデプロイ。

$ awslocal lambda create-function \
  --function-name hello_java_lambda \
  --zip-file fileb://target/hello-java-lambda.jar \
  --handler org.littlewings.lambda.App::handleRequest \
  --runtime java11 \
  --role test-role

関数を更新する場合は、こちらになります。

$ awslocal lambda update-function-code \
  --function-name hello_java_lambda \
  --zip-file fileb://target/hello-java-lambda.jar

動作確認。

$ awslocal lambda invoke --function-name hello_java_lambda --payload '{"message": "Hello World", "repeat": 3 }' --cli-binary-format raw-in-base64-out result.json
{
    "StatusCode": 200,
    "LogResult": "",
    "ExecutedVersion": "$LATEST"
}

OKです。

$ cat result.json 
{"result":["Hello World","Hello World","Hello World"]}

では、いったん削除。

$ awslocal lambda delete-function --function-name hello_java_lambda
AWS SAMでデプロイする

せっかくテンプレートファイルが生成されているので、AWS SAMでもデプロイしてみましょう。

テンプレートファイルのRuntimejava11に変更し、Handlerも作成したクラスおよびメソッドに変更します。

    Properties:
      Runtime: java11
      Handler: org.littlewings.lambda.App::handleRequest

LocalStackの場合は先にAmazon S3バケットを作成して

$ awslocal s3 mb s3://my-bucket

デプロイ。

$ samlocal deploy --stack-name my-stack --template-file template.yaml --region us-east-1 --s3-bucket my-bucket

作成されたAWS Lambda関数のFunctionNameを確認して

$ awslocal lambda list-functions
{
    "Functions": [
        {
            "FunctionName": "my-stack-AppFunction-e4577968",
            "FunctionArn": "arn:aws:lambda:us-east-1:000000000000:function:my-stack-AppFunction-e4577968",
            "Runtime": "java11",
            "Role": "arn:aws:iam::000000000000:role/cf-my-stack-AppFunctionRole",
            "Handler": "org.littlewings.lambda.App::handleRequest",
            "CodeSize": 290766,
            "Description": "",
            "Timeout": 60,
            "MemorySize": 512,
            "LastModified": "2021-09-26T07:20:51.368+0000",
            "CodeSha256": "qY08o6ZnzAkdcl03jNcWNhgkZvY8e+UnziUnuN4ObP0=",
            "Version": "$LATEST",
            "VpcConfig": {},
            "TracingConfig": {
                "Mode": "PassThrough"
            },
            "RevisionId": "467b7b1a-85ce-45c3-949b-d729fd100669",
            "State": "Active",
            "LastUpdateStatus": "Successful",
            "PackageType": "Zip"
        }
    ]
}

確認。

$ awslocal lambda invoke --function-name my-stack-AppFunction-e4577968 --payload '{"message": "Hello World", "repeat": 3 }' --cli-binary-format raw-in-base64-out result.json

こちらもOKですね。

$ cat result.json
{"result":["Hello World","Hello World","Hello World"]}

オマケ: AWS SAMでAWS Lambda関数を作成して、LocalStackにデプロイしてみる

なんとなく、AWS SAMでAWS Lambda関数を作成して、LocakStackにデプロイするところもやってみましょう。
こちらは、Amazon API Gateway経由で動かすことにします。

プロジェクトの作成。

$ samlocal init

Java 11で、プロジェクト名はhello-java-lambda-samで作成。

Template selection: 1

    -----------------------
    Generating application:
    -----------------------
    Name: hello-java-lambda-sam
    Runtime: java11
    Dependency Manager: maven
    Application Template: hello-world
    Output Directory: .
    
    Next steps can be found in the README file at ./hello-java-lambda-sam/README.md

プロジェクト内に移動。

$ cd hello-java-lambda-sam

生成されたファイルは、こんな感じです。

$ tree
.
├── HelloWorldFunction
│   ├── pom.xml
│   └── src
│       ├── main
│       │   └── java
│       │       └── helloworld
│       │           └── App.java
│       └── test
│           └── java
│               └── helloworld
│                   └── AppTest.java
├── README.md
├── events
│   └── event.json
└── template.yaml

9 directories, 6 files

テンプレートファイル。

template.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  hello-java-lambda-sam

  Sample SAM Template for hello-java-lambda-sam

# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
  Function:
    Timeout: 20

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
    Properties:
      CodeUri: HelloWorldFunction
      Handler: helloworld.App::handleRequest
      Runtime: java11
      MemorySize: 512
      Environment: # More info about Env Vars: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#environment-object
        Variables:
          PARAM1: VALUE
      Events:
        HelloWorld:
          Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
          Properties:
            Path: /hello
            Method: get

Outputs:
  # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
  # Find out more about other implicit resources you can reference within SAM
  # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
  HelloWorldApi:
    Description: "API Gateway endpoint URL for Prod stage for Hello World function"
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
  HelloWorldFunction:
    Description: "Hello World Lambda Function ARN"
    Value: !GetAtt HelloWorldFunction.Arn
  HelloWorldFunctionIamRole:
    Description: "Implicit IAM Role created for Hello World function"
    Value: !GetAtt HelloWorldFunctionRole.Arn

生成されたAWS Lambda関数本体。

HelloWorldFunction/src/main/java/helloworld/App.java

package helloworld;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

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

/**
 * Handler for requests to Lambda function.
 */
public class App implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {

    public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) {
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json");
        headers.put("X-Custom-Header", "application/json");

        APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent()
                .withHeaders(headers);
        try {
            final String pageContents = this.getPageContents("https://checkip.amazonaws.com");
            String output = String.format("{ \"message\": \"hello world\", \"location\": \"%s\" }", pageContents);

            return response
                    .withStatusCode(200)
                    .withBody(output);
        } catch (IOException e) {
            return response
                    .withBody("{}")
                    .withStatusCode(500);
        }
    }

    private String getPageContents(String address) throws IOException{
        URL url = new URL(address);
        try(BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()))) {
            return br.lines().collect(Collectors.joining(System.lineSeparator()));
        }
    }
}

https://checkip.amazonaws.com を使って、自端末のグローバルIPを返すAWS Lambda関数のようです。

テストコード。

HelloWorldFunction/src/test/java/helloworld/AppTest.java

package helloworld;

import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import org.junit.Test;

public class AppTest {
  @Test
  public void successfulResponse() {
    App app = new App();
    APIGatewayProxyResponseEvent result = app.handleRequest(null, null);
    assertEquals(result.getStatusCode().intValue(), 200);
    assertEquals(result.getHeaders().get("Content-Type"), "application/json");
    String content = result.getBody();
    assertNotNull(content);
    assertTrue(content.contains("\"message\""));
    assertTrue(content.contains("\"hello world\""));
    assertTrue(content.contains("\"location\""));
  }
}

HelloWorldFunction/pom.xml

<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/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>helloworld</groupId>
    <artifactId>HelloWorld</artifactId>
    <version>1.0</version>
    <packaging>jar</packaging>
    <name>A sample Hello World created for SAM CLI.</name>
    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-lambda-java-core</artifactId>
            <version>1.2.1</version>
        </dependency>
        <dependency>
          <groupId>com.amazonaws</groupId>
          <artifactId>aws-lambda-java-events</artifactId>
          <version>3.6.0</version>
        </dependency>
        <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>4.13.1</version>
          <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-shade-plugin</artifactId>
          <version>3.2.4</version>
          <configuration>
          </configuration>
          <executions>
            <execution>
              <phase>package</phase>
              <goals>
                <goal>shade</goal>
              </goals>
            </execution>
          </executions>
        </plugin>
      </plugins>
    </build>
</project>

今回は、生成されたコードをそのまま使うことにします。pom.xmlの定義は、Java 8向けですが、とりあえず
気にしないことにします。

ビルドして

$ samlocal build
Building codeuri: /path/to/hello-java-lambda-sam/HelloWorldFunction runtime: java11 metadata: {} functions: ['HelloWorldFunction']
Running JavaMavenWorkflow:CopySource
Running JavaMavenWorkflow:MavenBuild
Running JavaMavenWorkflow:MavenCopyDependency
Running JavaMavenWorkflow:MavenCopyArtifacts

Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml

Commands you can use next
=========================
[*] Invoke Function: sam local invoke
[*] Deploy: sam deploy --guided
    

Amazon S3バケットを作成して

$ awslocal s3 mb s3://my-bucket

デプロイ。

$ samlocal deploy --stack-name my-stack --region us-east-1 --s3-bucket my-bucket

Amazon API GatewayREST APIのIDを確認して

$ awslocal apigateway get-rest-apis

変数に設定。

$ REST_API_ID=...

動作確認。

$ curl http://localhost:4566/restapis/$REST_API_ID/Prod/_user_request_/hello
{ "message": "hello world", "location": "[自端末のグローバルIP]" }

OKですね。

これで、確認したいところはひととおり見れた感じです。