「ビジネスロジックは、モデルではなくサービス内にあるべきです」とどれくらい正確ですか?


397

状況

今晩、私はStackOverflowに関する質問に回答しました。

質問:

既存のオブジェクトの編集は、リポジトリレイヤーまたはサービスで行う必要がありますか?

たとえば、借金のあるユーザーがいる場合。彼の借金を変えたい。UserRepositoryで実行するか、たとえば、BingingServiceなどのサービスでオブジェクトを取得して編集し、保存する必要がありますか?

私の答え:

オブジェクトを同じオブジェクトに変更する責任を負い、リポジトリを使用してこのオブジェクトを取得する必要があります。

状況の例:

class User {
    private int debt; // debt in cents
    private string name;

    // getters

    public void makePayment(int cents){
        debt -= cents;
    }
}

class UserRepository {
    public User GetUserByName(string name){
        // Get appropriate user from database
    }
}

私が受け取ったコメント:

ビジネスロジックは実際にはサービス内にある必要があります。モデルではありません。

インターネットは何と言っていますか?

だから、サービス層を実際に(意識的に)使用したことがないので、これで検索できました。サービスレイヤーパターンと作業単位パターンについて読み始めましたが、これまでのところ、サービスレイヤーを使用する必要があると確信しているとは言えません。

例えば、Annemic Domain Modelのアンチパターンに関するMartin Fowlerの次の記事をご覧ください。

ドメイン空間には名詞にちなんで名前が付けられたオブジェクトがあり、これらのオブジェクトは、真のドメインモデルが持つ豊富な関係と構造に関連付けられています。ビヘイビアーを見ると問題が発生しますが、これらのオブジェクトにはビヘイビアーがほとんどないため、ゲッターとセッターのバッグにすぎないことがわかります。実際、これらのモデルには多くの場合、ドメインオブジェクトにドメインロジックを配置しないという設計ルールが付属しています。代わりに、すべてのドメインロジックをキャプチャするサービスオブジェクトのセットがあります。これらのサービスはドメインモデルの上に存在し、データにドメインモデルを使用します。

(...)ドメインオブジェクトにあるべきロジックは、ドメインロジック-検証、計算、ビジネスルール-好きなものは何でも。

私にとって、これはまさにその状況のように思えました。そのクラス内にまさにそれを行うメソッドを導入することにより、オブジェクトのデータの操作を提唱しました。ただし、これはどちらの方法でも指定する必要があることを認識しており、これらのメソッドの呼び出し方法(リポジトリを使用)に関連している可能性があります。

また、その記事(下記を参照)では、サービスレイヤーは、実際の作業集約型レイヤーよりも、基礎となるモデルに作業を委任するファサードと見なされていると感じました。

アプリケーション層[サービス層の名前]:ソフトウェアが実行することになっているジョブを定義し、表現力豊かなドメインオブジェクトに問題を解決するよう指示します。この層が担当するタスクは、ビジネスにとって意味があるか、他のシステムのアプリケーション層との対話に必要です。この層は薄く保たれます。ビジネスルールや知識は含まれていませんが、タスクを調整し、次のレイヤーのドメインオブジェクトのコラボレーションに作業を委任するだけです。ビジネスの状況を反映する状態はありませんが、ユーザーまたはプログラムのタスクの進捗を反映する状態を持つことができます。

ここで強化されています

サービスインターフェイス。サービスは、すべての着信メッセージが送信されるサービスインターフェイスを公開します。サービスインターフェイスは、アプリケーションに実装されたビジネスロジック(通常、ビジネスレイヤーのロジック)を潜在的な消費者に公開するファサードと考えることができます。

そしてここに

サービスレイヤーには、アプリケーションやビジネスロジックを含めず、主にいくつかの懸念事項に焦点を当てる必要があります。ビジネスレイヤーコールをラップし、クライアントが理解できる共通言語でドメインを翻訳し、サーバーと要求クライアント間の通信媒体を処理する必要があります。

これは、サービスレイヤーについて説明する他のリソースとは大きく異なります。

サービス層は、同じトランザクションに属するアクションの作業単位であるメソッドを持つクラスで構成する必要があります。

または、すでにリンクしている質問に対する2番目の回答

ある時点で、アプリケーションにはビジネスロジックが必要になります。また、入力を検証して、要求されている悪意のあるものやパフォーマンスの悪いものがないことを確認することもできます。このロジックはサービス層に属します。

"解決"?

この回答のガイドラインに従って、サービスレイヤーを使用する次のアプローチを思いつきました。

class UserController : Controller {
    private UserService _userService;

    public UserController(UserService userService){
        _userService = userService;
    } 

