GYAOのtsです。
我々のチームは、オールパブリッククラウドで、Microservice Architectureを採用した次期バックエンドを設計中です。
経緯
前回はSpringBootのアプリケーションを作成してDockerイメージ化し、GKEで立ち上げてみましたが、実際に永続化層に保存するためにWebserviceを作成する。何を使っても作成できると思うが、 個人的にApacheCamelが気に入っている。
日本ではあまり普及していないようだが、EIPのimplementationであり、様々なコンポーネントを再利用して構築する。
コード量はかなり少なくて済む。今回はCamelに関してはあまり掘り下げないで、SpringBootAppに統合することに主眼を置く。
pom.xml
pom.xmlに必要なライブラリをimportする。FullExecutableJarでパッケージングするので、これらのライブラリはすべて1つのjarに含まれる。
<?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>jp.co.yahoo.zeolite.agent</groupId>
<artifactId>storeagent</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>storeagent</name>
<description>Spring Boot Application</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<camel-ver>2.17.1</camel-ver>
</properties>
<dependencies>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Apache Camel -->
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-core</artifactId>
<version>${camel-ver}</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-restlet</artifactId>
<version>${camel-ver}</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-spring-boot</artifactId>
<version>${camel-ver}</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-swagger-java</artifactId>
<version>${camel-ver}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>1.3.6.RELEASE</version>
<configuration>
<mainClass>jp.co.yahoo.zeolite.agent.StoreagentApplication</mainClass>
<layout>WAR</layout>
<executable>true</executable>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
追加したのは主に下記のライブラリ。
- spring-boot-starter-jetty
- 組み込みtomcat→組み込みjettyにコンテナを切り替え。
- spring-boot-starter-thymeleaf
- 可視化のためのtoolも作ろうと思うので、テンプレートエンジンにthymeleaf(タイムリーフ)
- camel-core
- Apache Camelのコアライブラリ
- camel-restlet
- restAPI作成に使用します。他にいくつかCamelコンポーネントがあるが、今回はこれを使用。
- camel-swagger-java
- api-docを作ってくれるのが嬉しい。
- API Gatewayや、Apigeeとつなぐ際にも便利。今後はこれがrest定義のスタンダードになりそうですね。
- camel-spring-boot
- Apache CamelのSpringBootサポート
Route定義
前述のように、Apache Camelは各camelコンポーネントを組み合わせてアプリケーションを構築する。
コンポーネントは主にオフィシャルサイトに集約されている。
RouteBuilderクラスのconfigureメソッドをオーバーライドしてcamelルートと呼ばれるフローを下記のように実装していく。
from("jetty:https://0.0.0.0/myapp/").to("bean:myBean?method=methodName");
例えばこの場合だと、localhost:80/myappでアクセスできるwebserviceを立ち上げることができる(ようは組み込みjettyが立ち上がるだけだが)。
header情報やbodyの情報はMessageにバインドされ、ほぼそのままto()で呼び出すコンポーネントに渡される。(この場合はbeanコンポーネントを使用しているので、myBeanでcontextに登録されたインスタンスのmethodNameメソッドに渡される)
今回はcamel-spring-bootを使用する、RouteBuilderクラスを継承した、FatJarRouterクラスが用意されている。
SpringBootアプリケーションの起動クラスがこのFatJarRouterクラスを継承するかたちにすればよい。
@SpringBootApplication
@ComponentScan("jp.co.yahoo.zeolite")
public class StoreagentApplication extends FatJarRouter {
public static void main(String[] args) {
SpringApplication.run(StoreagentApplication.class, args);
}
@Override
public void configure() throws Exception {
//handling Exceptions.
onException(Exception.class)
.handled(true)
.setBody(exceptionMessage().prepend("{\"error\":\"").append("\"}"))
.convertBodyTo(String.class)
.log(LoggingLevel.ERROR, "${body}")
.to("direct:setWebserviceResponseHeaders")
.setHeader(Exchange.HTTP_RESPONSE_CODE, constant(500));
errorHandler(deadLetterChannel("direct:deadlog").maximumRedeliveries(0));
from("direct:deadlog").routeId("DEAD_LOG").log(LoggingLevel.ERROR, "${body}");
from("direct:setWebserviceResponseHeaders")
.routeId("SET_WEB_HEADERS")
.setHeader(Exchange.CONTENT_TYPE,
constant("application/json; charset=utf-8"));
//Rest Configurations.
restConfiguration().component("restlet")
.enableCORS(true)
.apiContextPath("/api-docs")
.apiProperty("api.title", "Zeolite StoreAgent APIs")
.apiProperty("api.version", "1.0.0")
.apiProperty("cors", "true")
.contextPath("/service").host(InetAddress.getLocalHost().getHostName()).port(21001)
.dataFormatProperty("prettyPrint", "true")
.bindingMode(RestBindingMode.off)
.componentProperty("disableStreamCache", "true");
//Rest API
rest("/upsert")
.description("Upsert API. 対象ItemをUpsertします。")
.produces("application/json").consumes("application/json")
.post().description("upsert the item.")
.param().name("itemKey").type(RestParamType.header).description("The key of an item.").dataType("string").endParam()
.param().name("item").type(RestParamType.body).description("The item to store.").dataType("string").endParam()
.responseMessage().header("http Status code").endResponseHeader().endResponseMessage()
.to("direct:upsert");
from("direct:upsert")
.setBody(constant("{\"message\" : \"OK!\"}"))
.to("mock:upsert");
}
}
今回はcamel-swaggerも使用するので、下記のように仕様書に必要なコメントも一緒にも書いておく。
//Rest API
rest("/upsert")
.description("Upsert API. 対象ItemをUpsertします。")
.produces("application/json").consumes("application/json")
.post().description("upsert the item.")
.param().name("itemKey").type(RestParamType.header).description("The key of an item.").dataType("string").endParam()
.param().name("item").type(RestParamType.body).description("The item to store.").dataType("string").endParam()
.responseMessage().header("http Status code").endResponseHeader().endResponseMessage()
.to("direct:upsert");
あとはmaven経由で立ち上げるだけ。
Postmanを使用してリクエストしてみる。
普通に返ってくる。
また、Camel Swaggerも試してみる。
Swaggerに関しては下記を参照
camel-swaggerはデフォルトで/api-docsに定義書を作成する。
この場合、http://localhost:21001/service/api-docs
にアクセスすると、定義書が返ってってくる。
これをSwaggerUIや、つなぐ先のAPIGateway等に食わせると、楽チン。
所感
これだけのコーディングで定義書付きのrest apiが立ち上がる。しかもhttpのheaderやbodyはそのままMessageのheader、bodyにバインドされるので、使い勝手がいい。
何より、実績のあるものを使いまわせるのが嬉しい。
以前はCamelBootクラスを独自に作ってFullExecutableで機動するように作っていたが、SpringBootの登場によって、一切いらなくなった。jarは一個で起動スクリプトもいらない。置いて叩くだけ。なのでDockerとも相性がいい。macだろうがWindowsだろうがIDEで開発し、maven経由でそのまま立ち上げデバッグが可能。
自分の中では鉄板構成になりつつある。
知っておくといざという時にPJを救う強力な武器になる。
私は幾度となく救われました。
基本、何がこようがSpringBoot+ApacheCamel+Thymeleaf+Commonsで短期間でどうにかなったりします。
POJOなのでアウトソーシングしやすかったりもします。
なんでもっと流行らないかなぁ。。。
次回
上記をもっと掘り下げてみようかと。