この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
API Gateway + AWS LambdaでデータのCRUDを行うAPIを作成したいという要件は、それなりにあるかと思います。今回はAWS LambdaをJavaで実装したいケースを想定し、Spring Bootを使ってみました。
CRUDを行うデータベースはDynamoDBとし、以下の記事と同じ方法を採っております。
Spring Data DynamoDBでSpringからDynamoDBにアクセスする
実装について
では実装についてです。まずはデータを登録するDynamoDBのテーブルを作成し、その後でソースについて見ていきたいと思います。
1.DynamoDBのテーブル
AWSコンソール上で以下のようにテーブルを作成しました。これは以前の記事と同じです。
- テーブル名 ・・・ SpringUser
- プライマリキー ・・・ id
2.build.gradle
Gradleを使用し、build.gradleに以下のように記述しました。
buildscript {
ext {
springBootVersion = '1.3.4.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'spring-boot'
jar {
baseName = 'LambdaSpringBootApiSample'
version = '0.0.1-SNAPSHOT'
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile('org.springframework.boot:spring-boot-starter')
compile('com.amazonaws:aws-lambda-java-core:1.1.0')
compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.7.4'
compile('com.google.code.gson:gson:2.6.2')
compile group: 'com.github.derjust', name: 'spring-data-dynamodb', version: '4.2.3'
compile('org.projectlombok:lombok:1.16.8')
testCompile('org.springframework.boot:spring-boot-starter-test')
}
eclipse {
classpath {
containers.remove('org.eclipse.jdt.launching.JRE_CONTAINER')
containers 'org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8'
}
}
3.Postと登録
API GatewayによるPostメソッド、およびPostされたデータを登録するLambda Functionについてです。
Postメソッド
API GatewayのPostメソッドは以下の様に設定しました。「LambdaSpringBootApiSamplePost」というLambda Functionを呼び出しています。
PostHandlerとLambda
API Gatewayから呼び出されるLambda Functionのソースです。エントリーポイントとなる「handleRequest」メソッド内で、ApplicationContextを作成してSpringアプリケーションを起動しています。
データを登録する主なロジックは「run」メソッドに記述しており、API Gatewayから取得した値を読み込み(45〜51行目)、DynamoDBに登録しています(52・52行目)。
PostHandler.java
package com.example;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.example.repositories.SpringUserRepository;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import lombok.Setter;
@SpringBootApplication
public class PostHandler implements RequestHandler<Object, Object>{
@Autowired
private SpringUserRepository repository;
@Setter
private Object input;
@Setter
private Context context;
@Override
public Object handleRequest(Object input, Context context) {
String args[] = new String[0];
try (ConfigurableApplicationContext ctx = SpringApplication.run(PostHandler.class, args)) {
PostHandler app = ctx.getBean(PostHandler.class);
app.setInput(input);
app.setContext(context);
app.run(args);
return "success.";
} catch (Exception e) {
e.printStackTrace();
context.getLogger().log("error.\n");
return "error.";
}
}
public void run(String... args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
Gson gson = new Gson();
String json = gson.toJson(input);
SpringUser inputUser = mapper.readValue(json, SpringUser.class);
SpringUser registerUser = new SpringUser(inputUser.getFirstName(), inputUser.getLastName());
repository.save(registerUser);
}
}
上記のソースを含めてjarを作成し、Lambda Functionとして登録します。
4.Putと更新
API GatewayによるPutメソッド、およびPutされたデータを更新するLambda Functionについてです。以降は上記のPostとあまり変わりないので、スクリーンショットとソースの表示のみで詳細な説明は省きます。
Putメソッド
PutHandlerとLambda
データを更新するLambda Functionのソースです。
PutHandler.java
package com.example;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.example.repositories.SpringUserRepository;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import lombok.Setter;
@SpringBootApplication
public class PutHandler implements RequestHandler<Object, Object>{
@Autowired
private SpringUserRepository repository;
@Setter
private Object input;
@Setter
private Context context;
@Override
public Object handleRequest(Object input, Context context) {
String args[] = new String[0];
try (ConfigurableApplicationContext ctx = SpringApplication.run(PutHandler.class, args)) {
PutHandler app = ctx.getBean(PutHandler.class);
app.setInput(input);
app.setContext(context);
app.run(args);
return "success.";
} catch (Exception e) {
e.printStackTrace();
context.getLogger().log("error.\n");
return "error.";
}
}
public void run(String... args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
Gson gson = new Gson();
String json = gson.toJson(input);
SpringUser inputUser = mapper.readValue(json, SpringUser.class);
SpringUser updateUser = repository.findOne(inputUser.getId());
updateUser.setFirstName(inputUser.getFirstName());
updateUser.setLastName(inputUser.getLastName());
repository.save(updateUser);
}
}
Lambda Functionは以下となります。
5.Getと取得
API GatewayによるGetメソッド、およびデータを取得するLambda Functionについてです。
Getメソッド
GetHandlerとLambda
データを取得するLambda Functionのソースです。取得したデータを返却するところが、上記のソースとは異なっています。
GetHandler.java
package com.example;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.example.repositories.SpringUserRepository;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Setter;
@SpringBootApplication
public class GetHandler implements RequestHandler<Object, Object>{
@Autowired
private SpringUserRepository repository;
@Setter
private Object input;
@Setter
private Context context;
@Override
public Object handleRequest(Object input, Context context) {
String args[] = new String[0];
try (ConfigurableApplicationContext ctx = SpringApplication.run(GetHandler.class, args)) {
GetHandler app = ctx.getBean(GetHandler.class);
app.setInput(input);
app.setContext(context);
String result = app.run(args);
return result;
} catch (Exception e) {
e.printStackTrace();
context.getLogger().log("error.\n");
return "error.";
}
}
public String run(String... args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
Iterable<SpringUser> users = repository.findAll();
String json = mapper.writeValueAsString(users);
return json;
}
}
4.Deleteと削除
API GatewayによるDeleteメソッド、およびデータを削除するLambda Functionについてです。
Deleteメソッド
DeleteHandlerとLambda
データを削除するLambda Functionのソースです。
DeleteHandler.java
package com.example;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.example.repositories.SpringUserRepository;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import lombok.Setter;
@SpringBootApplication
public class DeleteHandler implements RequestHandler<Object, Object>{
@Autowired
private SpringUserRepository repository;
@Setter
private Object input;
@Setter
private Context context;
@Override
public Object handleRequest(Object input, Context context) {
String args[] = new String[0];
try (ConfigurableApplicationContext ctx = SpringApplication.run(DeleteHandler.class, args)) {
DeleteHandler app = ctx.getBean(DeleteHandler.class);
app.setInput(input);
app.setContext(context);
app.run(args);
return "success.";
} catch (Exception e) {
e.printStackTrace();
context.getLogger().log("error.\n");
return "error.";
}
}
public void run(String... args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
Gson gson = new Gson();
String json = gson.toJson(input);
SpringUser inputUser = mapper.readValue(json, SpringUser.class);
SpringUser deleteUser = repository.findOne(inputUser.getId());
repository.delete(deleteUser);
}
}
6.DynamoDB周りの共通処理
最後にDynamoDBにCRUDするための共通処理についてです。
DynamoDBConfig
DynamoDBへ接続するためのクラスです。以前の記事と大体同じなのですが、クレデンシャルを実行環境から取得するよう「EnvironmentVariableCredentialsProvider」メソッドを使用するように変更しています。
DynamoDBConfig.java
package com.example;
import org.socialsignin.spring.data.dynamodb.repository.config.EnableDynamoDBRepositories;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.EnvironmentVariableCredentialsProvider;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient;
@Configuration
@EnableDynamoDBRepositories(basePackages = "com.example.repositories")
public class DynamoDBConfig {
@Value("${amazon.dynamodb.endpoint}")
private String amazonDynamoDBEndpoint;
@Bean
public AmazonDynamoDB amazonDynamoDB() {
AmazonDynamoDB amazonDynamoDB = new AmazonDynamoDBClient(amazonAWSCredentials());
if (!StringUtils.isEmpty(amazonDynamoDBEndpoint)) {
amazonDynamoDB.setEndpoint(amazonDynamoDBEndpoint);
}
return amazonDynamoDB;
}
@Bean
public AWSCredentials amazonAWSCredentials() {
return new EnvironmentVariableCredentialsProvider().getCredentials();
}
}
またEndpointを定義ファイルから取得しており、その定義ファイルも載せておきます。
application.yml
amazon:
dynamodb:
endpoint: https://dynamodb.ap-northeast-1.amazonaws.com
SpringUser
CRUDを行う対象のデータを表す、SpringUserクラスです。
SpringUser.java
package com.example;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAutoGeneratedKey;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable;
@DynamoDBTable(tableName = "SpringUser")
public class SpringUser {
private String id;
private String firstName;
private String lastName;
public SpringUser(){
}
public SpringUser(String firstName, String lastName){
this.firstName = firstName;
this.lastName = lastName;
}
@DynamoDBHashKey
@DynamoDBAutoGeneratedKey
public String getId()
{
return id;
}
@DynamoDBAttribute
public String getFirstName()
{
return firstName;
}
@DynamoDBAttribute
public String getLastName()
{
return lastName;
}
public void setId(String id){
this.id = id;
}
public void setFirstName(String firstName){
this.firstName = firstName;
}
public void setLastName(String lastName){
this.lastName = lastName;
}
}
SpringUserRepository
データのCRUDを実際に行うリポジトリクラスです。
SpringUserRepository.java
package com.example.repositories;
import org.socialsignin.spring.data.dynamodb.repository.EnableScan;
import org.springframework.data.repository.CrudRepository;
import com.example.SpringUser;
@EnableScan
public interface SpringUserRepository extends CrudRepository<SpringUser, String> {
}
動作確認
では実際にAPI Gatewayを叩いて動作を確認してみます。
登録
API GatewayにPostして、データを登録してみます。
$ curl https://xxxxxx.execute-api.ap-northeast-1.amazonaws.com/test -v -X POST -d "{\"firstName\":\"Ichiro\", \"lastName\":\"Test\"}"
(中略)
< HTTP/1.1 200 OK
(中略)
$ curl https://xxxxxx.execute-api.ap-northeast-1.amazonaws.com/test -v -X POST -d "{\"firstName\":\"Jiro\", \"lastName\":\"Test\"}"
(中略)
< HTTP/1.1 200 OK
DynamoDB
更新
Putにて上記DynamoDBの最初のデータを更新してみます。
$ curl https://xxxxxx.execute-api.ap-northeast-1.amazonaws.com/test -v -X PUT -d "{\"id\":\"f8fd8d22-dee5-422b-a587-fdc11a61fe85\", \"firstName\":\"Seiya\", \"lastName\":\"Test\"}"
(中略)
< HTTP/1.1 200 OK
DynamoDB
取得
Getにてデータを取得してみます。
$ curl https://xxxxxx.execute-api.ap-northeast-1.amazonaws.com/test -v -X GET
(中略)
"[{\"id\":\"f8fd8d22-dee5-422b-a587-fdc11a61fe85\",\"firstName\":\"Seiya\",\"lastName\":\"Test\"},{\"id\":\"03faef7c-5547-4ded-8d1a-9c97923b2eda\",\"firstName\":\"Ichiro\",\"lastName\":\"Test\"}]"
削除
Deleteにてデータを削除してみます。
$ curl https://xxxxxx.execute-api.ap-northeast-1.amazonaws.com/test -v -X DELETE -d "{\"id\":\"f8fd8d22-dee5-422b-a587-fdc11a61fe85\"}"
(中略)
< HTTP/1.1 200 OK
DynamoDB
まとめ
簡単なサンプルですが、Javaを使ってAPI Gateway + AWS Lambda でAPIを実現することができました。何かの時に参考になれば幸いです。