LoginSignup
9
9

More than 5 years have passed since last update.

ApacheCamelでScalaDSLを使う

Last updated at Posted at 2015-03-17

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を生成出来るフォーマットを仕様書として自動生成

なんて事をしているからでは無いだろか。

  1. HTTPのプロトコルでデータが飛んでくる
  2. データの書式はJSON
  3. 出力形式はCSV
  4. 出力先はファイル

みたいに書いておけば、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を使う

build.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を追加したほうが良い。

build.sbt
+   "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プロジェクトを選んで
archetypeArtifactIdcamel-archetype-scalaを選べば作れる。
必要なdependenciesは勝手に追加してくれる。
スクリーンショット 2015-02-25 12.50.59.png

新規作成ウィザードでscalaのVersionを選べるので、2.10系にするのをお忘れなく。
スクリーンショット 2015-02-25 12.51.14.png

以下のコマンドでも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で書いてみる。

  1. 指定ディレクトリを監視する。
  2. 指定ファイルが存在したらRouteを起動する。
  3. ファイルの中身を出力する。
  4. 中身を別のファイルに書き出す。
FileMove.scala
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/inboundwork/outboundは監視するディレクトリを記述
    絶対パスで書く場合はfile:///usr/local/tmp/とか書く。※file://までは固定。
  • 実行したらinboudに適当なファイルを置く。

なんの変哲も無いファイル移動だけど、 連携元システムからファイル連携して、ゴニョゴニョする! みたいなプログラムが簡単に書けそう!と思いませんか?

つまらないサンプルを使った説明

Camelが動いた程度しか分からない、何も面白くは無いけどサンプルコード。
とりあえずログ出力くらいする。
先ほどとは違い、実行クラスとDSL定義を2クラスに分けて作成。

SampleRouteBuilder.scala
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から実行すれば良い。

RouteBuilderLauncher.scala
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はfromtoに分かれている。
基本的には、入る→出る。
この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の文法で書いてみる。

JapanCamelUserAssociationTutorial.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をそれぞれ別のクラスに書くとこんな感じ。

JapanCamelUserAssociationTutorial.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 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でなければ、関数リテラルとして定義することも出来る。

JapanCamelUserAssociationTutorial.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 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を使った基本文法をまとめようかな。

参考

9
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
9