LoginSignup
5
2

More than 3 years have passed since last update.

Apache Camel のroute構築で用いる Java DSL の個人的なコーディング規約

Last updated at Posted at 2020-07-29

Apache Camel では「route」を作成する際、用意されたメソッドをひたすら繋げるという独自のDSLで書く。このDSLは条件分岐ループなどにも対応している。

ただ、Javaのスタイルガイドに従って書くと、条件分岐やループの内部処理を作るメソッドも同じインデントになってしまい、DSLとして非常に読みづらい。なので自前でインデントを追加した方が良いと考えられる。

また、他の部分についても複数の書き方ができたりするが、読みやすい側に統一したい。というわけで、個人的に決めているコーディング規約をまとめた。使ったことのある機能のみしか決められないので、今後経験が増えたら追加・修正していくつもり。

RouteBuilder

基本となるroute全体の形について。 configure() の中身だけ示す。

from("direct:sample").routeId("MySampleRoute")
        // some process
        .to("mock:aaa").id("SamplePartA")
        .log(LoggingLevel.DEBUG,
                "very long text...")

        // another process
        .to("mock:bbb")
;
  • 基本は1行1メソッド(1処理)とする
    • ただし直前のを修飾するものは同じ行を推奨
      • 例えば id() は同じ行(XMLのDSLの場合を考えればいい)
      • routeId() はrouteにIDを設定するので、 from() と同じ行、せめて次の行
    • 処理のグループ単位を表したければ空行を設ける
  • 継続行はインデントを2段(※)つける
    • Javaのスタイルに準拠
      • メソッドの引数を改行する場合もインデント2段
      • 引数が長すぎる場合は、定数に切り出すことも検討すること
    • 逆に言えば始まりの from() だけ2段飛び出す
    • DSLで構造化する際はさらにインデントする(後述)
  • 終わりを示す ; は行を分ける
    • 処理を追加した際、diffで余計な変更が出なくなる

※ インデント1段 = 4スペース(checkstyle)または1タブ、これは別途規約を定めること

構造化

条件分岐やループなどを作るものは、基本的に xxx()end() となっている。共通ルールは以下の通り。

  • その内部を1段インデントする
    • ブロックを視覚的に認識しやすくする
    • Javaの同機能の構文に合わせる
  • end() を省略しない
    • 後段に処理を追加する際、 end() を忘れると意味が変わってしまう
    • なお、 endXxx() が用意されていても使わないこと(使い道が違う?)
  • オプションは xxx() と同じ行にする(長くなる場合は次の行にして1段下げる)

choice

switch文に相当する(breakは不要)。ただしwhen側に条件式を書くので、SQLやRubyでできる式の無いcase文のほうがより似ている。

from("direct:sample")
        .choice()
            .when(new MyPredicateA())
                .to("mock:aaa")
            .when(new MyPredicateB())
                .to("mock:bbb")
            .otherwise()
                .to("mock:zzz")
        .end()
;
  • when()otherwise() は1段、内部処理はさらに1段下げる
    • Javaのswitch文に合わせた書き方
    • ちなみにRubyのcase式だとwhenは下げない(elsifと同じという考え方のため)
  • when() の引数に条件を書く
    • when().simple(...) などは禁止する
  • otherwise() は処理が無い場合は省略する

