web-dev-qa-db-ja.com

「ビジネスロジックは、モデルではなくサービス内にある必要があります」はどの程度正確ですか?

状況

今晩、StackOverflowに関する質問に answer を付けました。

質問:

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

たとえば、借金があるユーザーがいるとします。彼の借金を変えたい。 UserRepositoryまたはサービス(たとえば、BuyingService)でオブジェクトを取得して編集し、保存することでそれを行う必要がありますか?

私の答え:

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

状況の例:

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
    }
}

受け取ったコメント:

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

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

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

たとえば、貧血ドメインモデルのアンチパターンに関するMartin Fowlerによる この記事 を例にとります。

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

(...)ドメインオブジェクトに含める必要があるロジックは、ドメインロジック(検証、計算、ビジネスルール)です。

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

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

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

どちらが強化されるか ここ

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

そして ここ

サービス層には、アプリケーションまたはビジネスロジックが含まれておらず、主にいくつかの問題に焦点を当てる必要があります。ビジネスレイヤーの呼び出しをラップし、クライアントが理解できる共通言語にドメインを翻訳し、サーバーと要求元クライアント間の通信媒体を処理する必要があります。

これは、サービスレイヤーについて説明する その他のリソース とは対照的です。

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

または、私がすでにリンクしている質問の 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;
    }
}

結論

全体として、それほど大きな変化はありません。コントローラーのコードがサービスレイヤーに移動しました(これは良いことなので、このアプローチには利点があります)。しかし、これは私の元の答えとは何の関係もないように見えます。

デザインパターンはガイドラインであり、可能な限り実装するために定められたルールではないことを理解しています。しかし、私はサービス層とそれをどのように見なすべきかについての明確な説明を見つけていません。

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

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

  • ドメインとサービス層の間に層があるべきですか?

そして最後に重要なことですが、元のコメントに従ってください

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

  • これは正しいです?

    • モデルではなくサービスにビジネスロジックをどのように導入しますか?
408
Jeroen Vannevel

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

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

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

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

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

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

aサービス」に関しては、Bob MartinはFooBarServiceという名前のクラスを持つことに反対するでしょう。それはオブジェクト指向ではありません。サービスは何をしますか? FooBarsに関連するすべてFooBarUtilsというラベルが付いている場合もあります。彼はサービスレイヤー(より適切な名前はビジネスロジックレイヤーになるでしょう)を提唱すると思いますが、そのレイヤーのすべてのクラスには意味のある名前が付けられます。

41
Daniel Kaplan

私は今、グリーンフィールドプロジェクトに取り組んでおり、昨日はほとんどアーキテクチャ上の決定を行わなければなりませんでした。おかしなことに、「エンタープライズアプリケーションアーキテクチャのパターン」のいくつかの章を再訪する必要がありました。

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

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

最終的には次のようになります。

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

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

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

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

以下の図は、これをかなりよく示しています。

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

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

26
CodeART

Martin FowlerのAnemic Domain Modelの記事 を読めば、答えは明らかだと思います。

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

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

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

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

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

9
firelore

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

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、またはコントローラがある場所before実際にドメインオブジェクトへの変更をコミットします。なぜなら、ユーザーが[保存]ボタンをクリックする前またはクリックしたときに、彼らはそれを行うことができません、そしてwhy。セッターを呼び出すときの例外のテストは醜いハックです。トランザクションを開始する前にすべての検証を実行する必要があったからです。

これが、検証ロジックを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;
    }
}

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

8
Scott Whitlock

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

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

4
Mr Cochese

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

他の人によってほのめかされている長いバージョンは、Wordの「モデル」に等価なものがあるということです。投稿は、データモデルとドメインモデルが同じであるかのように切り替わりますが、これはよくある間違いです。また、「サービス」という言葉に多少の誤解があるかもしれません。

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

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

ここでの回答の大部分は、承認済みとマークされたものを除いて、すべて問題を曇らせていると思います。承認済みとマークされたものは良いですが、私は返信する必要性をまだ感じていて、一般的には4弾が進むべき道であることに同意します。

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

2
WolfgangSenff

サービス層の概念は、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を破棄 になります。

1
Zapadlo

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

ビジネスロジックがないサービスレイヤーについて引用したソースは、ビジネスレイヤーである別のレイヤーを紹介します。多くのシナリオで、サービス層とビジネス層は1つに圧縮されます。それは、実際にシステムをどのように設計するかによって異なります。あなたは3つの層で仕事を成し遂げて、装飾を続けて、ノイズを加えることができます。

理想的にできることは、ドメインモデルで動作して状態を永続化するビジネスロジックを含むmodel servicesです。サービスをできるだけ分離する必要があります。

1
sunny

MVCモデルでは、ビジネスロジックとして定義されます。彼がMVCを使用していない限り、それが他のどこかにあるべきであるという主張は正しくありません。サービスレイヤーをモジュールシステムと同様に表示します。これにより、関連する機能のセットをNiceパッケージにバンドルできます。そのサービス層の内部には、あなたと同じ働きをするモデルがあります。

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

0
stonemetal