    public ActionResult MakeHimPay(string username, int amount) {
        _userService.MakeHimPay(username, amount);
        return RedirectToAction("ShowUserOverview");
    }

    public ActionResult ShowUserOverview() {
        return View();
    }
}

class UserService {
    private IUserRepository _userRepository;

    public UserService(IUserRepository userRepository) {
        _userRepository = userRepository;
    }

    public void MakeHimPay(username, amount) {
        _userRepository.GetUserByName(username).makePayment(amount);
    }
}

class UserRepository {
    public User GetUserByName(string name){
        // Get appropriate user from database
    }
}

class User {
    private int debt; // debt in cents
    private string name;

    // getters

    public void makePayment(int cents){
        debt -= cents;
    }
}

結論

ここではすべてがあまり変わっていません。コントローラーからのコードはサービス層に移動しました(これは良いことなので、このアプローチには利点があります)。しかし、これは私の元の答えとは何の関係もなかったようです。

設計パターンはガイドラインであり、可能な限り実装されるべき規則ではありません。しかし、サービスレイヤーの明確な説明と、それをどのように考慮する必要があるかについてはわかりません。

  • 単純にコントローラーからロジックを抽出し、代わりにサービス内に配置する手段ですか?

  • コントローラーとドメイン間で契約を結ぶことになっていますか?

  • ドメインとサービスレイヤーの間にレイヤーが必要ですか?

そして、最後になりましたが、元のコメントに続いて

ビジネスロジックは実際にはサービス内にある必要があります。モデルではありません。

  • これは正しいです?

    • モデルではなくサービスにビジネスロジックを導入するにはどうすればよいですか?

6
サービス層は、トランザクションスクリプトの不可避な部分を集約ルートに作用する場所として扱います。サービスレイヤーが複雑になりすぎて、Anemic Modelの方向に移動していることを示す場合、ドメインモデルに注意とレビューが必要です。また、その性質によって複製されることのないロジックをSLに配置しようとします。
パベルボロニン

サービスはモデルレイヤーの一部だと思いました。私はそれを考えるのは間違っていますか?
フロリアンマーゲイン

私は経験則を使用します。必要のないものに依存しないでください。そうしないと、必要のない部分の変更によって(通常はマイナスの)影響を受ける可能性があります。結果として、私は多くの明確に定義された役割になります。すべてのクライアントが使用する限り、データオブジェクトには動作が含まれます。それ以外の場合は、必要なロールを実装するクラスに動作を移動します。
ベルチン

1
アプリケーションフロー制御ロジックはコントローラーに属します。データアクセスロジックはリポジトリに属します。検証ロジックはサービス層に属します。サービスレイヤーは、コントローラーレイヤーとリポジトリレイヤー間の通信を仲介するASP.NET MVCアプリケーションの追加レイヤーです。サービス層にはビジネス検証ロジックが含まれています。倉庫。asp.net/mvc/overview/older-versions-1/models-data/...
Kbdavis07

3
私見、正しいOOPスタイルのドメインモデルは、実際には「ビジネスサービス」を避け、モデル内のビジネスロジックを保持する必要があります。しかし、実際には、明確に名前が付けられたメソッドで巨大なビジネスレイヤーを作成し、メソッド全体にトランザクション(または作業単位)を配置することが非常に魅力的であることが示されています。複数のモデルクラスを1つのビジネスサービスメソッドで処理する方が、それらのモデルクラス間の関係を計画するよりもはるかに簡単です。なぜなら、答えるのが難しい質問があるからです。データベーストランザクションはどこで開始/コミットする必要がありますか?など
-JustAMartin

回答:


368

サービスの責任を定義するには、まずサービスとは何かを定義する必要があります。

サービスは、正規または一般的なソフトウェア用語ではありません。実際、Serviceクラス名の接尾辞は非常に悪質なManagerによく似てます。オブジェクトが実際に何をするかについてはほとんど何も伝えません。

