アプリごとのBehavior

アプリケーションギャップ

例えば、以下のようなプロジェクト構成の開発があるとします。

dblib
DBFluteの自動生成クラス、DB周りのロジック (ライブラリ扱い)
web1
コンシューマ向けのWEBサイト
web2
コンシューマ向けの携帯用WEBサイト (別会社に発注)
web3
管理用WEBサイト (海外に発注)
batch
バッチプロジェクト
web4
さらに別のコンシューマ向けのWEBサイト (海外に発注)

一つのDBを色々なアプリケーションが共有してDBアクセスするとして、dbilb プロジェクトを全てのアプリケーションが参照します。 この場合、かなり大人数で同時に開発が進み、かつ、アプリケーションごとに開発する組織が違うため、なにかとフットワークは重く、 マネジメントの難易度も単一プロジェクトに比べて遥かに高いものになるでしょう。

ジェネレーションギャップでは足りない

DBFluteでは、ジェネレーションギャップという形で自動生成とアプリケーションのギャップを埋められるような構造にはなっていますが、 このような構造の場合では、(特にBehaviorの)Exクラスへのメソッド追加はかなり至難なものになることが想像されます。 バージョン管理システムでのコンフリクト、そして、とあるアプリでしか利用しないメソッドの乱立、もしくは、そもそもそれぞれのアプリには、dblib プロジェクトへの編集権限を持たせない運用もする可能性があります(dblib プロジェクトを jar ファイルとして配るなど)。

また、外だしSQLの BehaviorQueryPath もBehaviorが前提となっているため、dblib プロジェクトの編集なしでは外だしSQLのタイプセーフな呼び出しは実現できません。

DBFluteでは、このようなアプリ間のギャップのことを、アプリケーションギャップ と呼びます。

アプリごとの Behavior

DBFluteでは、このアプリケーションギャップを埋めるために、アプリごとの Behavior を自動生成できるようにしています。これを ApplicationBehavior と呼びます。

対して、ここで言う dblib プロジェクトのことを、ライブラリプロジェクト と呼び、そのライブラリプロジェクト内にある(通常の) Behavior を LibraryBehavior と呼びます。(ApplicationBehaviorを利用している場合に)

このような構造にすることで、それぞれのアプリケーションのディベロッパーは、ライブラリプロジェクトのクラスを編集することなく、 Behaviorに独自のメソッドを追加したり、外だしSQLを定義したりすることができます。(SQLファイルはそれぞれのアプリケーションプロジェクトに配置)

ApplicationBehaviorの構造

ApplicationBehaviorは、それぞれのアプリケーションプロジェクトの中のクラスとして自動生成され、LibraryBehavior の Exクラスを継承します。ApplicationBehavior には、そのアプリでしか利用しないメソッドの追加、そして、そのアプリでしか利用しない外だしSQLの BehaviorQueryPath を定義することができます。

また、外だしSQLもアプリケーションプロジェクトに配置することで、それに関連する CustomizeEntity や ParameterBean もアプリケーションプロジェクトの中に自動生成します。

e.g. ApplicationBehaviorの自動生成クラス(と外だしSQLの配置) {MEMBER} @Directory
dblib // ライブラリプロジェクト
 |-src/main/java
    |-com.example.dblib.dbflute.bsbhv
       |-BsMemberBhv.java // システム全体で共通の外だしSQLのBehaviorQueryPath
    |-com.example.dblib.dbflute.exbhv
       |-MemberBhv.java // システム全体で共通のメソッドを定義
web1
 |-src/main/java
 |  |-com.example.dblib.dbflute.bsbhv
 |  |  |-BsMemberBhvAp.java // アプリ固有の外だしSQLのBehaviorQueryPath 
 |  |-com.example.dblib.dbflute.bsentity.customize
 |  |  |-BsAppOriginal.java // アプリ固有のCustomizeEntity(Bsクラス)
 |  |-com.example.dblib.dbflute.exbhv
 |  |  |-MemberBhvAp.java // アプリ固有のメソッドを定義
 |  |-com.example.dblib.dbflute.exentity.customize
 |  |  |-AppOriginal.java // アプリ固有のCustomizeEntity(Exクラス)
 |-src/main/resources
    |-com.example.dblib.dbflute.exbhv
       |-MemberBhv_selectAppOriginal.sql // アプリ固有の外だしSQL
