DBFluteのBuri連携のおさらい

@DBFlute, Java, Buri
実務での実績もでてきたようなので、DBFluteBuri連携について、
環境構築手順と実装方法についておさらいしておきます。
(Buri-1.0.0を基準とします)

気合いが入ってることが大前提です。
中途半端な気持ちではBuriを役立てることはできません。
必ず、DBFluteBuri連携機能のExampleプロジェクト
「dbflute-buri-example」を参考にするようにして下さい。
#
# DBFluteBuri連携の概要
#
Buriはステータス実装のための便利な機能を持っています。
その機能を有効利用するためにDBFluteBuriと連携します。
ポイントは:

A. タイプセーフで安全実装
B. ディベロッパBuriの内部構造を意識させない

「A」は言うまでもないですね。DBFluteのポリシーです。
Buri内部で例外が発生したりするととてもやっかいです。
極力トラブルが発生しないようにします。

「B」は
「ディベロッパーはBuriの内部構造を意識せずに、
 純粋に業務的なステータス実装に集中する」
ということです。
便利な機能を使うためにその他の煩雑な知識が必要になっては
意味がありません。その代わりアーキテクトはできるだけトラブルが
ないように、環境構築やアプリへの微調整など頑張ることが大事です。
#
# 環境構築手順
#
前提条件:
o SeasarBuriが動作するEclipseプロジェクトが整っていること
o XPDLができあがっていること
o DBスキーマはまだ作成されていない

1. DBユーザと空のDBスキーマを作成
2. EMechaで普通にDBFlute環境
3. buriDefinitionMap.dfpropの作成・設定
4. playsql配下にBuriの内部テーブルのDDL
5. playsql配下にDBFluteBuri用VIEWのDDL
6. playsql配下に業務テーブルのDDL
7. ReplaceSchema実行
8. JDBC/Doc/Generate実行
9. Buri拡張の設定
<1. DBユーザと空のDBスキーマを作成>
まずはDB環境です。後の手順でReplaceSchemaを実行しますので、
とりあえずは「DBユーザ」と「空のDBスキーマ」を作成して下さい。

例えば、Oracleであれば以下のような感じで:
(dbflute-buri-exampleではOracleを利用しています)
create user buriexampledb identified by buriexampledb;
grant connect to buriexampledb;
grant resource to buriexampledb;
grant create view to buriexampledb;
grant create synonym to buriexampledb;
<2. EMechaで普通にDBFlute環境>
こちらは、DBFluteのドキュメント
「EMechaによるセットアップ」をご覧下さい。
<3. buriDefinitionMap.dfpropの作成・設定>
DBFluteクライアント/dfprop配下に「buriDefinitionMap.dfprop」
という名前のファイルを配置します。手動で作成するよりかは、
dbflute-buri-exampleburiDefinitionMap.dfpropを
コピーして持って来るようにして下さい。

activityDefinitionMapにXPDL上のアクティビティを定義します。
「status」にはステータスになりえるアクティビティを定義します。
基本的にはXPDL上のFinishModeがManualのアクティビティがそれです。
「action」にはそのプロセスで利用する全てのアクションを定義します。

tableProcessMapに実際の業務テーブルとプロセスとの関連を定義します。
一つのテーブルに複数のプロセスが関連付けられるようになっています。
一つのテーブルに一つのプロセスであれば、
list:{}の中に一つだけプロセスを指定して下さい。
ex) DOCUMENT = list:{ 文章管理.文書公開 }
#
# ex)
# 文章管理パッケージに文書公開プロセスと文書作成プロセスがあり、
# それらプロセスがDOCUMENTテーブルに関連付く場合
#
map:{
    # /- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    # 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 ; 問題なし ; 手直し必要}
            }
        }
    }
    # - - - - - - - - - -/

    # /- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    # 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:{ 文書管理.文書公開 }
    }
    # - - - - - - - - - -/
}
# map:{
#     ; activityDefinitionMap = map:{
#         ; [Package] = map:{
#             ; [Process] = map:{
#                 ; status = list:{[Status] ; [Status] ; ...}
#                 ; action = list:{[Action] ; [Action] ; ...}
#             }
#         }
#     }
#     ; tableProcessMap = map:{
#         ; [Table] = list:{ [Package].[Process] ; [Package].[Process] ; ... }
#     }
# }
<4. playsql配下にBuriの内部テーブルのDDLBuriの内部テーブルのDDLを配置します。

DBFluteクライアント/playsql配下
「replace-schema-01-buri-internal.sqlOracleであれば、dbflute-buri-examplereplace-schema-01-buri-internal.sqlをそのまま
持って来てもOKです。
<5. playsql配下にDBFluteBuri用VIEWのDDLDBFluteが利用するBuriの内部テーブルを参照するためのVIEWの
DDLを配置します。

