2011年11月4日金曜日

[Seasar2][ドメイン駆動]ドメイン駆動の各階層について

次のような階層構造とする。

プレゼンテーション層(Seasar2のアクションクラス)

サービス層(独自に定義、DI設定も追加が必要)

ドメイン層(ドメイン・リポジトリ、独自に定義、DI設定も追加が必要)

データアクセス層(DAO・エンティティ)

上位の階層は1つ下の階層にあるクラスのみを呼び出せる。
2つ以上下の階層にあるクラスを直接呼び出してはいけない。

また、サービスとDAOはインターフェースとする。
(DI設定により、実装を差し替えられるようにするため。主にテストで必要となる)

リポジトリは、必ずドメイン生成とドメイン取得をメソッドとして用意する。
ドメインにDAOをインジェクションする場合、newはできないのでコピーメソッドを用意する。

public class Domain {

    /**
     * DI内容のコピー
     *
     * @return DIの内容をコピーしたドメインのインスタンス
     */
    public Object copyDi() {

        //
        // DIではnewするごとにインジェクションはできず、DIコンテナの
        // getメソッドを使うと、シングルトンやリクエスト、セッション
        // ごとのインスタンスしか戻すことができない。
        // よって、不特定多数のドメインを生成する場合は、インジェクション
        // 設定をクローンメソッドによりコピーする必要がある。
        // (そうしないと、シングルトンの内容を別のクラスで書き換える
        // といったバグの温床になる)
        //

        try {
            // クラスの新しいインスタンスを生成する
            Object obj = this.getClass().newInstance();

            // メソッド配列を取得する
            Method[] methods = this.getClass().getMethods();

            // getterメソッドのマップを作成するまでループ
            Map<String, Method> getterMap = new HashMap<String, Method>();
            for (int i = 0; i < methods.length; i++) {

                // メソッド名がDaoかRepositoryのgetterの場合はマップに記録
                if (methods[i].getName().indexOf("get") == 0) {
                    if (methods[i].getName().contains("Dao")
                            || methods[i].getName().contains("Repository")) {
                        String targetName = methods[i].getName().substring(3);
                        getterMap.put(targetName, methods[i]);
                    }
                }
            }

            // setterを使い、新しいインスタンスに設定を行うループ
            for (int i = 0; i < methods.length; i++) {

                // DaoかRepositoryのsetterの場合は、設定を行う
                if (methods[i].getName().indexOf("set") == 0) {
                    if (methods[i].getName().contains("Dao")
                            || methods[i].getName().contains("Repository")) {
                        String targetName = methods[i].getName().substring(3);
                        methods[i].invoke(obj, getterMap.get(targetName)
                                .invoke(this));
                    }
                }
            }

            // 生成したクローンを呼び出し側に戻す
            return obj;

        } catch (Exception e) {

            // 何らかのエラーが起きた場合はnullを戻す
            return null;
        }
    }
}

コード中にも理由が書かれているが、ドメインはデータベース行数分だけ生成されるため、DIのインスタンス生成タイミングと一致しない。

1. リポジトリがドメインをDIする。(シングルトンで用意されているDaoがドメインにDIされる)
2. リポジトリでドメイン生成・ドメイン取得する場合は、上記1.のDIをコピーする。

Seasar2におけるドメイン・リポジトリのDI設定例は、次の通り。

  <component class="org.seasar.framework.container.autoregister.FileSystemComponentAutoRegister">
    <initMethod name="addClassPattern">
      <arg>"rootpackage.domain"</arg>
      <arg>".*Repository"</arg>
    </initMethod>
  </component>

  <component class="org.seasar.framework.container.autoregister.FileSystemComponentAutoRegister">
    <initMethod name="addClassPattern">
      <arg>"rootpackage.domain"</arg>
      <arg>".*Domain"</arg>
    </initMethod>
  </component>

FileSystemComponentAutoRegisterは、プロジェクト内のファイルから探す。
JARにまとめたクラスからDIさせる場合は、JarComponentAutoRegisterを使う。

0 件のコメント:

コメントを投稿