DBFlute: Buri対応のプロトタイプ公開

@DBFlute, Java, escafeFlow, S2Buri, Buri

実は、少し前のバージョンから既にあったのですが、
プロトタイプということで公開したいと思います。
(Buriがなんなのか、そしてどういうメリットがあるのかは
ここでは割愛させて頂きます)

以前もDBFluteBuriを対応しておりましたが、
それは本当にただBuriのデータプロバイダの役割を
S2Daoの代わりにDBFluteがやるというだけで、
「連携」という感じではありませんでした。

やるならやるで、せっかくDBFluteなので、
発想力を活かしたことしたいなぁと思っていました。
一年前くらいから。
きっかけはこちらの記事で、ずっと気になってました。
いつかやりたい、いつかやりたいとずっと思っていました。
id:j5ik2oさん、きっかけをありがとうございました。

機会あって色々と実用に向けた仕様を詰めて実装することができました。
機会を下さった方、そして仕様を授けて下さった方、本当に感謝感謝です。
「実用に向けて」ってのはなかなか一人では到達しないものです。

ポイント概要:

o Buri操作を完全タイプセーフに!
o Buri内部テーブルとの連携

具体的な機能:

A. Buri内部テーブルのBehavior/ConditionBeanを自動生成
B. ステータス(アクティビティ)・アクションのENUMを自動生成
C. Buri管理テーブルのBehaviorにtoNextStatus()を自動生成
D. 該当ステータスのEntityを検索
E. 該当Entityの現在ステータス(Enum)を取得

これから説明する際にExampleとして利用するプロセスは以下の通り:

パッケージ : 文章管理
プロセス  : 文書公開
アクティビティ:登録 → 公開待ち → 公開 → 公開中 → 公開終了
関連づける業務テーブル:ドキュメント(DOCUMENT)テーブル

ステータスと呼べるアクティビティは「公開待ち」と「公開中」です。
他のアクティビティはFinishModeがAutomaticです。
アクションは「next」と「update」と「delete」とオーソドックな感じ。
(分岐のないシンプルなモデルです)
nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
A. Buri内部テーブルのBhv/CB自動生成
nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
どれだけBuriの内部テーブルを参照する必要があるかの
さじ加減が不明ですが、参照したいことは必ずあると思います。
まあ普通にDBに存在するテーブルなので自動生成しちゃえば
いいのですが、ちょっと困った問題が一つありました。

Buriの内部テーブルが「BuriStateUser」ってテーブル名だったり、
「BuriUserID」・「insertDate」ってカラム名だったりして、
キャメルケースなんですね。業務テーブルが同じ形式ならいいのですが、
そうでない場合(の方が多いかと)は、そのまま自動生成すると、
Entity名などが「Buristateuser」と先頭以外の単語の先頭文字が
小文字になってしまいます。すると単に醜いというのもそうなのですが、
Eclipse: キャメルケースの補完奥義が使えなくて実装がしづらいです。
これをなんとかするために、DBFluteではBuri内部テーブルであることを
検知して、(結構無理矢理)「BuriStateUser」と綺麗に自動生成される
ようにしています。

というように、Buri内部テーブルに対してCBも使えますし、
外だしSQL(2WaySQL)でSql2Entityも使えますので、
いくらでもDBFlute方式で参照することが可能です。
BuriStateCB cb = new BuriStateCB();
cb.setupSelect_BuriData();
cb.query().queryBuriData().setPkeyNum_Equal(...);
cb.query().queryBuriData().setDataType_Equal(...);
cb.query().queryBuriData().setTableName_Equal(...);
List<BuriState> stateList = _buriStateBhv.selectList(cb);
nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
B. ステータス(アクティビティ)・アクションのENUMを自動生成
nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
「C」や「D」や「E」の布石になります。
やはりENUMが欲しいものです。

但し、現状ではDBFluteプロパティに定義したものを自動生成するだけで、
XPDLを読み込んで一気に自動生成するということはしていません。
単純に実現に時間が掛かりそうって話もありますが、
やるなら、別のScriptプログラムでXPDLからdfpropファイルを
自動生成しちゃえばいいだけの話でもあり、
優先度の低い問題だということで現状サポートしていません。

さて、設定はdfprop配下に「buriDefinitionMap.dfprop」
というファイルを作り、以下のように定義します。
# /- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# o activityDefinitionMap: (NotRequired - Default 'map:{}')
#  The definition of activities.
#  If you specify this property, you can use generated ENUM classes.
# 
; activityDefinitionMap = map:{
    ; 文章管理 = map:{
        ; 文書公開 = map:{
            ; status = list:{公開待ち ; 公開中}
            ; action = list:{next ; update ; delete}
        }
        ; 文書作成 = map:{
            ; status = list:{レビュー ; 問題なし ; やり直し}
            ; action = list:{next ; update ; delete ; ok ; boo}
        }
    }
}
# - - - - - - - - - -/