DBFluteクライアント/playsql配下
「replace-schema-02-dbflute-buri.sql」

どのDBを使っていようが、dbflute-buri-examplereplace-schema-02-dbflute-buri.sqlをそのまま
持って来て下さい。そして、Oracleでないなら、
VIEWのSQLの「LOCALTIMESTAMP」の部分を利用するDBに
合わせて適切に調節して下さい。(INDEXの利用対象となるか確認)
この件は、こちらの記事が参考になります。
http://d.hatena.ne.jp/taktos/20090616/1245150534
<6. playsql配下に業務テーブルのDDL>
業務テーブルのDDLを配置します。

DBFluteクライアント/playsql配下
「replace-schema-03-basic.sql
<7. ReplaceSchema実行>
Buriの内部テーブルのDDLDBFluteBuri用VIEWのDDL、
業務テーブルのDDLを実行するためにReplaceSchemaタスクを実行します。

業務テーブルにマスタデータやテストデータを登録する場合は、
実行前にDBFluteクライアント/playsql/data配下に準備したデータを
配置するようにして下さい。
<8. JDBC/Doc/Generate実行>
JDBCタスク、Docタスク、Generateタスクを実行します。

DBFluteBuri連携のためのクラスが生成されます。
allcommon.plugin.buriパッケージ配下のクラスがそれです。
BasicBuriUserDataProvider:
BuriUserDataProviderの基本実装(AccessContext利用)
{buri-user.dicon定義対象}

BasicParticipantProvider:
BuriUserDataProviderの基本実装(AccessContext利用)
{buri-user.dicon定義対象}

BehaviorToDataAccessRule:
BuriDBFluteのアダプタ
{buri-share.dicon定義対象(Buri-1.0.1よりburi-user.diconでOK)}

BuriDef:
Processの定義
(buriDefinitionMap.dfpropで定義したプロセスのENUMの集まり)

FixedBuriDataAccessScriptFactoryImpl:
Long型以外のPKを利用可能にする
{buri-extension.dicon定義対象}

FixedScriptDataAccessUtilKeyImpl:
Long型以外のPKを利用可能にする
{FixedBuriDataAccessScriptFactoryImplでnewされる}
<9. Buri拡張の設定>
Buriの設定ファイルに指定する必要のあるものは以下の四つです:

o BehaviorToDataAccessRule
o FixedBuriDataAccessScriptFactoryImpl
o BasicBuriUserDataProvider
o BasicParticipantProvider
BehaviorToDataAccessRuleは、buri-share.diconに指定します。
buri-share.diconはBuri本体からコピーしてきて修正します。
dbflute-buri-exampleburi-share.diconを
参考に配置・修正して下さい。
(Buri-1.0.1以降の場合は、buri-user.diconで指定できるようです)
FixedBuriDataAccessScriptFactoryImplは、
buri-extension.diconに指定します。
buri-extension.diconはBuri本体からコピーしてきて修正します。
dbflute-buri-exampleburi-extension.diconを
参考に配置・修正して下さい。
BasicBuriUserDataProviderおよび
BasicParticipantProviderは、buri-user.diconに指定します。
dbflute-buri-exampleburi-user.diconを
参考に配置・修正して下さい。

また、DBFluteの共通カラムの自動設定で利用するAccessContextの
設定をして下さい。DBFluteの共通カラムのページを参考に。

これは、ステータス更新ユーザ(の履歴)を保持するための設定です。
AccessContextとBuriのステータス更新を関連付けて保持します。
業務的に「誰がこのステータスを変更したのか」を保持することは
ほとんどの場合において必須かと思われます。なので、ここでは
この機能を利用することを前提として説明をしています。

Buriの権限機能の仕組みを利用して実現しています。
権限チェックを行いたい場合は、このあたりを独自に拡張して下さい。
dbflute-buri-exampleが利用しているBuriのバージョンと
実際に利用しようとしているBuriのバージョンが同じであれば、
Exampleのdiconを持って来て、パッケージ名の調整などをする
だけでもOKです。
#
# 実装方法
#
DBFluteBuri連携の大きな特徴は以下のとおりです:

A. タイプセーフなtoNextStatus()
B. 指定されたステータスによる絞り込み条件での検索
C. Buriが保持する情報へのスムーズなアクセス
<A. タイプセーフなtoNextStatus()>
Buriのプロセスに関連付けられた業務テーブルのBehaviorに
toNextStatus_[プロセス]()メソッドが生成されています。
プロセスの指定はメソッドで解決されタイプセーフです。
アクションの指定はENUMによるタイプセーフです。
_documentBhv.toNextStatus_文書管理_文書公開(document, BuriDef.文書管理_文書公開_Action.Next);