実際には、サービスが行うべきことは、アーキテクチャ固有です。

  1. 従来の階層化アーキテクチャでは、サービスは文字通りビジネスロジックレイヤーと同義です。UIとデータの間のレイヤーです。したがって、すべてのビジネスルールがサービスに組み込まれます。データレイヤーは基本的なCRUD操作のみを理解し、UIレイヤーはプレゼンテーションDTOとビジネスオブジェクト間のマッピングのみを処理する必要があります。

  2. RPCスタイルの分散アーキテクチャ(SOAP、UDDI、BPELなど)では、サービスは物理エンドポイントの論理バージョンです。基本的に、メンテナーがパブリックAPIとして提供したい操作のコレクションです。さまざまなベストプラクティスガイドでは、サービス操作は実際にはCRUDではなくビジネスレベルの操作である必要があると説明されており、私も同意する傾向があります。

    ただし、実際のリモートサービスを介してすべてをルーティングするとパフォーマンスが大幅に低下する可能性があるため、通常、これらのサービスに実際にビジネスロジックを実装させないことが最善です。代わりに、ビジネスオブジェクトの「内部」セットをラップする必要があります。1つのサービスに1つまたは複数のビジネスオブジェクトが含まれる場合があります。

  3. MVP / MVC / MVVM / MV *アーキテクチャでは、サービスはまったく存在しません。または、そうする場合、この用語は、コントローラーまたはビューモデルに挿入できる汎用オブジェクトを指すために使用されます。ビジネスロジックはモデルに含まれています。複雑な操作を調整するために「サービスオブジェクト」を作成する場合、それは実装の詳細と見なされます。悲しいことに、多くの人々はこのようにMVCを実装していますが、モデル自体は何もしないので、アンチパターン(Anemic Domain Model)と見なされます。これはUIのプロパティの集まりにすぎません。

    一部の人々は、100行のコントローラーメソッドを採用し、それをすべてサービスに組み込むと、何らかの形でより優れたアーキテクチャになると誤解しています。それは本当にありません。おそらく不要な別の間接層を追加するだけです。実際には、コントローラーはまだ作業を行っていますが、名前があまりよくない「ヘルパー」オブジェクトを介して作業を行っています。貧血ドメインモデルを有用なモデルに変える方法の明確な例については、ジミーボガードの邪悪なドメインモデルのプレゼンテーションを強くお勧めします。これには、公開しているモデルと、ビジネスコンテキストで実際に有効な操作の慎重な検査が含まれます。

    たとえば、データベースにOrdersが含まれていて、Total Amountの列がある場合、アプリケーションはおそらく(a)履歴であり、(b)あるはずなので、実際にそのフィールドを任意の値に変更することは許可されません何によって決まる順番だけでなく、おそらく他のいくつかの時間に敏感なデータ/ルール。ユーザーコードができますので、注文を管理するためのサービスを作成することは、必ずしも、この問題を解決していません、まだ実際のOrderオブジェクトを取得し、それに金額を変更します。代わりに、注文自体は、安全で一貫した方法でのみ変更できるようにする責任があります。

  4. DDDでは、サービスは、集約ルートに適切に属さない操作がある場合の状況を特に対象としています。多くの場合、サービスの必要性は、正しいルートを使用しなかったことを意味する可能性があるため、ここで注意する必要があります。しかし、あなたがそうであると仮定すると、サービスは複数のルート間で操作を調整するために使用されるか、ドメインモデルにまったく関係しない懸念(おそらく、BI / OLAPデータベースへの情報の書き込みなど)を処理するために使用されます。

    DDDサービスの1つの注目すべき側面は、トランザクションスクリプトを使用できることです。大規模なアプリケーションで作業する場合、最終的には、T-SQLまたはPL / SQLプロシージャを使用して何かを達成する方が、ドメインモデルに煩わされるよりもはるかに簡単なインスタンスに遭遇する可能性が非常に高くなります。これは問題なく、サービスに属します。

    これは、サービスの階層化アーキテクチャ定義からの根本的な逸脱です。サービス層はドメインオブジェクトをカプセル化します。DDDサービスは、ドメインオブジェクトにないものをカプセル化し、意味をなさない。

  5. サービス指向アーキテクチャでは、サービスはビジネス機能の技術的権限と見なされます。つまり、ビジネスデータの特定のサブセットの排他的所有者であり、そのデータに触れることは許可されていません。単に読み取ることさえ許可されていません。

    必然的に、サービスは実際にはSOAのエンドツーエンドの提案です。つまり、サービスはスタック全体ほど特定のコンポーネントではなく、アプリケーション全体(またはビジネス全体)は、メッセージングレイヤーとUIレイヤーを除き、交差することなく並行して実行されるこれらのサービスのセットです。各サービスには、独自のデータ、独自のビジネスルール、独自のUIがあります。それらはビジネスに沿ったものであるため、互いに調整する必要はありません。また、ビジネス自体と同様に、各サービスには独自の責任があり、他のサービスとはほぼ独立して動作します。

    そのため、SOAの定義では、ビジネスロジックのあらゆる部分がどこにでも含まれていますが、システム全体もそうです。SOAのサービスはコンポーネントを持ち、エンドポイントを持つことができますが、元の「S」の意味と矛盾するため、コードの一部をサービスと呼ぶのはかなり危険です。

    SOAは一般にメッセージングに非常に熱心であるため、以前にサービスにパッケージ化した操作は一般にハンドラーにカプセル化されますが、多重度は異なります。各ハンドラーは、1つのメッセージタイプ、1つの操作を処理します。これは、単一責任原則の厳密な解釈ですが、考えられるすべての操作が独自のクラスにあるため、優れた保守性を実現します。コマンドは技術的なものではなくビジネスオペレーションを表すため、集中化されたビジネスロジックは実際には必要ありません。

