2009-09-29 4 views
7

この質問は、その中核となる設計上の問題です。 Java/Java EEの例を使って質問を説明します。デザイン:ドメインオブジェクトとサービスオブジェクトの間の線が明確でないとき

永続性のためにJPAを、サービス層にEJBを使用して構築されたWebメールアプリケーションを考えてみましょう。

public void incomingMail(String destination, Message message) { 
    Mailbox mb = findMailBox(destination); // who cares how this works 
    mb.addMessage(message); 
} 

このように、私たちはEJBのサービスメソッドを持っているとしましょう。これは一見合理的なビジネスメソッドです。おそらくメールボックスオブジェクトは引き続きアタッチされ、変更内容をデータベースにシームレスに保存します。結局のところ、それは透明な持続性の約束です。

メールボックスオブジェクトは、このメソッドを持っているでしょう:

public void addMessage(Message message) { 
    messages.add(message); 
} 

それが複雑になるのはここだ - 私たちは、他のメールボックスの種類を持つようにしたいと仮定します。送信者に自動的に応答するAutoRespondingMailboxと受信した電子メールごとにヘルプデスクチケットを自動的に開くHelpDeskMailboxがあるとします。

行うには自然な事はAutoRespondingMailboxは、このメソッドを持っているメールボックスを拡張するために、次のようになります。

public void addMessage(Message message) { 
    String response = getAutoResponse(); 
    // do something magic here to send the response automatically 
} 

問題は、私たちのMaiboxオブジェクトであり、それはサブクラスはまた、「ドメインオブジェクト」です(この例ではですJPAエンティティ)。 Hibernateの人たち(そして他の多くの人)は、依存しないドメインモデル、つまりコンテナ/ランタイム提供サービスに依存しないドメインモデルを宣言しています。そのようなモデルの問題は、AutoRespndingMailbox.addMessage()メソッドは、たとえばJavaMailにアクセスできないため、電子メールを送信できないということです。

HelpDeskMailboxでは、HelpDeskシステムと通信するWebServicesまたはJNDIインジェクションにアクセスできなかったため、まったく同じ問題が発生します。

ですから、このように、サービス層でこの機能を置くことを余儀なくしている。そのようにinstanceofを使用する必要が

public void incomingMail(String destination, Message message) { 
    Mailbox mb = findMailBox(destination); // who cares how this works 
    if (mb instanceof AutoRespondingMailbox) { 
     String response = ((AutoRespondingMailbox)mb).getAutoResponse(); 
     // now we can access the container services to send the mail 
    } else if (mb instanceof HelpDeskMailbox) { 
     // ... 
    } else { 
     mb.addMessage(message); 
    } 
} 

は、問題の最初の兆候です。メールボックスをサブクラス化するたびにこのサービスクラスを変更することは、問題のもう一つの兆候です。

誰もこれらの状況の処理方法に関するベストプラクティスを持っていますか?何人かは、メールボックスオブジェクトはコンテナサービスにアクセスしなければならないと言っています。これはいくつかのファジシングで行うことができますが、JPAの意図された使い方と戦うのは間違いありません。これは予想されるユースケースではないということです。

代わりに何をする予定ですか?私たちのサービス方法をリサイズし、多形性をあきらめますか?私たちのオブジェクトは自動的にCスタイルの構造体に降格し、オブジェクト指向の利点のほとんどを失います。

Hibernateチームは、ドメイン・レイヤーとサービス・レイヤーの間でビジネス・ロジックを分割し、コンテナーに依存しないすべてのロジックをドメイン・エンティティーに入れ、それに依存するすべてのロジックコンテナをサービス層に追加します。誰かが多態性を完全にあきらめたり、インスタンスや他の不幸に頼らなくても、私がどのように行うかの例を私に与えることができれば、私はそれを受け入れることができます

答えて

4

メールボックスがメールボックスです...

...しかしautorespondingメールボックスは、それに接続されているいくつかのルールを持つメールボックスです。おそらくこれはメールボックスのサブクラスではなく、1つ以上のメールボックスと一連のルールを制御するMailAgentです。

警告:私はDDDの経験は限られていますが、この例では誤った仮定に基づいて私に当たっています。規則を適用する振る舞いがメールボックスに属すること。メッセージにルールを適用することはメールボックスとは独立していると思います。つまり、受信者のメールボックスは、フィルタリング/ルーティングルールで使用される基準の1つのみです。この場合、ApplyRules(メッセージ)またはApplyRules(メールボックス、メッセージ)サービスは私にはもっと意味があります。

+0

私の質問の意図を忘れてしまったと思います。この例の詳細を忘れて、エンティティのいくつかのサブクラスがコンテナ提供サービスに依存する動作を必要とすると仮定します。 – TTar

+0

私はこれに同意します。問題は、ドメインオブジェクトがもはやデータの単純な所有者ではなくなったことですが、今では(永続性に影響を与えた)振る舞いをそれに関連付けました。個人的には、これはサービスレベルで処理されるべきもののような感じです。 –

+0

ドメインオブジェクトがデータの単純な所有者である場合、私のドメインオブジェクトは単なる構造体ではありませんか?オブジェクト指向設計の定義は、データとビヘイビアがオブジェクトに結合されているのではないのですか? – TTar

0

