Why camel?
- データ連携とかトンネルシステム作るのに楽ちんそう。
camelって何?
- Apache謹製のメッセージング(データ連携)F/W
- システム間のデータ連携などで役立つOSS
- 様々なプロトコルに対応(http,ftp,activemqを始めとして、FIXやらFacebookやら何やら)
- 日本ではマイナー
- Tomcatと連携したりしてServletの代わりにもなるっぽい
- DSLで処理を記述して、シンプルなコードで処理を数珠つなぎに書ける
- DSLはJava、SpringのXML、Scala、Groovyなど様々なDSLが用意されている
- 一番使われているのはDSLはXMLっぽい。
マイナーな理由(推測)
- Messagingってエンプラでの需要が多く、Rookie的なCamelは採用されにくい。
- 日本語ドキュメントが圧倒的に足りない。(日本語の本はない)
- 英語ドキュメントも少ないけど、本家が割りと書いてある。(でもJavaやSpringDSLばかり)
XMLが一番使われている理由(推測)
- XMLを生成出来るフォーマットを仕様書として自動生成
なんて事をしているからでは無いだろか。
- HTTPのプロトコルでデータが飛んでくる
- データの書式はJSON
- 出力形式はCSV
- 出力先はファイル
みたいに書いておけば、DSLが生成されて実装工程なしに、データ基盤連携が作成される。
ということをやっているのでは無いかな、と。
ざっくりCamelの仕組み
箱(Exchange
)をベルトコンベア(DSLで定義されたRoute
)に乗せて、機械的な作業(DSLで定義されたコンポーネント
)を実施する。
機械では難しい場合は職人(Processor
)が作業をする。
そんな事を処理をすることが得意。
- 難しい事は苦手
- でも職人(
Processor
)を呼ぶことで解決出来る。 - けど、あまり難しいことをするとベルトコンベアが複雑になる。
そんな感じ。
一つのベルトコンベアは出来る限りシンプルに細かくする。
そして箱の中身を常に整理して実装していくことが大切。
Why scala?
そりゃ・・・
- 文献の少ない事にチャレンジしたい。
検証環境等
- Max OS Yosemite
- java 1.7.0_25
- camel 2.14.0
(2015/03/11に 2.15.0がReleaseされた。scala DSLではRouteBuilderが非推奨になり、ScalaRouteBuilerが推奨されている。) - scala 2.10.4 (or scala2.11.6)どちらがイマイチ分からない
- Scala IDE 3.0.3
- IntelliJ(CommunityEcition) 14.0.3
scala の versionは 2.10.x or 2.11.x?
訂正
scala2.11でOK。
scalaから下記の取消で吐いていたWARNログのクラスは別パッケージに含まれるようになった。
なので、後述するbuild.sbtで追加になったパッケージをDependenciesに追加する。
camel-scalaのページの最下部を見ると、camel-scala_2.11以降を使う場合はscalaのVersionは2.10系が必須(Better?)らしい。
Starting from version 2.11 Camel Scala DSL is compiled against Scala 2.10. If you plan to use Scala 2.9 with Camel 2.11, add the following to your pom.xml file instead.
[pomの内容]
Support for Scala 2.9 is deprecated and will be removed starting from the Camel 2.12 and 3.0.
試しにscala2.11.5でcamel-scala2.14.1を使ったら動いたけど、WARNレベルの例外を吐いてた。
12:19:37.488 [main] WARN o.a.c.i.c.AnnotationTypeConverterLoader - Ignoring converter type: org.apache.camel.scala.converter.ScalaTypeConverter as a dependent class could not be found: java.lang.NoClassDefFoundError: scala/xml/Elem
java.lang.NoClassDefFoundError: scala/xml/Elem
at java.lang.Class.getDeclaredMethods0(Native Method) ~[na:1.7.0_25]
at java.lang.Class.privateGetDeclaredMethods(Class.java:2521) ~[na:1.7.0_25]
- 以下略 -
ただ、本家のRelease Notesを見ていると
camel-scala upgraded from Scala 2.10.x to 2.11.x
こんな一文が発見される。
吐かれるログもWARNレベルなので、2.11.xを使った方が無難?
環境構築 (scala環境構築が必要な場合)
IntelliJ IDEA
多少古いけど、こちらが非常に分かりやすい。
【図解】Scala 2.10 + IntelliJ IDEA 12 で「Hello World」する
最新(2015/03現在)はScalaプラグインはSBTプラグインが同梱されているので、それを飛ばすくらいだった気がする。
ScalaIDE (Eclipse plugin)
eclipse系の場合はScalaIDE for eclipseからDL。
先述したように、camel-scalaを動かす時のscalaのバージョンは2.10系の方が良い。
でも、現時点でのScalaIDEの最新は4系となり、基本はscala2.11系用になる。
scala2.10も動かせるけど、ちょっと設定変更が必要。
Camleインストール(プロジェクトのセットアップ)
特に何かをインストールする作業は無く、ビルドファイルにcamelのモジュールを組み込むだけ。
後はヒルドファイルが依存性を解決してかき集めてくれる。
ビルドファイル
sbtを使う
name := "experiment"
version := "1.0"
scalaVersion := "2.11.6"
val camelVersion = "2.15.0"
libraryDependencies ++= Seq(
"org.scala-lang.modules" %% "scala-xml" % "1.0.3",
"org.apache.camel" % "camel-core" % camelVersion,
"org.apache.camel" % "camel-scala" % camelVersion,
"org.apache.activemq" % "activemq-core" % "5.7.0",
"ch.qos.logback" % "logback-core" % "1.1.2",
"ch.qos.logback" % "logback-classic" % "1.1.2"
)
なお、camelのMinimum的には不要だが、scala2.11的にはscala-xml
以外にも、以下の2つのjarを追加したほうが良い。
+ "org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.3",
"org.scala-lang.modules" %% "scala-xml" % "1.0.3",
+ "org.scala-lang.plugins" %% "scala-continuations-library" % "1.0.2",
mavenを使う
ScalaIDEの場合、プロジェクト新規作成時にMavenプロジェクトを選んで
archetypeArtifactId
でcamel-archetype-scala
を選べば作れる。
必要なdependenciesは勝手に追加してくれる。
新規作成ウィザードでscalaのVersionを選べるので、2.10系にするのをお忘れなく。
以下のコマンドでもcamel-scalaのmavenプロジェクトが出来る。
mvn archetype:generate -DgroupId=jp.dem3umegumi.experiment \
-DartifactId=camel-experiment \
-Dversion=1.0.0-SNAPSHOT \
-DarchetypeGroupId=org.apache.camel.archetypes \
-DarchetypeArtifactId=camel-archetype-scala \
-DarchetypeVersion=2.15.0
Tips
MyRouteBuilder.scalaとMyRouteMain.scalaというファイルが生成されるので参考にしても良い。
(でも、あまり参考にならないかも知れない)
Camelを使う
40秒でCamelの凄さを味あう
Buildファイルを作成して、プロジェクトの依存ライブラリにcamel-coreとかcamel-scalaが追加されたら、いよいよ動かしてみる。
さんぷるとして、以下のようなプログラムをCamelで書いてみる。
- 指定ディレクトリを監視する。
- 指定ファイルが存在したらRouteを起動する。
- ファイルの中身を出力する。
- 中身を別のファイルに書き出す。
package jp.den3umegumi.experimental.camel.tutorial
import org.apache.camel.main.Main
import org.apache.camel.scala.dsl.builder.{RouteBuilder, RouteBuilderSupport}
object FileMove extends App with RouteBuilderSupport {
val main = new Main
main.addRouteBuilder(new RouteBuilder {
"file://work/inbound?fileName=foo.txt" ==> {
log("find file.")
log("${body}")
-->("file://work/outbound?fileName=bar.txt")
}
})
main.run
}
上記のコードをコピペして実行する。
- packageは適宜変更(scalaなので、してもしなくても良い)。
-
work/inbound
とwork/outbound
は監視するディレクトリを記述
絶対パスで書く場合はfile:///usr/local/tmp/
とか書く。※file://
までは固定。 - 実行したらinboudに適当なファイルを置く。
なんの変哲も無いファイル移動だけど、 連携元システムからファイル連携して、ゴニョゴニョする! みたいなプログラムが簡単に書けそう!と思いませんか?
つまらないサンプルを使った説明
Camelが動いた程度しか分からない、何も面白くは無いけどサンプルコード。
とりあえずログ出力くらいする。
先ほどとは違い、実行クラスとDSL定義を2クラスに分けて作成。
package jp.den3umegumi.experimental.camel.route
import org.apache.camel.scala.dsl.builder.RouteBuilder
/**
* サンプルルート
*/
class SampleRouteBuilder extends RouteBuilder {
/**
* timerトリガーのRoute
*/
"timer:foo-timer?repeatCount=1" ==> {
log("foo timer start.")
-->("direct:hello-world")
log("foo timer end.")
}
/**
* directトリガーのRoute
*/
"direct:hello-world" ==> {
delay(100)
log("Hello World!")
}
}
実行用のクラスはorg.apache.camel.scala.dsl.builder.RouteBuilderSupport
をmixinしたObjectから実行すれば良い。
package jp.den3umegumi.experimental.camel.launch
import jp.den3umegumi.experimental.camel.route.SampleRouteBuilder
import org.apache.camel.main.Main
import org.apache.camel.scala.dsl.builder.RouteBuilderSupport
import org.slf4j.LoggerFactory
/**
* camelのトリガー
*/
object RouteBuilderLauncher extends App with RouteBuilderSupport {
private val logger = LoggerFactory.getLogger(getClass)
val main = new Main
main.enableHangupSupport;
main.addRouteBuilder(new SampleRouteBuilder)
logger.debug("camel routing start")
main.run
}
説明
殴り書き感が否めないので、余計読み辛いけど・・・。
/**
* timerトリガーのRoute
*/
"timer:foo-timer?repeatCount=1" ==> {
log("foo timer start.")
-->("direct:hello-world")
log("foo timer end.")
}
camelはfrom
とto
に分かれている。
基本的には、入る→出る。
このRouteを数珠つなぎにしてデータを連携させる。
from
何をトリガにしてRouteが起動させるかを定義する。
詳細は各Component毎に異なるが基本的には以下の書式となる。
[scheme or protoclol]:[名前]?[optionKey1]=[optionValue1]&[optionKey2]=[optionValue2]...
で構成される。
ここでは"timer:foo-timer?repeatCount=1" ==>
がfrom
にあたり、foo-timer
という名前のtimer
というスキーマー(プロトコル)で、repeatCount=1
(繰り返し回数1回)で実行される。
他にもperiod
などのがあり、以下の様に記述すれば、1000msec毎に5回繰り返すようになる。(repeatCount=0と指定すると無限)
/**
* timerトリガーのRoute
*/
"timer:foo-timer?repeatCount=5&period=1000" ==> {
log("foo timer start.")
-->("direct:hello-world")
log("foo timer end.")
}
Timer Componentの詳細な記述方式はこちらなどを参考。
log
次にlog("foo timer start.")
で、DSLですでに用意されているlogを出力する書き方。
デフォルトではINFOレベルでログを出力し、第一引数にLoggingLevelを取ることも出来る。
log(org.apache.camel.LoggingLevel.DEBUG ,"bar direct start.")
こんな感じ。
to
RouteをChainさせる場合に呼び出す。
ここでは-->("direct:hello-world")
として "direct:hello-world" ==>
のRouteを呼び出している。
Direct Componentは同期で次のRouteを呼び出す。つまり、"direct:hello-world"
Routeから復帰するまで、次の処理が行われない。
Directの対照的に非同期でのRoute呼び出しとしてSeda Componentがある。
/**
* timerトリガーのRoute
*/
"timer:foo-timer?repeatCount=1" ==> {
log("foo timer start.")
-->("seda:hello-world")
log("foo timer end.")
}
/**
* sedaトリガーのRoute
*/
"seda:hello-world" ==> {
delay(100) // 100msec 待つ。
log("Hello World!")
}
これを実行すると、Hello World!
の出力がfoo timer end.
よりも後に出力される。
directの場合は同期しているので、一瞬のラグがあった後に表示される。
Mavenでプロジェクトを作った場合
日本Apache Camelユーザ会のサンプルをScalaで書く
こちらはJavaで記述しているので、これをScalaで書いてみる。
出来る限りJava to Scalaで書く
変に色気は出さず、そのままscalaの文法で書いてみる。
import org.apache.camel.{Exchange, Processor}
import org.apache.camel.main.Main
import org.apache.camel.scala.dsl.builder.{RouteBuilderSupport, RouteBuilder}
object HelloCamelMain extends App with RouteBuilderSupport {
val main = new Main
main.addRouteBuilder(new RouteBuilder {
"timer:timerName?period=3000" ==> {
process(new OutputHelloProcess)
}
})
main.run
}
class OutputHelloProcess extends Processor {
override def process(e: Exchange): Unit = {
println("Hello")
}
}
Processorの処理を外出ににする(その1)
Processor
はDSLだけでは記述しにくい(or 出来ない)、処理をプログラミング言語で記述することが出来る。
Camelの起動クラスとRouteBuilderとProcessorをそれぞれ別のクラスに書くとこんな感じ。
import org.apache.camel.{Exchange, Processor}
import org.apache.camel.main.Main
import org.apache.camel.scala.dsl.builder.{RouteBuilderSupport, RouteBuilder}
object HelloCamelMain extends App with RouteBuilderSupport {
val main = new Main
main.addRouteBuilder(new HelloCamelRouteBuilder)
main.run
}
class HelloCamelRouteBuilder extends RouteBuilder {
"timer:timerName?period=3000" ==> {
process(new OutputHelloProcess)
}
}
class OutputHelloProcess extends Processor {
override def process(e: Exchange): Unit = {
println("Hello")
}
}
Processorの処理を外出ににする(その2)
別クラスに定義するほどのProcessorでなければ、関数リテラルとして定義することも出来る。
import org.apache.camel.{Exchange, Processor}
import org.apache.camel.main.Main
import org.apache.camel.scala.dsl.builder.{RouteBuilderSupport, RouteBuilder}
object HelloCamelMain extends App with RouteBuilderSupport {
val main = new Main
main.addRouteBuilder(new HelloCamelRouteBuilder)
main.run
}
class HelloCamelRouteBuilder extends RouteBuilder {
private val hello = (e: Exchange) => {
println(Hello)
}
"timer:timerName?period=3000" ==> {
process(hello)
}
}
どハマリポイント
ここで一番注意しなければならないのが、 from - toが定義されているDSL部はクラスの一番下 に定義しなくてはならない!!!
もし、このコードを
class HelloCamelRouteBuilder extends RouteBuilder {
// 関数リテラルのprocessor
private val hello = (e: Exchange) => {
println(Hello)
}
// DSL
"timer:timerName?period=3000" ==> {
process(hello)
}
}
こう書いたら実行時エラーになる。
class HelloCamelRouteBuilder extends RouteBuilder {
// DSL
"timer:timerName?period=3000" ==> {
process(hello)
}
// 関数リテラルのprocessor
private val hello = (e: Exchange) => {
println(Hello)
}
}
今後の予定
- Scala-DSLを使った基本文法をまとめようかな。