最終的に、選択したアーキテクチャには、ビジネスロジックのほとんどを備えたコンポーネントまたはレイヤーが存在します。結局のところ、ビジネスロジックがあちこちに散らばっている場合は、スパゲッティコードしかありません。ただし、そのコンポーネントをサービスと呼ぶかどうか、およびオペレーションの数やサイズなどの観点からどのように設計されるかは、アーキテクチャの目標によって異なります。

正しい答えも間違った答えもありません。あなたの状況に当てはまるものだけです。


12
非常に精巧な答えをありがとう、私が考えることができるすべてを明確にしました。他の回答も同様に優れた品質に優れていますが、私はこの回答がすべてに勝ると信じており、したがって、私はこれを受け入れます。他の答えとしてここに追加します:絶妙な品質と情報ですが、残念ながら私はあなたに賛成票を与えることができるだけです。
イェルーンVannevel

2
従来の階層化アーキテクチャでは、サービスはビジネスロジック層の同義語であるという事実には同意しません。
CodeART

1
@CodeART:3層アーキテクチャです。私がしている時々 、「サービス」層と呼ばれ、プレゼンテーションとビジネス層の間の「アプリケーション層」がある4層のアーキテクチャを、見て、正直なところ、私が今までこれが実装見てきた唯一の場所に成功は巨大な広大ですSAPやOracleのような無限に設定可能な全ビジネス向け製品を実行できます。ここで言及する価値はないと思いました。必要に応じて説明を追加できます。
アーロンノート

1
しかし、100以上のラインコントローラー(たとえば、コントローラーがメッセージを受け入れる-次にJSONオブジェクトをデシリアライズし、検証を行い、ビジネスルールを適用し、dbに保存し、結果オブジェクトを返す)を取得し、サービスメソッドと呼ばれるもののいずれかにロジックを移動した場合それは私達がそれの各部分を苦痛なく個別に単体テストするのを助けるか。
artjom

2
@Aaronaught ORMを介してdbにマップされたドメインオブジェクトがあり、その中にビジネスロジックがない場合、この貧弱なドメインモデルかどうかを明確にしたかったのですか?
artjom 14年

40

あなたの役職については、この質問には意味がないと思います。MVCモデルは、データとビジネスロジックで構成されます。論理はモデルではなくサービスにあるべきだと言うことは、「乗客は車ではなく座席に座るべきだ」と言うようなものです。

この場合も、「モデル」という用語はオーバーロードされた用語です。おそらく、MVCモデルではなく、データ転送オブジェクト(DTO)の意味でのモデルを意味します。別名エンティティ。これがマーティン・ファウラーが言っていることです。

私の見方では、マーティン・ファウラーは理想的な世界のことを語っています。HibernateとJPA(Javaの土地)の現実の世界では、DTOは非常に漏れやすい抽象化です。エンティティにビジネスロジックを入れたいです。物事がずっときれいになります。問題は、これらのエンティティが管理/キャッシュされた状態で存在する可能性があり、理解するのが非常に難しく、常に努力が妨げられることです。私の意見をまとめると、Martin Fowlerは正しい方法を推奨していますが、ORMはそれをあなたに妨げています。

ボブ・マーティンはより現実的な提案をしており、彼はこのビデオでそれを無料ではないと思います。彼はあなたのDTOを論理から解放することについて話しています。データを保持し、よりオブジェクト指向で、DTOを直接使用しない別のレイヤーに転送するだけです。これにより、漏れやすい抽象化があなたを噛まないようにします。DTOを含むレイヤーとDTO自体はオブジェクト指向ではありません。しかし、いったんその層から抜け出すと、Martin Fowlerが提唱しているオブジェクト指向のようになります。

この分離の利点は、永続層を抽象化することです。JPAからJDBC(またはその逆)に切り替えることができ、ビジネスロジックを変更する必要はありません。DTOに依存するだけで、それらのDTOがどのように取り込まれるは気にしません。

トピックをわずかに変更するには、SQLデータベースがオブジェクト指向ではないという事実を考慮する必要があります。ただし、ORMには通常、テーブルごとにエンティティ(オブジェクト)があります。ですから、最初からすでに戦闘に負けています。私の経験では、エンティティをオブジェクト指向の方法で望むとおりに表現することはできません。

