Apache Camel 3がリリースされたので使ってみる

この記事は[2019 新卒 エンジニア Advent Calendar 2019](https://qiita.com/advent-calendar/2019/2019-graduates)の 8 日目の記事です。

今年 4 月に新卒 Web エンジニアとして働き始め、アサインされたプロジェクトで[Apache Camel](https://camel.apache.org/)というフレームワークを利用することとなり、一から勉強しています。Webエンジニアとしては、MVCフレームワークとはかなり違う雰囲気に戸惑いましたが、使っていくうちに「ハマるアプリケーションにはハマる」フレームワークという印象を受けました。

この度 2019 年 11 月 28 日にメジャーバージョンアップとなる**Apache Camel 3.0.0**がリリースされましたので、今回はこれを簡単に紹介したいと思います。

# Apache Camel とは

そもそも Apache Camel とは、[**EIP (Enterprise Integration Pattern)**](https://www.enterpriseintegrationpatterns.com/)をベースとした「統合フレームワーク」です。詳細は省きますが、ひとまず「様々な部品の間をつないで 1 つの大きなアプリケーションを構成するためのフレームワーク」くらいの認識でよいかと思います。

Camel を触ったことがない方には、先にこちらの記事などで Camel で用いられる主要な概念について把握されることをおすすめします。 <https://qiita.com/mkyz08/items/db3083a761cb92828926>

# Getting Started

サンプルには以下の環境を用いました。Kotlin から Camel の Java DSL を使います。また、Camel は単体でも動作しますが、Spring / Spring Boot の手厚いサポートがあるので、今回はこれを使います。

- Kotlin 1.3.61
- Gradle (Kotlin DSL)
- Spring Boot 2.2.2
- Apache Camel 3.0.0
- IntelliJ IDEA CE 2019.3

## [Spring Initializr](https://start.spring.io)で雛形をつくる

Camel 2.X の依存関係は Spring Initializr で追加することができますが、本稿執筆時点では[Camel3 は未対応](https://github.com/spring-io/start.spring.io/pull/347)のようですので、後ほど別途追加します。**（2019/12/12 追記）→Spring Boot 2.2以降でDependenciesにApache Camelを追加した際にCamel 3が指定されるようになっていました。**

![start.spring.io.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/371799/55760e9b-ece5-f698-d86c-f0fa8ef9c327.png)

Generate でダウンロードされたプロジェクトの Zip アーカイブを展開し、IDE で開きます。

## Camel 3 を追加する

Spring Boot 用の Starter パッケージが用意されているので、こちらを利用します。
Camel 2.X では groupId が`org.apache.camel`でしたが、`org.apache.camel.springboot`に移されています。

```kotlin:build.gradle.kts
dependencies {
    (略)
    implementation("org.apache.camel.springboot:camel-spring-boot-starter:3.0.0")
}
```

`camel-spring-boot-starter`により、Spring アプリケーションの起動と同時に`SpringCamelContext`が生成され、Spring Bean に登録された`RouteBuilder`を検索して自動的に Route を登録してくれます。

Camel 3ではこの他のパッケージも大きく構成が変更され、`camel-core`のサイズが削減されました。これにより、起動・終了の高速化が図られています。

## Route を定義する

`RouteBuilder`を継承するクラスを作成し、`configure`メソッド内に Java DSL で Route を定義していきます。

Timer Component が 2 秒おきにメッセージを発し、それを Log Component で出力するという簡単な Route 定義です。

```kotlin:Camel3TestRouteBuilder.kt
package com.example.camel3test

import org.apache.camel.builder.RouteBuilder
import org.springframework.stereotype.Component

@Component
class Camel3TestRouteBuilder : RouteBuilder() {
    override fun configure() {
        from("timer:tick?period=2000&fixedRate=true")
            .to("log:foo")
    }
}
```

Camel を Spring Boot 上で使う場合で、SpringApplication に何も記述しない場合にはすぐにアプリケーションが終了してしまいます。これを起動したままにする設定を加えます。

```:application.properties
camel.springboot.main-run-controller = true
```

`./gradlew bootRun` で起動すると、定義通り動作していることが確認できます。

```text
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.2.RELEASE)

2019-12-08 19:39:05.694  INFO 78358 --- [           main] c.e.camel3test.Camel3TestApplicationKt   : Starting Camel3TestApplicationKt on MK-MacBook-Air.local with PID 78358 (/Users/mmck328/camel3-test/build/classes/kotlin/main started by mmck328 in /Users/mmck328/camel3-test)
2019-12-08 19:39:05.698  INFO 78358 --- [           main] c.e.camel3test.Camel3TestApplicationKt   : No active profile set, falling back to default profiles: default
2019-12-08 19:39:06.649  INFO 78358 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.apache.camel.spring.boot.CamelAutoConfiguration' of type [org.apache.camel.spring.boot.CamelAutoConfiguration$$EnhancerBySpringCGLIB$$35f35be2] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2019-12-08 19:39:07.393  INFO 78358 --- [           main] o.a.c.s.boot.SpringBootRoutesCollector   : Loading additional Camel XML routes from: classpath:camel/*.xml
2019-12-08 19:39:07.394  INFO 78358 --- [           main] o.a.c.s.boot.SpringBootRoutesCollector   : Loading additional Camel XML rests from: classpath:camel-rest/*.xml
2019-12-08 19:39:07.401  INFO 78358 --- [           main] o.a.c.s.boot.SpringBootCamelContext      : Apache Camel 3.0.0 (CamelContext: camel-1) is starting
2019-12-08 19:39:07.402  INFO 78358 --- [           main] o.a.c.management.JmxManagementStrategy   : JMX is enabled
2019-12-08 19:39:07.646  INFO 78358 --- [           main] o.a.c.s.boot.SpringBootCamelContext      : StreamCaching is not in use. If using streams then its recommended to enable stream caching. See more details at http://camel.apache.org/stream-caching.html
2019-12-08 19:39:07.683  INFO 78358 --- [           main] c.s.b.CamelSpringBootApplicationListener : Starting CamelMainRunController to ensure the main thread keeps running
2019-12-08 19:39:07.687  INFO 78358 --- [           main] o.a.c.s.boot.SpringBootCamelContext      : Route: route1 started and consuming from: timer://tick?fixedRate=true&period=2000
2019-12-08 19:39:07.687  INFO 78358 --- [inRunController] org.apache.camel.main.BaseMainSupport    : Using properties from classpath:application.properties
2019-12-08 19:39:07.695  INFO 78358 --- [           main] o.a.c.s.boot.SpringBootCamelContext      : Total 1 routes, of which 1 are started
2019-12-08 19:39:07.699  INFO 78358 --- [           main] o.a.c.s.boot.SpringBootCamelContext      : Apache Camel 3.0.0 (CamelContext: camel-1) started in 0.295 seconds
2019-12-08 19:39:07.708  INFO 78358 --- [           main] c.e.camel3test.Camel3TestApplicationKt   : Started Camel3TestApplicationKt in 2.545 seconds (JVM running for 3.005)
2019-12-08 19:39:08.711  INFO 78358 --- [ - timer://tick] foo                                      : Exchange[ExchangePattern: InOnly, BodyType: null, Body: [Body is null]]
2019-12-08 19:39:10.694  INFO 78358 --- [ - timer://tick] foo                                      : Exchange[ExchangePattern: InOnly, BodyType: null, Body: [Body is null]]
2019-12-08 19:39:12.693  INFO 78358 --- [ - timer://tick] foo                                      : Exchange[ExchangePattern: InOnly, BodyType: null, Body: [Body is null]]
2019-12-08 19:39:14.693  INFO 78358 --- [ - timer://tick] foo                                      : Exchange[ExchangePattern: InOnly, BodyType: null, Body: [Body is null]]
...
```

# Type-safe Endpoint DSL で Route を定義する

Camel 3 から Type-safe Endpoint DSL が導入されました。先ほどの Route を書き換えてみましょう。

まず、`camel-endpointdsl`の依存関係を追加します。

```kotlin:build.gradle.kts
dependencies {
    (略)
    implementation("org.apache.camel:camel-endpointdsl:3.0.0")
}
```

そして`RouteBuilder`を定義しますが、Type-safe Endpoint DSL を使うために`EndpointRouteBuilder`を継承します。従来の URL 形式の Endpoint 定義に比べて IDE のサポートも受けやすく、コードも見やすくなった印象です。

```kotlin:Camel3TestEndpointRouteBuilder.kt
package com.example.camel3test

import org.apache.camel.builder.endpoint.EndpointRouteBuilder
import org.springframework.stereotype.Component

@Component
class Camel3TestEndpointRouteBuilder : EndpointRouteBuilder() {
    override fun configure() {
        from(timer("tock").period(2000).fixedRate(true))
            .to(log("bar"))
    }
}
```

同じ動作の Route を定義できました。

```text
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.2.RELEASE)

2019-12-08 19:55:01.686  INFO 78368 --- [           main] c.e.camel3test.Camel3TestApplicationKt   : Starting Camel3TestApplicationKt on MK-MacBook-Air.local with PID 78368 (/Users/mmck328/camel3-test/build/classes/kotlin/main started by mmck328 in /Users/mmck328/camel3-test)
2019-12-08 19:55:01.690  INFO 78368 --- [           main] c.e.camel3test.Camel3TestApplicationKt   : No active profile set, falling back to default profiles: default
2019-12-08 19:55:02.630  INFO 78368 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.apache.camel.spring.boot.CamelAutoConfiguration' of type [org.apache.camel.spring.boot.CamelAutoConfiguration$$EnhancerBySpringCGLIB$$bb1bf434] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2019-12-08 19:55:03.596  INFO 78368 --- [           main] o.a.c.s.boot.SpringBootRoutesCollector   : Loading additional Camel XML routes from: classpath:camel/*.xml
2019-12-08 19:55:03.597  INFO 78368 --- [           main] o.a.c.s.boot.SpringBootRoutesCollector   : Loading additional Camel XML rests from: classpath:camel-rest/*.xml
2019-12-08 19:55:03.602  INFO 78368 --- [           main] o.a.c.s.boot.SpringBootCamelContext      : Apache Camel 3.0.0 (CamelContext: camel-1) is starting
2019-12-08 19:55:03.603  INFO 78368 --- [           main] o.a.c.management.JmxManagementStrategy   : JMX is enabled
2019-12-08 19:55:03.820  INFO 78368 --- [           main] o.a.c.s.boot.SpringBootCamelContext      : StreamCaching is not in use. If using streams then its recommended to enable stream caching. See more details at http://camel.apache.org/stream-caching.html
2019-12-08 19:55:03.858  INFO 78368 --- [           main] c.s.b.CamelSpringBootApplicationListener : Starting CamelMainRunController to ensure the main thread keeps running
2019-12-08 19:55:03.861  INFO 78368 --- [           main] o.a.c.s.boot.SpringBootCamelContext      : Route: route1 started and consuming from: timer://tock?fixedRate=true&period=2000
2019-12-08 19:55:03.863  INFO 78368 --- [inRunController] org.apache.camel.main.BaseMainSupport    : Using properties from classpath:application.properties
2019-12-08 19:55:03.865  INFO 78368 --- [           main] o.a.c.s.boot.SpringBootCamelContext      : Route: route2 started and consuming from: timer://tick?fixedRate=true&period=2000
2019-12-08 19:55:03.873  INFO 78368 --- [           main] o.a.c.s.boot.SpringBootCamelContext      : Total 2 routes, of which 2 are started
2019-12-08 19:55:03.876  INFO 78368 --- [           main] o.a.c.s.boot.SpringBootCamelContext      : Apache Camel 3.0.0 (CamelContext: camel-1) started in 0.271 seconds
2019-12-08 19:55:03.881  INFO 78368 --- [           main] c.e.camel3test.Camel3TestApplicationKt   : Started Camel3TestApplicationKt in 2.745 seconds (JVM running for 3.21)
2019-12-08 19:55:04.971  INFO 78368 --- [ - timer://tock] bar                                      : Exchange[ExchangePattern: InOnly, BodyType: null, Body: [Body is null]]
2019-12-08 19:55:04.971  INFO 78368 --- [ - timer://tick] foo                                      : Exchange[ExchangePattern: InOnly, BodyType: null, Body: [Body is null]]
2019-12-08 19:55:06.869  INFO 78368 --- [ - timer://tock] bar                                      : Exchange[ExchangePattern: InOnly, BodyType: null, Body: [Body is null]]
2019-12-08 19:55:06.869  INFO 78368 --- [ - timer://tick] foo                                      : Exchange[ExchangePattern: InOnly, BodyType: null, Body: [Body is null]]
2019-12-08 19:55:08.869  INFO 78368 --- [ - timer://tock] bar                                      : Exchange[ExchangePattern: InOnly, BodyType: null, Body: [Body is null]]
2019-12-08 19:55:08.869  INFO 78368 --- [ - timer://tick] foo                                      : Exchange[ExchangePattern: InOnly, BodyType: null, Body: [Body is null]]
...
```

# その他変更点

その他、[公式のマイグレーションガイド](https://camel.apache.org/manual/latest/camel-3-migration-guide.html)で変更点を確認できます。
個人的に印象に残った点を抜粋します。（もう少し色々使ううちに違いに気づくかもしれません）

- `CamelContext`はアプリケーション内で1つに制限されるようになり、これに伴っていくつかのAPIを変更
- 各コンポーネントのリネーム
  - `http4` -> `http`
      - 旧`http` (Apache HttpClient v3ベース)は廃止
  - `hdfs2` -> `hdfs`
  - `mongodb3` -> `mongodb`
  - `netty4` -> `netty`
  - ...
- Hystrix EIPの変更 `.hystrix()` -> `.circuitBraker()`
  - [Resillient4Jなどの別の実装を使えるようにするため](https://issues.apache.org/jira/browse/CAMEL-14182)

# まとめ

以上、Apache Camel 3について紹介しました。全体的に洗練された印象で、Type-safe Endpoint DSLはHTTP Componentなど定義が長くなりがちなComponentではより効果を発揮しそうです。~~むしろこの記事、Type-safe Endpoint DSLを試しただけなのでは？~~