web2
 |-src/main/java
 |  |-com.example.dblib.dbflute.bsbhv
 |  |  |-BsMemberBhvAp.java // アプリ固有の外だしSQLのBehaviorQueryPath
 |  |-com.example.dblib.dbflute.bsentity.customize
 |  |  |-BsAppOriginal.java // アプリ固有のCustomizeEntity(Bsクラス)
 |  |-com.example.dblib.dbflute.exbhv
 |  |  |-MemberBhvAp.java // アプリ固有のメソッドを定義
 |  |-com.example.dblib.dbflute.exentity.customize
 |  |  |-AppOriginal.java // アプリ固有のCustomizeEntity(Exクラス)
 |-src/main/resources
    |-com.example.dblib.dbflute.exbhv
       |-MemberBhv_selectAppOriginal.sql // アプリ固有の外だしSQL

ApplicationBehavior の suffix

ApplicationBehavior のクラス名の suffix は、BhvAp (びへぶえーぴー)となります。

ApplicationBehavior のパッケージ

パッケージは、基本的に ApplicationBehavior と LibraryBehavior で同じにします。(但し、dfpropの設定で別々にすることは可能)

アプリケーション独自の外だしSQLのBehaviorの名前

外だしSQLのファイル名の Behavior の名前は、LibraryBehavior のもの、もしくは、ApplicationBehavior(Ap付き)(@since 0.9.8.2)のもの。 EMecha の EMSql で BhvAp の Behavior に対して作成した場合は後者となる。

ApplicationBehaviorの自動生成

通常の自動生成環境とは別に、ApplicationBehavior用の自動生成環境を作ります。 全く別のDBFluteクライアントをもう一つ用意する必要はありませんが(してもOKですが)、 通常の自動生成で利用するDBFluteタスクとは違う設定で動作するように環境構築する必要があります。 そのために、環境毎にDBFluteプロパティ(dfprop)を切り替えることができる EnvironmentType を利用します。(EnvironmentTypeは、アプリケーションごとに定義)

切り替えるDBFluteプロパティ

以下、主な切り替え対象のDBFluteプロパティです。その他、ApplicationBehavior の自動生成において調整が必要なプロパティがあれば臨機に追加していきます(以下は、各種設定がDBFluteのデフォルトに近い状態を想定しています)。

basicInfoMap.dfprop
必須。generateOutputDirectory と applicationBehaviorMap の設定
documentDefinitionMap.dfprop
アプリごとのSchemaHTMLを作成する場合に、documentOutputDirectory と schemaHtmlFileName を設定
outsideSqlDefinitionMap.dfprop
ProcedurePmbの自動生成が有効になっている場合に、それを無効にする
refreshDefinitionMap.dfprop
ResourceSynchronizerを利用している場合に、アプリのそれを無効にする
e.g. ApplicationBehaviorのDBFluteプロパティ構成 @Directory
dfprop
 |-web1
 |  |-basicInfoMap+.dfprop            // 自動生成出力先をweb1に。そして、BhvApの有効化
 |  |-documentDefinitionMap+.dfprop   // web1用のSchemaHTMLの出力先など設定
 |  |-outsideSqlDefinitionMap+.dfprop // ProcedurePmbの自動生成を無効に
 |  |-refreshDefinitionMap+.dfprop    // リフレッシュ先プロジェクトを web1 に
 |-web2
 |  |-basicInfoMap+.dfprop            // 基本となるプロパティ(マージされる側)
 |  |-documentDefinitionMap+.dfprop   // SchemaHTMLの出力先など設定
 |  |-outsideSqlDefinitionMap+.dfprop // ProcedurePmbの自動生成を無効に
 |  |-refreshDefinitionMap+.dfprop    // リフレッシュ先プロジェクトを web2 に
 |
 |-basicInfoMap.dfprop // dblib プロジェクトの(通常の)自動生成
 |-...