「についてはサービス」、ボブ・マーティンという名前のクラスを持つに反します。それはオブジェクト指向ではありません。サービスは何をしますか? 関連するもの。ラベル付けすることもできます。彼はサービス層(より良い名前はビジネスロジック層)を提唱すると思いますが、その層のすべてのクラスには意味のある名前があります。 FooBarServiceFooBarsFooBarUtils


2
ORMに関するあなたの意見に同意します。実際には、エンティティが複数のテーブルに保存されている場合に、エンティティを直接データベースにマッピングするという嘘を広めます。
アンディ14年

@Daniel Kaplanボブ・マーティンのビデオの更新されたリンクが何であるか知っていますか?
ブライアンモレアーティ

25

私は現在、グリーンフィールドプロジェクトに取り組んでおり、昨日、いくつかのアーキテクチャ上の決定を行う必要がありました。面白いことに、「エンタープライズアプリケーションアーキテクチャのパターン」のいくつかの章を再検討する必要がありました。

これが私たちが思いついたものです:

  • データ層。データベースを照会および更新します。レイヤーは、注入可能なリポジトリを介して公開されます。
  • ドメイン層。これは、ビジネスロジックが存在する場所です。この層は、注入可能なリポジトリを使用し、ビジネスロジックの大部分を担当します。これはアプリケーションのコアであり、徹底的にテストします。
  • サービス層。この層はドメイン層と通信し、クライアントの要求を処理します。私たちの場合、サービス層は非常に単純です-ドメイン層に要求を中継し、セキュリティと他のいくつかの横断的な問題を処理します。これは、MVCアプリケーションのコントローラーと大差ありません-コントローラーは小さくシンプルです。
  • クライアント層。SOAPを介してサービス層と通信します。

次のようになります。

クライアント->サービス->ドメイン->データ

クライアント、サービス、またはデータレイヤーを適切な量の作業に置き換えることができます。ドメインロジックがサービス内に存在し、サービスレイヤーを置換または削除することを決定した場合、すべてのビジネスロジックを別の場所に移動する必要があります。このような要件はまれですが、発生する可能性があります。

これをすべて言ったが、これはマーティン・ファウラーが言ったことにかなり近いと思う

これらのサービスはドメインモデルの上に存在し、データにドメインモデルを使用します。

次の図は、これを非常によく示しています。

http://martinfowler.com/eaaCatalog/serviceLayer.html

http://martinfowler.com/eaaCatalog/ServiceLayerSketch.gif


2
昨日SOAPを決めましたか?それは要件ですか、それとも良い考えがありませんでしたか?
JensG

1
RESTは要件に合わせて削減しません。SOAPまたはREST、これは答えに違いをもたらしません。私の理解では、サービスはドメインロジックへのゲートウェイです。
-CodeART

ゲートウェイに完全に同意します。SOAPは(標準化された)ブロートウェアなので、私は尋ねなければなりませんでした。そして、はい、質問/回答にも影響はありません。
JensG

6
自分自身を支持し、サービス層を殺します。UIはドメインを直接使用する必要があります。これは以前に見たことがあり、ドメインは常にリッチなモデルではなく、貧血のDTOの束になります。
アンディ14年

これらのスライドでは、サービスレイヤーに関する説明とこの図の上にかなりきちんとしたスライドがあります。slideshare.net
Marc Juchli

9

これは、ユースケースに本当に依存するものの1つです。サービスレイヤーの全体的なポイントは、ビジネスロジックを統合することです。これは、複数のコントローラーが実際に支払い方法を気にせずに同じUserService.MakeHimPay()を呼び出すことができることを意味します。サービスで行われることは、オブジェクトプロパティの変更と同じくらい簡単な場合もあれば、他のサービスを扱う複雑なロジック(つまり、サードパーティサービスの呼び出し、検証ロジックの呼び出し、または単に何かをデータベースに保存すること)の場合もあります。 )

これは、ドメインオブジェクトからすべてのロジックを削除する必要があるという意味ではありません。ドメインオブジェクトのメソッドにそれ自体で何らかの計算を実行させる方が理にかなっている場合があります。最後の例では、サービスはリポジトリ/ドメインオブジェクト上の冗長レイヤーです。要件の変更に対する優れたバッファーを提供しますが、実際には必要ありません。サービスが必要だと思われる場合は、ドメインオブジェクトではなく、単純な「オブジェクトYのプロパティXの変更」ロジックを実行してみてください。ドメインクラスのロジックは、ゲッター/セッターを介してすべてのフィールドを公開するのではなく、「フィールドからこの値を計算する」に分類される傾向があります。


