2011年11月7日月曜日

[Seasar2][ドメイン駆動][ビジネスルール]ビジネスルールの外部化について

ドメイン駆動では、全ての業務ロジックをドメイン層に記述する。
従ってチェック等の処理もドメインもしくはリポジトリのクラスに記述するのが原則である。
このビジネスルールは業務をアプリケーションにしていく上で、最も変わりやすいものである。
この部分のロジックだけを外部化することにより、プログラムの柔軟性を高められる。

例えばプロジェクトがユースケース駆動を採用している場合、全ての業務チェックはユースケースに記述されることとなる。
ユースケースはシステムの機能を列挙したものであるから、そこに書かれている業務チェックは、全て機能ごとの業務チェックということになる。
オブジェクト指向プログラミングにおいては、「クラスを機能単位に分割してはいけない。モノ単位に分割する」という原則があるため、業務チェックをそのままクラスに実装しようとすると、重複するチェックが多数出てくる上、機能単位にまとめることもできないため、無理やり変な基本クラスを作って共通業務チェックを差し込むことになりかねない。

そのようなケースでは、機能ごとの業務チェックをクラスとし、複数の業務チェックで共通して行われるチェックは基本クラスとすべきである。
複数のクラスが相互作用した結果、特定の機能を実現されることから、こうしたチェックはどこかのクラスで呼び出されることとなる。業務チェックのクラスを必要なときだけnewして、チェックしたいインスタンスを渡し、エラーがあったら例外をスローする、などの共通処理を形成しやすい。

このような業務チェックは、ドメイン層(ドメインもしくはリポジトリ)と双方向関連のクラスとして定義すべきである。
例えば、次のようなビジネスルールがあったとする。

「ユーザはメールアドレスをユーザIDとしてサイトにユーザ登録する。よって、重複したメールアドレスを登録しようとした場合は、エラーとしなくてはならない」

このビジネスルールは、ユーザ登録時の業務チェックであることから、UserRegistrationBusinessRuleといったクラスを作成し、業務チェックのメソッドを作成する。

public class UserRegistrationBusinessRule {

    private UserRepository userRepository;

    public UserRegistrationBusinessRule(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public void doCheck(String mail) throws BusinessRuleViolationException {

        UserDomain userDomain = userRepository.findByMail(mail);
        if (userDomain != null) {
            throw new BusinessRuleViolationException();
        }
    }
}

メールアドレスをキーとしてリポジトリに問い合わせを行い、データが見つかった場合はビジネスルール違反例外(ユーザが定義した独自例外)としている。
このビジネスルールクラスによるチェックを行う箇所では、次のようにコードを記述する。

new UserRegistrationBusinessRule(userRepository).doCheck();

複数のビジネスルールに共通するチェックを抽出したら、基本クラスを作成することで重複処理の記述を回避できる。
staticメソッドでも問題ないが、デコレータやストラテジといったデザインパターンを使う場合は、staticにすると却って不利になるのでここではインスタンス化している。

全ての業務ロジックをリポジトリ・ドメインのクラスに記述すると、コードが長大になってしまうことがある。
そのように業務チェックが複雑化するロジックでは、重複したチェックも出やすい。
本来はドメイン層のクラスそのものに業務チェックを記述すべきであるが、場合によっては外部化を検討した方が分かりやすくなる。

0 件のコメント:

コメントを投稿