自己紹介
十川 亮平 (そごう りょうへい) Twitter: @rsogo
NCデザイン&コンサルティング株式会社で
モバイル向けのプラットフォーム (AppPot http://apppot.jp/) のプロダクトマネージャーや、モバイルやIoTの企画や技術面のコンサルタントをやってます。
Mule ESBを利用している開発者の一人としてお話しさせていただきます。
- 2018.07.11のMuleSoft Meetup Tokyo #1用のスライドです。セッション開始まで随時更新します。
https://meetups.mulesoft.com/events/details/mulesoft-tokyo-presents-mulesoft-meetup-tokyo-1#/
今日は
Mule ESBを使ってデータベースにアクセスするためのRESTfulなWeb APIを作るのを題材に、
Mule ESBを使った開発について話します。
内容としては、次のようなトピックを含みます。
- HTTP Listener
- DB Connector
- メッセージ変換
- エラーハンドリング
Muleとは
Mule ESBはMuleSoft社が開発したオープンソースのESB製品です。
Community Editionでも、基本的な機能やプロトコルに対応しています。
コネクタの一覧は下記のサイトから調べることができます。
https://www.mulesoft.com/exchange/#!/?types=connector&sortBy=name
パッケージ製品用のアダプターを使いたいなどの追加の機能を使いたい場合にはEnterprise Editionが用意されています。
全体構造
GET, POST, PUT, DELETE, GETだけれど取得条件指定など自由にかけるFreeQuery、
という5つの操作を提供するフローを作っています。今日は時間の関係で、GETとPOSTだけご説明します。
データベースのテーブルをリソースと見なして、
任意のリソースにアクセスできるようなデモです。
固定のテーブルにアクセスするだけならもっと簡単にできますが、
こんなこともできますよ、という内容になっています。
1つの操作の構造
一つの操作の構造はこのようになっています。構造のレベルではどの操作も同じです。
RESTfulっぽくするためにやっていること
アドレス可読性
提供する情報がURIで表現されていること。
ex) http://hostname:port/emploee/115
統一インターフェイス
HTTPの一般的なやり方にならう。
HTTPメソッド
情報の取得(GET)、作成(POST)、更新(PUT)、削除(DELETE)
HTTPステータスコード
リクエストが不正(400 BadRequest)、内部的なエラー(500 InternalServerError)
HTTP Listener
フロー全体と、各操作それぞれでListenする条件を定義できます。
HTTP Listener
フロー全体のリスナー
こういうhttp://{hostname}:8085/4apppot/{任意}
リクエストをリッスンする定義にしています。
<http:listener-config
name="HTTP_Listener_Configuration"
host="0.0.0.0" port="8085"
basePath="4apppot"
doc:name="HTTP Listener Configuration"/>
1つの操作用のリスナー
フロー全体のリスナーで受け取った上で、GET
メソッドで、{basePath}/query/*
というパスのリクエストを受け取る。
<flow name="FreeQueryFlow">
<http:listener config-ref="HTTP_Listener_Configuration"
path="/query/*"
doc:name="Receive HTTP request"
allowedMethods="GET">
<http:response-builder
reasonPhrase="#[flowVars['reason']]"
statusCode="#[flowVars['statusCode']]"/>
</http:listener>
DBコネクタ
たくさんのコネクタの中から、今回は一番使用頻度が高いかも知れないDatabase Connectorを使います。
JDBCドライバの準備
{MULE_HOME}/lib/user/
以下にJDBCドライバのライブラリを入れておく
接続情報の定義
<db:generic-config doc:name="Generic Database Configuration"
driverClassName="com.mysql.jdbc.Driver"
name="DB_Configuration"
url="jdbc:mysql://localhost:3306/{DATABASE_NAME}?user={DB_USER}&password={PASSWORD}&characterEncoding=utf8"/>
Select: GETの時
<db:select config-ref="DB_Configuration" doc:name="Perform a query in Database">
<db:dynamic-query>
<![CDATA[select * from #[VarTableName]
#[VarQuery]]>]
</db:dynamic-query>
</db:select>
Insert: POSTの時
<db:insert config-ref="DB_Configuration" doc:name="Database">
<db:dynamic-query>
<![CDATA[INSERT INTO #[VarTableName] (#[VarInsertColumns]) VALUES (#[VarInsertValues])]]>
</db:dynamic-query>
</db:insert>
フロー中のメッセージの利用
Muleのフロー定義中ではMEL(Mule Expression Language)というので、メッセージの参照や計算なんかができます。
- マニュアルからのサンプルの抜粋
#[payload]
#[message.inboundProperties.'propertyName']
#[payload.methodCall(parameters)]
#[xpath3('//root/element1')]
#[payload.age > 21]
#[message.inboundProperties.'locale' == 'en_us']
Mule Expression Language Referenceに使い方の例が載っているので、困ったらここを見たら良いと思います。
JSONとの変換はとっても簡単
リクエストで受け取ったJSONメッセージをオブジェクトに変換
<json:json-to-object-transformer returnClass="java.util.ArrayList" doc:name="JSON to Object"/>
SQL実行結果のオブジェクトからJSONに変換
<json:object-to-json-transformer doc:name="Convert Object to JSON"/>
Mule ESBのエラーハンドリング
エラーハンドリングはこんな感じの書き方になります。
<flow>
<リクエストの受取/>
<メッセージ変換など/>
<連携先の呼び出し/>
<応答メッセージの編集/>
<choice-exception-strategy>
<例外Xが起こったときのハンドリング/>
<例外Yが起こったときのハンドリング/>
</choice-exception-strategy>
</flow>
実際にエラー時の応答がRESTfulっぽくなるように書いてみましょう
<flow name="GetFlow">
・・・省略・・・
<choice-exception-strategy doc:name="Choice Exception Strategy">
<catch-exception-strategy doc:name="Catch Exception Strategy"
when="#[exception.causedBy(org.mule.module.db.internal.domain.connection.ConnectionCreationException)]">
<logger level="INFO" doc:name="Log Request" message="Error processing #[flowVars['originalRequest']]" />
<set-variable variableName="statusCode" value="500" doc:name="Set status code"/>
<set-variable variableName="reason" value="Internal Server Error" doc:name="Set reason phrase"/>
</catch-exception-strategy>
<catch-exception-strategy doc:name="Catch Exception Strategy"
when="#[exception.causedBy(org.mule.module.db.internal.resolver.query.QueryResolutionException)]">
<logger level="INFO" doc:name="Log Request" message="Error processing #[flowVars['originalRequest']]" />
<set-variable variableName="statusCode" value="400" doc:name="Set status code"/>
<set-variable variableName="reason" value="Bad Request" doc:name="Set reason phrase"/>
</catch-exception-strategy>
<catch-exception-strategy doc:name="Catch Exception Strategy"
when="#[exception.causedBy(java.sql.SQLSyntaxErrorException)]">
<logger level="INFO" doc:name="Log Request" message="Error processing #[flowVars['originalRequest']]" />
<set-variable variableName="statusCode" value="400" doc:name="Set status code"/>
<set-variable variableName="reason" value="Bad Request" doc:name="Set reason phrase"/>
</catch-exception-strategy>
<catch-exception-strategy doc:name="Catch Exception Strategy"
when="#[exception.causedBy(java.sql.SQLIntegrityConstraintViolationException)]">
<logger level="INFO" doc:name="Log Request" message="Error processing #[flowVars['originalRequest']]" />
<set-variable variableName="statusCode" value="400" doc:name="Set status code"/>
<set-variable variableName="reason" value="Bad Request" doc:name="Set reason phrase"/>
</catch-exception-strategy>
</choice-exception-strategy>
</flow>
スクラッチ開発との比較 - Why Mule ESB
サーバーサイドエンジニアが居て、
ある特定のデータソース向けにだけにプログラムを書けば良いのであえれば、スクラッチ開発で良いかも。
ESBのメリット
- 連携先が複数のデータソース、ソフトウェア、製品などに渡る場合
- 自分たちがオーナーではないシステムと連携する場合(自分たちの都合でコントロールできない)
Apache Camelとの比較 - Why Mule ESB
Apache CamelはMule ESBと同様にさまざまなプロトコルに対応したインテグレーションのためのソフトウェアです。
- Java系のソフトウェアに組み込むなら、Camelは選択肢となる
- ただし、ESB的な用途にはそのままでは使えない
- 独立したプラットフォームとして使いたいならMule ESB、自分たちで作るソフトウェアに組み込みたいならCamel
まとめ
今日ご説明した内容はQiitaで既に公開しています。
フローの定義の全体はGithubに公開していますので、アクセスしてみてください。
フィードバックもいただけるとありがたいです。
3.xで動作確認をしていますので、最新の4.xでは一部修正が必要です。
最後に
Mule、AnyPoint Platformが日本でもっと使われて、ユーザー同士で情報交換が活発にできるようになると嬉しいです!