2
ビジネスロジックを備えたサービスレイヤーを優先するという姿勢は非常に理にかなっていますが、それでも疑問が残ります。私の投稿では、サービスレイヤーをビジネスロジックのないファサードとして説明しているいくつかの立派なソースを引用しました。これはあなたの答えとは正反対ですが、おそらくこの違いを明確にできますか?
Jeroen Vannevel

5
ビジネスロジックの種類と、使用されている言語などの他の要因に本当に依存していることがわかりました。一部のビジネスロジックは、ドメインオブジェクトにうまく適合しません。1つの例は、データベースから結果を引き出した後の結果のフィルタリング/ソートです。これはビジネスロジックですが、ドメインオブジェクトでは意味がありません。サービスは、単純なロジックに最適であるか、ドメインの結果とロジックを変換することが、データの保存やオブジェクトからのデータの計算を扱うときに最も役立つことがわかります。
ファイアロア

8

プログラマがドメインオブジェクトにドメインロジックを配置することをshする理由を説明する最も簡単な方法は、通常、「検証ロジックをどこに配置するのか」という状況に直面することです。たとえば、このドメインオブジェクトを考えます。

public class MyEntity
{
    private int someProperty = 0;

    public int SomeProperty
    {
        get { return this.someProperty; }
        set
        {
            if(value < 0) throw new ArgumentOutOfRangeException("value");
            this.someProperty = value;
        }
    }
}

そのため、セッターにはいくつかの基本的な検証ロジックがあります(負にはできません)。問題は、このロジックを実際に再利用できないことです。ドメインオブジェクトへの変更を実際にコミットするに検証を行う必要がある画面またはViewModelまたはController がある場所。そしてなぜ。セッターを呼び出す際の例外のテストは、トランザクションを開始する前にすべての検証を実行する必要があったため、見苦しいハックです。

だからこそ、人々は検証ロジックをのようなある種のサービス(たとえば)に移しMyEntityValidatorます。その後、エンティティと呼び出しロジックの両方が検証サービスへの参照を取得し、それを再利用できます。

それを行わずに検証ロジックを再利用したい場合は、最終的にエンティティクラスの静的メソッドに配置します。

public class MyEntity
{
    private int someProperty = 0;

    public int SomeProperty
    {
        get { return this.someProperty; }
        set
        {
            string message;
            if(!TryValidateSomeProperty(value, out message)) 
            {
                throw new ArgumentOutOfRangeException("value", message);
            }
            this.someProperty = value;
        }
    }

    public static bool TryValidateSomeProperty(int value, out string message)
    {
        if(value < 0)
        {
            message = "Some Property cannot be negative.";
            return false;
        }
        message = string.Empty;
        return true;
    }
}

これにより、ドメインモデルの "貧弱"が少なくなり、プロパティの隣に検証ロジックが保持されます。これは素晴らしいことですが、静的メソッドが本当に好きな人はいないと思います。


1
あなたの意見では、検証のための最良のソリューションは何ですか?
Flashrunner

3
@Flashrunner-私の検証ロジックは、ビジネスロジックレイヤーに明確にありますが、場合によってはエンティティレイヤーとデータベースレイヤーにも複製されます。ビジネスレイヤーはユーザーに通知するなどして適切に処理しますが、他のレイヤーはエラー/例外をスローするだけで、プログラマー(自分)がデータを破損するようなミスを防ぐことができます。
スコットホイットロック

6

Martin FowlerのAnemic Domain Modelの記事を読むと、答えは明確だと思います。

ドメインであるビジネスロジックをドメインモデルから削除すると、オブジェクト指向の設計が本質的に破壊されます。

最も基本的なオブジェクト指向の概念を見てみましょう。オブジェクトはデータと操作をカプセル化します。たとえば、アカウントの閉鎖は、アカウントオブジェクトが自身で実行する必要がある操作です。したがって、サービス層にその操作を実行させることは、オブジェクト指向ソリューションではありません。これは手続き型であり、マーティンファウラーが貧血領域モデルについて話しているときに言及しているものです。

アカウントオブジェクトを自身で閉じるのではなく、サービスレイヤーでアカウントを閉じる場合、実際のアカウントオブジェクトはありません。アカウントの「オブジェクト」は単なるデータ構造です。マーティン・ファウラーが示唆するように、最終的にはゲッターとセッターが入ったバッグの束になります。


1
編集済み。私は実際、これは非常に有用な説明であると判断し、それが下票に値するとは思わない。
BadHorsie