条件には Predicate のインスタンスを指定する。(ドキュメント

  • 自作したクラスを使う
  • header(name)simple("${expr}") などで直接booleanを得る(ValueBuilder)
  • header(name).isEqualTo(value) のようにbooleanを導く(PredicateBuilder)

filter

分岐が when() 1個のみの場合は、 choice() の代わりに filter() の使用を検討する。行数やインデントが減り読みやすくなる。ガード節を書く際などは特に有用。

ガード節の例
// ガード節無しだと読みにくい(※これはcamelに限らない)
from("direct:noGuard")
        .choice()
            .when(new MyPredicate())
                .to("mock:doNothing")
            .otherwise()
                .to("mock:doSomething1")
                .to("mock:doSomething2")
                .to("mock:doSomething3")
        .end()
;

// choiceでもガード節は作れるけど、ちょっと大げさ
from("direct:guardByChoice")
        .choice()
            .when(new MyPredicate())
                .to("mock:doNothing")
                .stop()
        .end()

        .to("mock:doSomething1")
        .to("mock:doSomething2")
        .to("mock:doSomething3")
;

// filterだと簡潔
from("direct:guardByFilter")
        .filter(new MyPredicate())
            .to("mock:doNothing")
            .stop()
        .end()

        .to("mock:doSomething1")
        .to("mock:doSomething2")
        .to("mock:doSomething3")
;

split

拡張for文(for-each文)やforEachメソッドなどに相当する。Listや文字列などをバラし個別に新しいexchangeに詰めて処理する。

from("direct:sample")
        .split(body()).parallelProcessing()
            .log("content: ${body}")
            .to("mock:abc")
        .end()
;
  • split() の引数に、分割するものを書く
    • split().body() などは禁止する

loop, while

ループ回数をインデックス参照できる(0始まり)。for文のように回数指定する場合は loop() 、while文のように条件指定する場合は loopDoWhile() を使う。

from("direct:sample")
        .loop(100).copy()
            .log("index: ${exchangeProperty[CamelLoopIndex]}")
            .to("mock:abc")
        .end()
;

try … catch … finally

例外処理もある。

これもJavaの文法に合わせて、 doTry(), doCatch(), doFinally(), end() を同じインデント、それぞれの内部を1段下げることとする。

例: https://github.com/apache/camel/blob/camel-2.25.1/camel-core/src/test/java/org/apache/camel/processor/TryProcessorMultipleExceptionTest.java

TryProcessorMultipleExceptionTest.java
from("direct:start")
        .doTry()
            .process(new ProcessorFail())
            .to("mock:result")
        .doCatch(IOException.class, IllegalStateException.class)
            .to("mock:catch")
        .doFinally()
            .to("mock:finally")
        .end()
;

引数かメソッドチェーンか

以下のメソッドはexchangeにデータを入れるためのもの。

  • setHeader()
  • setProperty()
  • setBody()

これらは入れる値を「引数で指定する」方法と「メソッドチェーンで指定する」方法がある。規約では「引数で指定する」ほうに統一する。routeを構成する1メソッドがそのまま1処理になっているほうが分かりやすいと思う。

構造化のところで when()split() などに対し引数で指定しているのも、同じ考え方に基づく。

// disliked
from("direct:dislikedSample")
        .setHeader("foo").expression(new MyExpression())
        .setProperty("bar").header("foo")
        .setBody().exchangeProperty("bar")
;

// preferred
from("direct:preferredSample")
        .setHeader("foo", new MyExpression())
        .setProperty("bar", header("foo"))
        .setBody(exchangeProperty("bar"))
;

引数には Expression のインスタンスを指定する。(ドキュメント

  • 自作したクラスを使う
  • constant(obj) で直接Javaオブジェクトを指定する
  • header(name)simple("${expr}") などで値を得る

規則の例外

ただし引数指定だと煩雑になる場合はメソッドチェーンにする。この際はなるべく同じ行に続けて書く。

例えば marshal() 。引数に文字列を渡す方法もあるが、タイポなどを防ぐためにできれば用意された定数などを使いたい。そうすると引数の場合の方が長くなってしまう。

from("direct:marshalSample")
        .marshal().json(JsonLibrary.Gson)
        .marshal(new JsonDataFormat(JsonLibrary.Gson))
;

式展開

log() の引数の文字列では、 simple() と同じ式展開(と呼ぶのかは分からない…)が書ける。

from("direct:loggingSample")
        .log("foo :: ${header[foo]}")
        .log("bar :: ${exchangeProperty[bar]}")
        .log("baz :: ${body}")
;
  • headerやpropertyの要素は、 .name, :name, [name] のどれででも参照できる
    • exchangeの中ではこれらは Map なので、連想配列らしく [name] が良い気がする
    • 必要ない限りは、キーをクォートで括らない
  • headerの要素の参照時には、変数 header を使い headers は使わない
    • route構築に使っているメソッド名に合わせる

メソッド呼び出しなどさらに強力な式も書けるのだが、まだ詳細な規約は決められていない。
https://camel.apache.org/components/latest/languages/ognl-language.html

状況に応じた選択

似たことを異なる方法でできる場合がある。目的に合ったもの、目的に特化したものを選ぶほうが可読性が高い。

ログ出力

2通り用意されている。用途が異なるので迷うことは無いはず。

from("direct:start")
        .log(LoggingLevel.DEBUG, "org.apache.camel.sample", "any message")
        .to("log:org.apache.camel.sample?level=DEBUG&showAll=true&multiline=true")
;

choiceかfilterか

構造化のところで説明した通り。 filter() はif文1個(else無し)の分岐だけを書けると思っていい。

式展開かメソッド組合せか

Simple Expression Language式展開(?)を使えば様々な値や条件式を作れる。一方で、普通の値(ValueBuilder)に対して操作できるメソッドもある。メソッドで書けばコンパイル時点で単純なミスに気付ける可能性が高まるが、良いメソッドが無いと長くなりやすい。どちらが読みやすいかは状況によるので参考程度に。

from("direct:simpleOrValue")
        // create same strings
        .setHeader("sample1", simple("hoge: ${header[hoge]}"))
        .setHeader("sample2", header("hoge").prepend("hoge: "))

        // check same conditions
        .choice()
            .when(simple("${header[CamelSqlRowCount]} != 1"))
                .log("record not unique!")
            .when(header(SqlConstants.SQL_ROW_COUNT).isNotEqualTo(1))
                .log("record not unique!")
        .end()
;
5
2
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
5
2