API Gateway + AWS Lambda + Spring Boot でCRUD APIを作成してみた
はじめに
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を実現することができました。何かの時に参考になれば幸いです。