お題
WebAPIを楽に開発しようと思った時、素のサーブレットを使うのはしんどいので、Cloud Endpoints Frameworksを導入する。
楽なポイントとしては、↓
- Mavenコマンド一発でスケルトン(やチュートリアル)プロジェクトの生成が可能
- アノテーションベースでAPI定義が可能
- APIメソッドの引数をオブジェクトで受け取るようにすると勝手にJSON構造をマッピングしてくれる
- APIキーによるアクセス制限がアノテーションの属性一発で可能
- JSON Web TokenやFirebase Authenticationといったユーザー認証の仕組みもアノテーションベース
GAE試行Index
- GAE/Java8試行(その0:「App Engineについて」)
- GAE/Java8試行(その1:「Java8でWebアプリ作ってデプロイ」)
- GAE/Java8試行(その2:「Javaアプリ解説」)
- GAE/Java8試行(その3:「Javaアプリテストコード解説」)
- GAE/Java8試行(その4:「Datastoreへのアクセスロジック」)
- GAE/Java8試行(その5:「ローカル開発用コンソール」)
- GAE/Java8試行(その6:「デプロイ失敗」)
- GAE/Java8試行(その7:「ウォームアップリクエスト(ServletContextListener使用版)」)
開発環境
# OS
$ cat /etc/os-release
NAME="Ubuntu"
VERSION="17.10 (Artful Aardvark)"
# Java
$ java -version
java version "1.8.0_181"
Java(TM) SE Runtime Environment (build 1.8.0_181-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.181-b13, mixed mode)
# IDE
みんな大好きIntelliJ IDEA
実践
■Mavenプロジェクト生成
今回は、一からCloud Endpoints Frameworksを試すので最小のスケルトンでMavenプロジェクトを生成。
(groupId, artifactId, packageすべて「com.example.sky0621.fs」を入力)
$ mvn archetype:generate -Dappengine-version=1.9.67 -Djava8=true -Dfilter=com.google.appengine.archetypes:
[INFO] Scanning for projects...
・・・
[INFO] No archetype defined. Using maven-archetype-quickstart (org.apache.maven.archetypes:maven-archetype-quickstart:1.0)
Choose archetype:
1: remote -> com.google.appengine.archetypes:appengine-flexible-archetype (A basic Java application with Google App Engine flexible.)
2: remote -> com.google.appengine.archetypes:appengine-skeleton-archetype (A skeleton application with Google App Engine)
3: remote -> com.google.appengine.archetypes:appengine-standard-archetype (A basic Java application with Google App Engine Standard)
4: remote -> com.google.appengine.archetypes:endpoints-skeleton-archetype (A skeleton project using Cloud Endpoints Frameworks with Google App Engine Standard)
5: remote -> com.google.appengine.archetypes:guestbook-archetype (A guestbook application with Google App Engine)
6: remote -> com.google.appengine.archetypes:hello-endpoints-archetype (A simple starter application using Cloud Endpoints Frameworks with Google App Engine Standard)
7: remote -> com.google.appengine.archetypes:skeleton-archetype (Archetype with a README about Google App Engine archetypes)
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): : 4
すると、こんな感じでプロジェクトが出来上がる。
$ tree
.
├── LICENSE
├── README.md
├── build.gradle
├── pom.xml
└── src
└── main
├── java
│ └── com
│ └── example
│ └── sky0621
│ └── fs
│ └── YourFirstAPI.java
└── webapp
└── WEB-INF
├── appengine-web.xml
├── logging.properties
└── web.xml
唯一生成されたJavaクラス(YourFirstAPI.java
)の中身は「@Api
アノテーション」とクラス定義のみ。
@Api(name = "skeleton-api",
version = "v1")
public class YourFirstAPI {
}
■CRUD-API作成
設計
というほどの大層なものではなく、単にAPIとして扱うリソースを決め、機能を定義する。
リソース
書籍 - book
論理名 | 物理名 | 型 |
---|---|---|
ASIN | asin | string |
書籍名 | bookName | string |
著者名 | authors | string[] |
価格 | price | int |
機能
機能名 | HTTPメソッド | URL | リクエスト | レスポンス |
---|---|---|---|---|
登録 | POST | {hostname}/_ah/api/fs/v1/books | bookオブジェクト | none |
参照 | GET | {hostname}/_ah/api/fs/v1/books/{asin} | none | bookオブジェクト |
一覧 | GET | {hostname}/_ah/api/fs/v1/books | none | bookオブジェクトリスト |
更新 | PUT | {hostname}/_ah/api/fs/v1/books/{asin} | bookオブジェクト | none |
削除 | DELETE | {hostname}/_ah/api/fs/v1/books/{asin} | none | none |
実装
登録時や更新時のリクエストパラメータ並びに一覧取得時のレスポンスに用いる構造。
public class Book {
private String asin;
private String bookName;
private List<String> authors;
private int price;
public Book() {
}
public Book(String asin, String bookName, List<String> authors, int price) {
this.asin = asin;
this.bookName = bookName;
this.authors = authors;
this.price = price;
}
public String getAsin() {
return asin;
}
public void setAsin(String asin) {
this.asin = asin;
}
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
public List<String> getAuthors() {
return authors;
}
public void setAuthors(List<String> authors) {
this.authors = authors;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
}
あと、とりあえず永続化はメモリにしておくので、そのための器を適当に作っておく。
public class BookSaver {
private static List<Book> bookList = new ArrayList<>();
public static void add(Book book) {
bookList.add(book);
}
public static Book get(String asin) {
for (Book b : bookList) {
if (b.getAsin().equals(asin)) {
return b;
}
}
return null;
}
public static List<Book> list() {
return bookList;
}
public static void update(Book book) {
List<Book> bl = new ArrayList<>();
for (Book b : bookList) {
if (b.getAsin().equals(book.getAsin())) {
bl.add(book);
} else {
bl.add(b);
}
}
bookList = bl;
}
public static void delete(String asin) {
List<Book> bl = new ArrayList<>();
for (Book b : bookList) {
if (!b.getAsin().equals(asin)) {
bl.add(b);
}
}
bookList = bl;
}
}
最後に、APIメソッドを定義。
@Api(name = "fs",
version = "v1")
public class YourFirstAPI {
@ApiMethod(name = "books", path = "books", httpMethod = ApiMethod.HttpMethod.POST)
public void addBook(Book book) {
BookSaver.add(book);
}
@ApiMethod(name = "books", path = "books/{asin}", httpMethod = ApiMethod.HttpMethod.GET)
public Book getBook(@Named("asin") String asin) {
return BookSaver.get(asin);
}
@ApiMethod(name = "books", path = "books", httpMethod = ApiMethod.HttpMethod.GET)
public List<Book> listBooks() {
return BookSaver.list();
}
@ApiMethod(name = "books", path = "books/{asin}", httpMethod = ApiMethod.HttpMethod.PUT)
public void updateBook(@Named("asin") String asin, Book book) {
BookSaver.update(book);
}
@ApiMethod(name = "books", path = "books/{asin}", httpMethod = ApiMethod.HttpMethod.DELETE)
public void deleteBook(@Named("asin") String asin) {
BookSaver.delete(asin);
}
}
これだけ。
動作確認
まずビルド。
$ mvn clean package
続いて
$ mvn endpoints-framework:openApiDocs
さらに、ローカルでWebAPIサーバ起動。
$ mvn appengine:run
試しに、↓の2つを「http://localhost:8080/_ah/api/fs/v1/books
」にPOSTする。
{
"asin": "4873117585",
"bookName": "ゼロから作るDeep Learning",
"authors": ["斎藤 康毅"],
"price": 3672
}
{
"asin": "B07J6FP6NQ",
"bookName": "Vue.js入門",
"authors": ["川口 和也", "喜多 啓介", "野田 陽平", "手島 拓也", "片山 真也"],
"price": 3650
}
「http://localhost:8080/_ah/api/fs/v1/books
」にGETすると、↓のような結果が返る。
{
"items": [
{
"asin": "4873117585",
"bookName": "ゼロから作るDeep Learning",
"authors": [
"斎藤 康毅"
],
"price": 3672
},
{
"asin": "B07J6FP6NQ",
"bookName": "Vue.js入門",
"authors": [
"川口 和也",
"喜多 啓介",
"野田 陽平",
"手島 拓也",
"片山 真也"
],
"price": 3650
}
]
}
「http://localhost:8080/_ah/api/fs/v1/books/4873117585
」にGETすると、指定の1件が返る。
{
"asin": "4873117585",
"bookName": "ゼロから作るDeep Learning",
"authors": [
"斎藤 康毅"
],
"price": 3672
}
「http://localhost:8080/_ah/api/fs/v1/books/4873117585
」にDELETEすると、指定の1件が削除される。
再度「http://localhost:8080/_ah/api/fs/v1/books
」にGETすると、DELETEした書籍以外の登録情報が返る。
{
"items": [
{
"asin": "B07J6FP6NQ",
"bookName": "Vue.js入門",
"authors": [
"川口 和也",
"喜多 啓介",
"野田 陽平",
"手島 拓也",
"片山 真也"
],
"price": 3650
}
]
}
更新は省略。
参考
まとめ
今回のは、あくまでCloud Endpoints Frameworksのさわりでしかないけど、WebAPI作るにはデフォルトで用いてもいいくらい楽。
今後、APIキーや認証といった、より便利さが実感できる機能も試してみよう。