basicInfoMap.dfprop では、自動生成クラスの出力先の切り替えと共に、applicationBehaviorMap にて、ApplicationBehavior の自動生成を有効化を設定します。

e.g. ApplicationBehavior の basicInfoMap {environmentType=web1} @dfprop
map:{
    ; generateOutputDirectory = ../../web1/src/main/java

    # /- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    # o applicationBehaviorMap: (NotRequired - Default map:{})
    #  The settings for Application Behavior.
    #  Elements of this map are as below:
    #   o isApplicationBehaviorProject: (NotRequired - Default false)
    #    Does the project is for application behaviors?
    #    This property is a main signal for Application Behavior.
    #    Other properties (for Application Behavior) work when this is true.
    #   o libraryProjectPackageBase: (NotRequired - Default same as application's one)
    #    If application package base is different from library's one,
    #    set the property a value 'library's one'.
    #
    ; applicationBehaviorMap = map:{
        ; isApplicationBehaviorProject = true
        ; libraryProjectPackageBase = 
    }
    # - - - - - - - - - -/
}

基本的には LibraryBehavior と ApplicationBehavior は、同じパッケージであることが推奨されますが、設定次第で別々にすることもできます。この場合は packageBase も切り替えて、libraryProjectPackageBase を指定します。 但し、その場合でもアプリケーションで作成する外だしSQLの配置パッケージは、LibraryBehavior のパッケージに合わせる必要があります。

それぞれのアプリ用のコマンドを用意

EnvironmentTypeの設定方法は、それぞれのアプリ用のコマンドを用意するのがお奨めです。 (JDBCタスクとReplaceSchemaタスクのコマンドは、どのアプリでも共通なので不要)

e.g. ApplicationBehavior の自動生成のためのコマンドを用意 @Directory
dbflute_foo // DBFluteクライアント
 |-dfprop
 |  |-web1
 |  |-web2
 |  |-basicInfoMap.dfprop
 |  |-...
 |-doc.sh
 |-generate.sh
 |-sql2entity.sh
 |-outside-sql-test.sh
 |-web1-doc.sh
 |-web1-generate.sh
 |-web1-sql2entity.sh
 |-web1-outside-sql-test.sh
 |-web2-doc.sh
 |-web2-generate.sh
 |-web2-sql2entity.sh
 |-web2-outside-sql-test.sh

それぞれのコマンドは、ライブラリプロジェクト用のものをコピーして、環境変数 DBFLUTE_ENVIRONMENT_TYPE をそれぞれ設定します。

e.g. ApplicationBehavior の自動生成のためのコマンド {generate.sh,web1} @Directory
#!/bin/sh

cd `dirname $0`
. _project.sh

echo "/nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn"
echo "Specify the environment type to use web1."
echo "nnnnnnnnnn/"
export DBFLUTE_ENVIRONMENT_TYPE=web1

echo "/nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn"
echo "Specify the file path to be used as build-properties."
echo "nnnnnnnnnn/"
export MY_PROPERTIES_PATH=build-${MY_PROJECT_NAME}.properties

echo "/nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn"
echo "Execute {Generate}."
echo "nnnnnnnnnn/"
sh $DBFLUTE_HOME/etc/cmd/_df-generate.sh $MY_PROPERTIES_PATH

echo "/nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn"
echo "Remove the environment type (closing)."
echo "nnnnnnnnnn/"
unset DBFLUTE_ENVIRONMENT_TYPE

アプリ用のコマンドを実行