// 第一引数:Entity
// 第二引数:アクション
<B. 指定されたステータスによる絞り込み条件での検索>
Buriのプロセスに関連付けられた業務テーブルのBehaviorに
getEntities()メソッドが生成されています。
ステータスを指定して該当する業務テーブルを検索できます。
ステータスの指定はENUMによるタイプセーフです。
List<Document> releaseWaitingList = _documentBhv.getEntities(
    new ConditionBeanSetupper<DocumentCB>() {
        public void setup(DocumentCB cb) {
            cb.query().addOrderBy_Id_Desc();
        }
    }, BuriDef.文書管理_文書公開_Status.公開待ち);
    
// 第一引数:ステータス条件以外のCBの要素の指定
// 第二引数:検索したいステータス(可変長引数で複数指定可)
検索された業務テーブルのEntityリストは、
Buriが保持する情報を取得するためのVIEWのEntityを保持しています。
(内部的にsetupSelect_[ViewEntity]をしています)
「Buriが保持する情報へのスムーズなアクセス」にて説明します。

第二引数(可変長引数)を指定しなかった場合は、
ステータス条件無しの検索となります。
<C. Buriが保持する情報へのスムーズなアクセス>
getEntities()で検索されたEntityから以下の情報が取得できます:

o 現在ステータス(のENUM)
o Buri内部で管理している情報を取得するVIEWのEntity
List<Document> releaseWaitingList = _documentBhv.getEntities(
    new ConditionBeanSetupper<DocumentCB>() {
        public void setup(DocumentCB cb) {
            cb.query().addOrderBy_Id_Desc();
        }
    }, BuriDef.文書管理_文書公開_Status.公開待ち);

for (Document document : releaseWaitingList) {
    // 現在ステータス
    ... = document.getStatus_文書管理_文書公開();
    
    // VIEWのEntity
    BuriAllRoundState state
        = document.getBuriAllRoundState_文書管理_文書公開();
    
    // ステータスを更新した日時
    ... = state.getStatusUpdateDatetime();

    // ステータスを更新したユーザ
    // (ステータス更新時にAccessContextに格納されていた値)
    ... = state.getStatusUpdateUser();
}
さらにステータス更新履歴のVIEWをLoadReferrerで
取得することができます。
Document document = ...;

_documentBhv.loadBuriAllRoundStateHistory_文書管理_文書公開List(
    document
    , new ConditionBeanSetupper<BuriAllRoundStateHistoryCB>() {
        public void setup(BuriAllRoundStateHistoryCB cb) {
            cb.query().addOrderBy_StatusUpdateDatetime_Asc();
        }
    }
);

List<BuriAllRoundStateHistory> historyList
    = document.getBuriAllRoundStateHistory_文書管理_文書公開List();
for (BuriAllRoundStateHistory history : historyList) {
    // ステータスのパス ex) 文書管理.文書公開.公開待ち
    // (codeOf()メソッドでENUMとして取得できる)
    String statusPathName = history.getStatusPathName();
    文書管理_文書公開_Status status
        = BuriDef.文書管理_文書公開_Status.codeOf(statusPathName);

    // ステータスを更新した日時
    ... = history.getStatusUpdateDatetime();

    // ステータスを更新したユーザ
    ... = history.getStatusUpdateUser();
}
#
# 番外編
#
履歴に対してExistsSubQueryができます。
「とあるステータスになったことがある」という条件で
絞り込みができます。
このときのステータスの指定もタイプセーフです。
DocumentCB cb = ...;

cb.query().existsBuriAllRoundStateHistory_文書管理_文書公開(
    new SubQuery<BuriAllRoundStateHistoryCB>() {
        public void query(BuriAllRoundStateHistoryCB subCB) {
            subCB.query().setStatus_Equal(BuriDef.文書管理_文書公開_Status.レビュー待ち);
        }
    });
DBFluteが提供するViewはカスタマイズが可能です。
基本構造を崩さなければ、カラムを追加などはアプリの自由です。
#
# おまけ
#
履歴に関してですが「ステータス変更時のコメント」のような
業務的に結構重要な値を保持することができません。
なので、結局アプリ側で独自の履歴テーブルを用意して、
画面でステータス変更履歴を表示するときにBuriの履歴と
一緒に合わせてどうのこうのってやらなければなりません。

「ステータス変更履歴はBuriに任せることができる」って
いうのが無いとBuri利用の説得力が半減するので、
DBFluteで拡張してどうにかできないかなぁと考えたり...
(でもさすがに簡単な拡張じゃないので無理かも)
インターフェース的には、toNextStatus()に引数を増やして、
コメントを一緒に(内部的に)登録できるようにできるといいかな。
コメントに限らず設定次第で独自の値を第三引数で指定できると
一番良いのかも。。。