# /- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# o tableProcessMap: (NotRequired - Default 'map:{}')
#  The relation between tables and processes.
#  If you specify this property, you can use generated behavior methods.
# 
; tableProcessMap = map:{
    ; DOCUMENT = list:{ 文章管理.文書公開 ; 文章管理.文書作成 }
}
# - - - - - - - - - -/
activityDefinitionMapがまさしく自動生成されるEnumに相当します。
tableProcessMapは「定義したプロセスをどの業務テーブルに関連付けるか」
を設定します。これは「C」や「D」や「E」の機能のための設定です。

これで自動生成する以下のようなENUMクラスが自動生成され、
「C」や「D」や「E」で説明するBehaviorのメソッドも生成されます。
public enum 文章管理_文書公開_Status {
    公開待ち("公開待ち")
    ,
    公開中("公開中")
    ;
    ...
}
public enum 文章管理_文書公開_Action {
    Next("next")
    ,
    Update("update")
    ,
    Delete("delete")
    ;
    ...
}
nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
C. Buri管理テーブルのBehaviorにtoNextStatus()を自動生成
nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
さて、Buriで一番基本となるメソッドで、ここを使いやすく!というのが
今回の一番の目的です。「B」の設定で業務テーブルとアクティビティ定義が
関連付けられていれば、該当プロセスのtoNextStatus()が自動生成されます。
// DocumentBhv
public void toNextStatus_文章管理_文書公開(Document document, BuriDef.文章管理_文書公開_Action action) {
    _simpleBuriProcessor.toNextStatusAction(...)
}
一つのテーブルに複数のプロセスを関連付けること想定して、
メソッド名でプロセスを識別できるようにしています。
この場合は、文章管理_文書公開プロセスのtoNextStatus()です。
「toN」くらいまで入力して補完すれば候補に出てきます。

アクションの指定が必須になっています。
これは、実際は全てのステータスにおいて「update」とか「delete」とか
の基本的なアクションが必ず必要になるはずなので、というところです。

やはり、かなり業務そのものな情報であり、開発時の変更や
運用後の変更というのがかなり想定される部分であるため、
プログラムが安全に修正できるようにと文字列指定ではなく、
Enum指定にしています。
nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
D. 該当ステータスのEntityを検索
nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
「C」まででほとんどやりたいことはできているのですが、
実用に向けて考えるとまだちょっと足りません。
ステータスを指定してEntityのリストを検索する機能が必要です。
普通に検索一覧画面とかの検索条件などで利用されることが
想定されますし、他にも用途はたくさんありそうです。

Behaviorに「getEntities()」というメソッドが生成されます。
これを使って、指定されたステータスのEntityのリストを取得します。
ステータスの指定はもちろんEnum形式です。
List<Document> waitList = _documentBhv.getEntities(new ConditionBeanSetupper<DocumentCB>() {
        public void setup(DocumentCB cb) {
            cb.query().addOrderBy_Id_Desc();
        }
    }, BuriDef.文章管理_文書公開_Status.公開待ち);
ただ取得だけでは活躍の場面がかなり狭まることになるでしょう。
ソート順はほとんどの場合必須でしょうし、場合に寄っては
別の条件で追加の絞り込みをしたいこともあるでしょう。
なので、LoadReferrerと同じような感じでCBをSetupを
コールバックできるようにしています。
(これはdbflute-buri-exampleを発見して下さった方の
 フィードバックです。ありがとうございます!)
 
第二引数のステータスは可変長引数で複数指定できます。
SQLではIN句で表現されます。
 
で、欲しいEntityのリストは取れたとして、そのリスト内の
それぞれのEntityのステータスが欲しいと思います。
検索したEntityには、該当ステータスのEnumを取得する
メソッドが存在します。
for (Document document : waitList) {
    BuriDef.文章管理_文書公開_Status status
        = document.getStatus_文章管理_文書公開();
    ...
}
ステータスを表現するEnumは、「公開待ち」や「公開中」などの
ステータス名を取得することができますので、一覧画面での表示
なのにそのまま利用できます。