用意したGenerateタスクを実行すると、ApplicationBehavior (とDI設定ファイル)が自動生成されます。また、Docタスクでは、アプリケーションごとの SchemaHTML (テーブル定義は全く同じだが、外だしSQLのリストがそのアプリのものだけのリストに)が自動生成され、 Sql2Entityでは、アプリケーション独自の外だしSQLに対応する Entity が自動生成され、 OutsideSqlTestでは、アプリケーション独自の外だしSQLをテストします。

ApplicationBehaviorのDI設定

LibraryBehavior と同じく、ApplicationBehavior はDIコンテナに登録される必要があります。それぞれのDIコンテナの形式に合わせた ApplicationBehavior のためのDI設定ファイル(or クラス)が自動生成されるので、それぞれのアプリケーションにてDIコンテナがその設定ファイルを読み込まれるように設定します。(同時に LibraryBehavior のDI設定ファイルも読み込まれる必要があります)

Seasar(S2Container)
dbflute++.dicon が自動生成される。S2ClassBuilder を利用することで、他に設定なしで自動でコンテナに登録される。(利用することを前提とする)
Spring Framework
dbfluteBeansBhvAp.xml が自動生成される(コンテナに読み込まれるように設定が必要)
Google Guice
DBFluteModuleBhvAp.java が自動生成される(コンテナに読み込まれるように設定が必要)
T2 Lucy
dbfluteBeansBhvAp.xml が自動生成される(コンテナに読み込まれるように設定が必要)

LibraryBehavior とは別の(シングルトン)インスタンスで登録されます。

e.g. ApplicationBehaviorの自動生成されるDI設定ファイル {Seasar} @Directory
entity
 |-src/main/java
    |-...
web1
 |-src/main/java
 |  |-...
 |-src/main/resources
    |-dbflute++.dicon
web2
 |-src/main/java
 |  |-...
 |-src/main/resources
    |-dbflute++.dicon

Exampleのススメ

この構成のためのExampleプロジェクトが存在します。 この構成は複雑な部分があるため、(主に環境面において)必ずExampleを参考にするようにして下さい。

DBFlute Example - 特定環境

ApplicationBehaviorの利用

それぞれのアプリケーションでは、この ApplicationBehavior を利用してDBアクセスを行うことで、LibraryBehavior の機能に加えて、アプリ固有の機能(メソッドや外だしSQL)を利用することができます。

e.g. ApplicationBehaviorの利用 {MEMBER} @Java
protected MemberBhvAp memberBhvAp;

public void doSearch() {
    // アプリ固有のメソッド or アプリ固有の外だしSQLの呼び出し
    memberBhvAp.select...;
}

アプリ固有の機能を利用する必要のない場面では、LibraryBehavior を利用しても問題ありません。その場合は、どちらの Behavior から実行しても処理に違いはありません。(但し、紛らわしい面もあるので、一律 BhvAp を利用するというポリシーでも良いでしょう)

e.g. LibraryBehavior と ApplicationBehaviorの併用 {MEMBER} @Java
// 同時に両方をDIしても問題ない
protected MemberBhv memberBhv;
protected MemberBhvAp memberBhvAp;

public void doSearch() {
    // 全てのアプリ共通のメソッド or 全てのアプリ共通の外だしSQLの呼び出し
    memberBhv.select...;

    // アプリ固有のメソッド or アプリ固有の外だしSQLの呼び出し
    memberBhvAp.select...;
}

(Specify)DerivedReferrerも

Exクラスにアプリ固有のメソッドを定義するのは Behavior だけではなく、Entity も同様です。特に ConditionBean の (Specify)DerivedReferrer を利用するときは、ディベロッパー自身が Entity のExクラスにメソッドを定義することが求められます。この ApplicationBehavior を利用して、ライブラリプロジェクトの編集なしで (Specify)DerivedReferrer を実現することができます。アプリケーション独自の Entity を作成して、それを利用します。

ApplicationEntityの手動作成

アプリケーション独自の Entity (ApplicationEntity)は、手動で作成します。