1
リッチモデルには、主にマイナス面が1つあります。そして、それは開発者がモデルにわずかに関連するものをすべて取り込むことです。オープン/クローズの状態はアカウントの属性ですか?所有者はどうですか?そして銀行?それらはすべてアカウントによって参照されるべきですか?銀行と話をすることで口座を閉鎖しますか、それとも口座を通して直接口座を閉鎖しますか?貧弱なモデルでは、これらの接続はモデルに固有の部分ではなく、他のクラス(それらをサービスまたはマネージャーと呼びます)でこれらのモデルを操作するときに作成されます。
ヒューバート・グジェスコヴィアック

4

サービスレイヤーにビジネスロジックをどのように実装しますか?ユーザーから支払いを行う場合、プロパティから値を差し引くだけでなく、支払いを作成します。

支払い方法では、支払いレコードを作成し、そのユーザーの負債に追加して、これらすべてをリポジトリに保持する必要があります。これをサービスメソッドで行うのは非常に簡単で、操作全体をトランザクションでラップすることもできます。集約されたドメインモデルで同じことを行うと、はるかに問題が多くなります。


2

tl; drバージョン:
私の経験と意見では、ビジネスロジックを持つオブジェクトはドメインモデルの一部である必要があります。データモデルには、ロジックを含める必要はほとんどありません。サービスはおそらくこの2つを結び付け、横断的な関心事(データベース、ロギングなど)に対処する必要があります。ただし、受け入れられた答えは最も実用的なものです。

長いバージョンは、他の人によって暗示されていますが、「モデル」という言葉に曖昧さがあります。投稿では、データモデルとドメインモデルが同じであるかのように切り替わりますが、これはよくある間違いです。「サービス」という言葉にわずかな曖昧さがある場合もあります。

実際には、ドメインオブジェクトに変更を加えるサービスは必要ありません。その理由は、サービスがオブジェクトのすべてのプロパティに対して、そのプロパティの値を変更するためのメソッドを持っている可能性が高いためです。これは問題です。オブジェクトのインターフェイスがある場合(またはない場合でも)、サービスはオープンクローズドプリンシプルに従っていないためです。代わりに、モデルにデータを追加するたびに(ドメインとデータに関係なく)、サービスにさらに機能を追加する必要があります。特定の方法がありますが、これが「エンタープライズ」アプリケーションが失敗するのを私が見た最も一般的な理由です。特に、「エンタープライズ」とは「システム内のすべてのオブジェクトに対するインターフェースを持っている」と考える組織です。インターフェイスに新しいメソッドを追加することを想像できますか、次に、モデルの単一のプロパティに対して、2つまたは3つの異なる実装(アプリ内実装、モック実装、およびデバッグ実装、メモリ内実装)に?私にはひどいアイデアのように聞こえます。

ここにはもう長い問題はありませんが、要点は次のとおりです。ハードコアオブジェクト指向プログラミングでは、関連するオブジェクトの外部の誰もオブジェクト内のプロパティの値を変更できず、オブジェクト内のプロパティの値を参照してください。これは、データを読み取り専用にすることで軽減できます。多くの人が読み取り専用であってもデータを利用している場合など、問題が発生する可能性があり、そのデータのタイプを変更する必要があります。すべての消費者がそれに対応するために変更する必要がある可能性があります。だからこそ、APIをだれでもが使用できるようにする場合、パブリックまたは保護されたプロパティ/データさえも持たないように勧められます。それが、最終的にOOPが発明されたまさにその理由です。

ここでの回答の大部分は、承認済みとマークされているものを除き、すべて問題を曇らせていると思います。承認済みとマークされたものは良いですが、返信する必要があり、一般的には箇条書き4を使用する方法に同意する必要があると感じました。

DDDでは、サービスは、集約ルートに適切に属さない操作がある場合の状況を特に対象としています。多くの場合、サービスの必要性は、正しいルートを使用しなかったことを意味する可能性があるため、ここで注意する必要があります。しかし、あなたがやったと仮定すると、サービスは複数のルートにわたる操作を調整するために使用されるか、時にはドメインモデルをまったく含まない懸念を処理するために使用されます...


1

答えは、ユースケースに依存するということです。しかし、ほとんどの一般的なシナリオでは、サービスレイヤーに配置するビジネスロジックを順守します。あなたが提供した例は本当に簡単なものです。ただし、分離されたシステムまたはサービスについて考え始め、その上にトランザクション動作を追加すると、サービスレイヤーの一部としてそれを実行することが本当に必要になります。

サービスレイヤーのビジネスロジックがないことを引用したソースは、ビジネスレイヤーである別のレイヤーを導入します。多くのシナリオでは、サービス層とビジネス層は1つに圧縮されています。システムをどのように設計するかによります。3つのレイヤーで作業を完了し、装飾を続けてノイズを追加できます。