このEntityのgetStatus()メソッドですが、実はgetEntities()
で検索したEntity以外のEntityでも利用可能です。
BuriPathDataというビューを関連テーブルとして取得していれば、
Entity内部でそこから辿ってEnumを探して戻します。
通常のCBの検索で「cb.setupSelect_BuriPathData()」と
指定して検索したEntityではgetStatus()が利用可能です。
DocumentCB cb = new DocumentCB();
cb.setupSelect_BuriPathData();
List<Document> list = _documentBhv.selectList(cb);
for (Document document : list) {
    BuriDef.文章管理_文書公開_Status status
        = document.getStatus_文章管理_文書公開();
    ...
}
業務テーブルからBuriPathDataビューへの関連は、
実際にFK制約がなくても自動で設定されます。
実は、これも今回の対応の地味に肝なところなのですが、
業務テーブルとBuriPathDataを関連付けるのは結構やっかいです。
「何と何をON句の条件に入れればいいのか」などがわかりにくく、
間違いやすいため、そこをDBFluteが吸収しています。
文章上の機能項目として独立していませんが効果のある機能です。
nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
E. 該当Entityの現在ステータス(Enum)を取得
nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
今度は「D」とは逆です。
「該当Entityが現在どういうステータスなのか?」
を知りたくなることがあります(頻度は少ないかもしれませんが)。

Behaviorに「getStatus()」というメソッドが生成されます。
これを使って、指定されたEntityの現在ステータスを取得します。
戻り値はもちろんEnum形式です。
BuriDef.文章管理_文書公開_Status current
    = _documentBhv.getStatus_文章管理_文書公開(document);
但し、このメソッド「Entityの現在ステータスが複数ある場合」に
対応していません。Entityの現在ステータスは実は一つとは限らず、
複数のステータスであることがあります。その場合、本当は戻り値が
リストでないといけないのですができていません。
ちょっと実現に時間が掛かりそうなのですが、実際の業務ではそこまで
「Entityの現在ステータスが複数」で設計することは
ひとまず多くないだろうということで割り切っています。
将来対応するとなれば、getStatuses_Xxx()でリストが戻り値の
メソッドを追加することになるでしょう。
「D」と「E」ですが自前でやろうとすると結構ややこしいんですね。
BuriPathDataっていうビューがいて、そいつを参照しにいかないと
というところですが、このビューの仕様が、あの...その...
ちょっとわかりにくくって、もろもろ迷いポイントがあるので、
(どれでユニークなのか?PKEYNUMとPKEYVALどっち?とか)
少なくともディベロッパーには意識させない方が良いと思い、
その辺をDBFluteが内部で吸収します。
getEntities()とgetStatus()の中の実装を見ればわかります。
まだ、こちらプロトタイプ扱いの機能です。
実践をくぐり抜ける際にいろいろと微調整をする可能性があります。
なので、もし業務で実際に利用される方がいたらぜひご連絡下さい。
(互いにプラスになるように情報交換しましょう)

興味のある方はExampleプロジェクトを参考にして下さい。
(環境的な面も実装的な面も)
Exampleプロジェクト:dbflute-buri-example
Example実装(テストケース):DocumentBhvTest
Buriに関するDBFluteプロパティ:buriDefinitionMap.dfprop
ExampleのSchemaHTML:project-schema-buriexampledb.html

Exampleで利用しているDBはOracleです。
Exampleとしてはまだまだ色々と未完成ではあります。
(アクションの詳細な設定とか権限周りの設定とかまだこれから)
都度都度コミットしていく予定です。

一つ環境的な面で注意。DBFluteは既にDaoインターフェースがありません。
なので、Buriに対してBehaviorを使ったデータアクセスルールを定義する
必要があります。そのクラスがDBFluteBehaviorToDataAccessRuleです。
buri-share.diconに定義する必要があります。
こちらのクラスも更新される可能性があるのでご注意下さい。
(プロジェクト直下のreadme.txtを必ずお読み下さい)
アーキテクトはがっちりBuriを理解する必要があるでしょう。
Buriの内部テーブルも把握しておいた方が良いでしょう。
自分はまず最初Buri内部テーブルのERDを書きました。
そして実際に動かしてログをじっくり見てその動きを把握しました。
(まだ把握仕切れてないけど...)

ディベロッパーにその理解を強要するのはマネジメント的に
現実的でないと考え、DBFluteBuriの難しい部分を抑え、
目的に沿ったメソッドを提供することによって、
「ツールを使うことによって逆に発生するバグやコスト」
が極力発生しないようにするのが良いと考えました。
ディベロッパーはBuriの内部構造を意識すること無く、
Buriの提供する素晴らしい業務的な機能だけを意識して、
そのメリットを享受することができるようにと。

こうやって、良いフレームワークと「連携」をして、
新たな価値を生み出すのもやりがいのあることですね。
【追記】
http://d.hatena.ne.jp/makotan/20090311#p3
拡張ポイントを作って頂きました。
次のバージョンから反映されるようです。
DBFluteのRulesクラスをburi-share.diconでなく、
buri-user.diconに登録することが可能になります。
やはりこちらの方が安心ですね。
ありがとうございます!!!