GoogleCloudで汎用Database構築4 - kubernetes & SpringBoot & Apache Camel その1 -

GYAOのtsです。
我々のチームは、オールパブリッククラウドで、[Microservice Architecture](http://microservices.io/patterns/microservices.html)を採用した次期バックエンドを設計中です。

# 経緯

[前回](http://qiita.com/tstakano-yj/items/baaaed3199d89c5c3141)はSpringBootのアプリケーションを作成してDockerイメージ化し、GKEで立ち上げてみましたが、実際に永続化層に保存するためにWebserviceを作成する。何を使っても作成できると思うが、 個人的にApacheCamelが気に入っている。
日本ではあまり普及していないようだが、[EIP](http://www.enterpriseintegrationpatterns.com/)のimplementationであり、様々なコンポーネントを再利用して構築する。
コード量はかなり少なくて済む。今回はCamelに関してはあまり掘り下げないで、SpringBootAppに統合することに主眼を置く。

# pom.xml

pom.xmlに必要なライブラリをimportする。FullExecutableJarでパッケージングするので、これらのライブラリはすべて1つのjarに含まれる。

```xml: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>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](http://qiita.com/tstakano-yj/items/da0542c83a4d784de932)や、[Apigee](http://qiita.com/tstakano-yj/items/2a6c672990333795fcda)とつなぐ際にも便利。今後はこれがrest定義のスタンダードになりそうですね。
- camel-spring-boot
    - Apache CamelのSpringBootサポート

# Route定義

前述のように、Apache Camelは各camelコンポーネントを組み合わせてアプリケーションを構築する。
コンポーネントは主に[オフィシャルサイト](http://camel.apache.org/components.html)に集約されている。
RouteBuilderクラスのconfigureメソッドをオーバーライドしてcamelルートと呼ばれるフローを下記のように実装していく。

```java:example
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クラスを継承するかたちにすればよい。

```java:StoreagentApplication.java
@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も使用するので、下記のように仕様書に必要なコメントも一緒にも書いておく。

```java
        //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を使用してリクエストしてみる。

<img width="1081" alt="スクリーンショット 2016-11-14 22.03.01.png" src="https://qiita-image-store.s3.amazonaws.com/0/114329/60b04919-9ab5-d2c6-daf0-a2f724e35238.png">

普通に返ってくる。

また、Camel Swaggerも試してみる。
Swaggerに関しては下記を参照

- [swagger](http://swagger.io/)
- [camel-swagger](http://camel.apache.org/swagger.html)

[camel-swagger](http://camel.apache.org/swagger.html)はデフォルトで/api-docsに定義書を作成する。
この場合、http://localhost:21001/service/api-docs
にアクセスすると、定義書が返ってってくる。
これをSwaggerUIや、つなぐ先のAPIGateway等に食わせると、楽チン。

<img width="360" alt="スクリーンショット 2016-11-14 22.09.36.png" src="https://qiita-image-store.s3.amazonaws.com/0/114329/46cc1725-999b-b64d-d1c7-2d6962b083bb.png">

# 所感
これだけのコーディングで定義書付きのrest apiが立ち上がる。しかもhttpのheaderやbodyはそのままMessageのheader、bodyにバインドされるので、使い勝手がいい。

何より、実績のあるものを使いまわせるのが嬉しい。

以前はCamelBootクラスを独自に作ってFullExecutableで機動するように作っていたが、SpringBootの登場によって、一切いらなくなった。jarは一個で起動スクリプトもいらない。置いて叩くだけ。なのでDockerとも相性がいい。macだろうがWindowsだろうがIDEで開発し、maven経由でそのまま立ち上げデバッグが可能。
自分の中では鉄板構成になりつつある。
知っておくといざという時にPJを救う強力な武器になる。

私は幾度となく救われました。
基本、何がこようがSpringBoot+ApacheCamel+Thymeleaf+Commonsで短期間でどうにかなったりします。

POJOなのでアウトソーシングしやすかったりもします。
なんでもっと流行らないかなぁ。。。

# 次回

上記をもっと掘り下げてみようかと。