理想的にできるのは、ドメインモデルで動作して状態を維持するビジネスロジックを含むモデルサービスです。可能な限りサービスを分離するようにしてください。


0

MVCモデルでは、ビジネスロジックとして定義されます。彼がMVCを使用していないのでなければ、どこかにあると主張するのは間違っています。私は、サービスシステムをモジュールシステムに似ていると考えています。関連する機能のセットを素敵なパッケージにまとめることができます。そのサービス層の内部には、あなたと同じ仕事をするモデルがあります。

モデルは、アプリケーションデータ、ビジネスルール、ロジック、および機能で構成されます。 http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller


0

サービス層の概念は、DDDの観点から見ることができます。アーロンノートは彼の答えでそれを言及しました、私はそれについて少し詳しく説明します。

一般的なアプローチは、クライアントのタイプに固有のコントローラーを持つことです。たとえば、Webブラウザー、他のアプリケーション、機能テストなどが考えられます。要求と応答の形式は異なる場合があります。そこで、六角形アーキテクチャを利用するためのツールとしてアプリケーションサービスを使用します。具体的なリクエストに固有のインフラストラクチャクラスを注入します。たとえば、Webブラウザーリクエストを処理するコントローラーは次のようになります。

class WebBroserController
{
    public function purchaseOrder()
    {
        $data = $_POST;

        $responseData =
            new PurchaseOrderApplicationService(
                new PayPalClient(),
                new OrderRepository()
            )
        ;

        $response = new HtmlView($responseData);
    }
}

機能テストを書いている場合、偽の支払いクライアントを使用したいので、おそらくHTML応答は必要ありません。したがって、私のコントローラーは次のようになります。

class FunctionalTestController
{
    public function purchaseOrder()
    {
        $data = $_POST;

        $responseData =
            new PurchaseOrderApplicationService(
                new FakePayPalClient(),
                new OrderRepository(),
                $data
            )
        ;

        return new JsonData($responseData);
    }
}

したがって、アプリケーションサービスは、ビジネスロジックを実行するために設定した環境です。モデルクラスが呼び出されるのは、インフラストラクチャの実装です。

したがって、この観点からあなたの質問に答えます:

単純にコントローラーからロジックを抽出し、代わりにサービス内に配置する手段ですか?

番号。

コントローラーとドメイン間で契約を結ぶことになっていますか?

まあ、それはそう呼ぶことができます。

ドメインとサービスレイヤーの間にレイヤーが必要ですか?

いや。


根本的に異なるアプローチがありますが、それはあらゆる種類のサービスの使用を完全に拒否します。たとえば、David Westは著書Object Thinkingで、すべてのオブジェクトにはその仕事を行うために必要なすべてのリソースが必要であると主張しています。このアプローチにより、たとえば、ORMが破棄されます


-2

記録のために。

SRP:

  1. モデル=データ、ここではセッターとゲッターに進みます。
  2. ロジック/サービス=ここで決定が行われます。
  3. Repository / DAO =ここでは、情報を永続的に保存または取得します。

この場合、次の手順を実行しても問題ありません。

負債が計算を必要としない場合:

userObject.Debt = 9999;

ただし、計算が必要な場合:

userObject.Debt= UserService.CalculateDebt(userObject)

またはまた

UserService.UpdateDebt(userObject)

ただし、永続層で計算が行われる場合、そのようなストアプロシージャは、

UserRepository.UpdateDebt(userObject)

この場合、データベースからユーザーを取得して借金を更新したいので、いくつかのステップ(実際には2つ)でそれを行う必要があり、サービスの関数でそれをラップ/カプセル化する必要はありません。

User userObject=UserRepository.GetUserByName(somename);
UserService.UpdateDebt(userObject)

保存する必要がある場合は、3番目のステップを追加できます

User userObject=UserRepository.GetUserByName(somename);
UserService.UpdateDebt(userObject)
UserRepository.Save(userobject);

提案された解決策について

a)エンド開発者が関数にカプセル化する代わりに、いくつかを書くことを恐れる必要はありません。

b)インターフェイスについては、一部の開発者はインターフェイスが大好きで、彼らは大丈夫ですが、いくつかのケースでは、彼らはまったく必要ありません。

c)サービスの目的は、主に共有/静的関数を使用できるため、属性のないサービスを作成することです。単体テストも簡単です。


これは、「ビジネスロジックはモデルではなくサービス内にあるべきです」
ブヨ

3
文のどのような"We shouldn't be afraid to left the end-developer to write a couple of instead of encapsulate it in a function.『それは私の馬がなければ、私は大学にその年を過ごしていないだろう「?私はルイスブラックを引用することができます』。
マラキ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.