1つのオプションオブジェクトを "executor"オブジェクトの中にラップします。実行者オブジェクトはサービス層情報を含み、内部化データオブジェクトはドメイン情報を含む。その後、ファクトリを使用してこれらのオブジェクトを作成することで、 "instanceof"メソッドまたは同様の要素の範囲を制限し、別のオブジェクトがデータオブジェクトで実行できるように使用する共通のインタフェースを使用します。これはコマンドパターン(コマンドオブジェクトをエグゼキュータとして持つ)と状態パターン(状態はデータオブジェクトの現在の状態です)のどちらかがミックスされていますが、いずれも正確には適合しません。

+0

しかし、私たちはドメインモデルからコンテナの依存関係を守るという意味でこれをすべて行います。何のために?物事をもっと難しくするために? – TTar

+0

異なるフロントエンドサービスが同じバックエンドシステムを使用できるようにするには、私の理解です – aperkins

6

あなたは何か不足しています:メールボックスオブジェクトが実行時に提供されているインターフェイスに依存することは完全に賢明です。 「実行時サービスに依存しない」は正しいですが、コンパイル時の依存関係を持たないようにしてください。

唯一の依存関係がインターフェイスであるため、StructureMap,UnityなどのIoCコンテナを使用して、実行時インスタンスではなくテストインスタンスにオブジェクトを渡すことができます。ユニットテストのために、 - このクラスが何かに依存しないが、それは必ずしもランタイムによって提供されていないということ

public class AutoRespondingMailbox { 
    private IEmailSender _sender; 

    public AutoRespondingMailbox(IEmailSender sender){ 
     _sender = sender; 
    } 

    public void addMessage(Message message){ 
     String response = getAutoResponse(); 
     _sender.Send(response); 
} 

注:

最後に、AutoRespondingMailboxためのコードは次のようになります。コンソールなどに書き込むダミー​​のIEmailSenderを簡単に提供することもできます。また、プラットフォームが変更されたり、要件が変更されたりすると、オリジナルとは異なる方法論を使用する別のIEmailSenderを簡単に提供できます。 それはが "限界依存"態度の理由です。

+0

私はHibernateの人たちがそうでないと主張すると思います。彼らは実際にビジネスロジックを分割することができます/分割する必要があると信じています。全部を混乱させることなく、どうやって分割できるのか分かりません。 – TTar

+0

これはどのようにビジネスロジックを分割しないのですか? AutoRespondingMailboxの唯一のロジックは、どこかからの応答を取得し、それを自動的に送信することです。特定のインタフェースの存在以外には依存しません。そのインタフェースは、任意の数の方法で提供することができ、その多くはコンパイルされたコードには全く関係しません。 –

1

私はDDDにあまり経験はありませんが、これがどのように解決できるかについての提案が1つあります。

私はMailBoxクラスの抽象クラスを作成し、そのスーパークラスとしてMailBoxを使って3つの実装を作成しました。

私は、メソッドaddMessage(...)の名前付けがうまくいくと思います。この名前の追加は、提供されたメッセージをセッターのようにメールボックスに追加するだけで、既存の値を置き換えるのではなく、提供されたメッセージをある種のストレージに追加することを示唆しています。

しかし、あなたが探しているのはむしろ行動です。抽象メールボックスがすべてのサブクラスにメソッド​​を実装させた場合はどうなりますか?

あなたのメソッドfindMailBox(destination);は、どのメールボックスインスタンスを取得すべきかを何らかの方法で決定します。これはすでに責任があります。

メールボックスのさまざまなサブクラスをインスタンス化するとき、各サブクラスは、受信メッセージを処理する方法が異なる必要があります。

機能インターフェイス:

public interface MessageHandler { 
    void handleMessage(Message message); 
} 

抽象クラス:

public abstract MailBox{ 
    private MessageHandler handler; 

    protected MailBox(MessageHandler handler){ 
     this.handler = handler; 
    } 

インスタンス化:

MailBox mb1 = new MailStorage(new DefaultMessageHandler()); 
MailBox mb2 = new AutoreplyingMailBox(new AutoReplyingMessageHandler()); 
MailBox mb3 = new HelpDeskMailBox(new HelpDeskMessageHandler()); 

そして、あなたが望むならば、あなたもでしたが、これは次のことをやって分離することができ、 MailBoxのすべての異なるサブクラスを取り除き、代わりに異なるimplementaを作成するMessageHandlerインタフェースの一部です。

findMailBoxメソッドに提供される宛先によっては、MailBox(この場合抽象的ではない)をインスタンス化し、正しいMessageHandler実装を提供するだけで済みます。

public class MailBox { 

    private MessageHandler messageHandler; 

    public MailBox(MessageHandler messageHandler){ 
     this.messageHandler = messageHandler; 
    } 

    public void handleIncommingMessage(Message message){ 
     addMessage(message); 
     this.messageHandler.handleMessage(message); 
    } 
} 

あなたの例では、最終的なコードは、このメソッドはする必要はありません。この

public void incomingMail(String destination, Message message) { 
    Mailbox mb = findMailBox(destination); // who cares how this works 
    mb.handleIncommingMessage(message); 
} 

ようなものになるだろう:MailBox.handleIncommingMessage(...)だけん一つ(または2)になるだろう

新しいタイプのMailBoxまたはMessageHandlerが導入されたときに編集することができます。ロジックはデータから分離され、メッセージが追加されたとき(addMessage/handleIncommingMessage)がMailHandler実装に保持されるときのロジックが分離されます。

関連する問題