e.g. ApplicationEntityの作成(習慣的にクラス名の後ろにApを付与) {MEMBER} @Directory
web1
 |-src/main/java
 |  |-com.example.dblib.dbflute.bsbhv
 |  |  |-BsMemberBhvAp.java 
 |  |-com.example.dblib.dbflute.exbhv
 |  |  |-MemberBhvAp.java
 |  |-com.example.dblib.dbflute.nogen.entity // 手動作成用パッケージ(習慣的に nogen)
 |     |-MemberAp.java // 手動で作成(習慣的に XxxAp)

ApplicationEntityの実装

ライブラリプロジェクトの Entity (LibraryEntity)を継承するようにし、(Specify)DerivedReferrer で利用するプロパティを定義します。

e.g. ApplicationEntityの実装(プロパティの追加など) {MEMBER} @Java
public class MemberAp extends Member {
    ...
    public static final String ALIAS_firstLoginDatetime = "FIRST_LOGIN_DATETIME";

    protected Date _firstLoginDatetime;

    public Date getFirstLoginDatetime() {
        return _firstLoginDatetime;
    }

    public void setFirstLoginDatetime(Date firstLoginDatetime) {
        _firstLoginDatetime = firstLoginDatetime;
    }
    ...
}

ApplicationBehaviorにメソッド定義

ApplicationBehavior のExクラスに、ApplicationEntity を利用するメソッドを定義します。例えば、ConditionBean のリスト検索 であれば、doSelectList() の第二引数に ApplicationEntity の型を指定することで実現することができます。

e.g. ApplicationEntityを利用するリスト検索を追加 {MEMBER} @Java
public class MemberBhvAp extends BsMemberBhvAp {

    public ListResultBean<MemberAp> selectApList(MemberCB cb) {
        return doSelectList(cb, MemberAp.class);
    }
}

ApplicationBehaviorにメソッド呼び出し

このメソッド経由で、(Specify)DerivedReferrer のデータを(LibraryEntity の編集なしで)受け取ることができます。

e.g. ApplicationEntityを利用した(Specify)DerivedReferrer {MEMBER} @Java
public void doSearch() {
    MemberCB cb = new MemberCB();
    cb.specify().derivedMemberLoginList().min(new SubQuery<MemberLoginCB>() {
        public void query(MemberLoginCB subCB) {
            subCB.specify().columnLoginDatetime();
            subCB.query().setMobileLoginFlg_Equal_False();
        }
    }, MemberAp.ALIAS_firstLoginDatetime);
    ListResultBean<MemberAp> memberList = memberBhvAp.selectApList(cb);
    for (MemberAp member : memberList) {
        Date firstLoginDatetime = member.getFirstLoginDatetime();
    }
}

ConditionQuery(のwhere句再利用)は?

ConditionQuery のExクラスは、このアプリケーションギャップに対応していません。

where句の再利用メソッドは、システム全体で設計されることが望まれるため、 (極端に)逆に言うと、アプリケーションギャップを発生させてはいけないと想定されるためです。 また、万が一、アプリケーションギャップが発生しても、Behavior (外だしSQL)や Entity に比べて、遥かに発生頻度が低いことが想定されるため、大規模な構成でもライブラリプロジェクト集中方式で耐えられやすいと考えています。 (単純に、(DBFlute内部での)実現がとても難しいという噂もあります)

ReplaceSchemaのテストデータは?

このような大規模な構成の場合、ReplaceSchemaのテストデータもアプリケーションごとに追加したい可能性があります。 その場合は、ApplicationPlaySql 機能を利用します。

現場フィット - アプリごとのPlaySql(テストデータ)

Exampleのススメ

dbflute-bhvap-seasar-example や dbflute-bhvap-spring-example では、実際にアプリごとのBehaviorを利用しています。 この構成は複雑な部分があるため、(主に環境面において)必ずExampleを参考にするようにして下さい。

DBFlute Example - 特定環境