web-dev-qa-db-ja.com

サービスレイヤーはリポジトリの実装にどのように適合しますか?

POCOモデルクラスと永続性を処理するリポジトリクラスを作成しました。 POCOはリポジトリにアクセスできないため、リポジトリには正しくないように見えるビジネスロジックタスクがたくさんあります。私が読んだことから、UIコンシューマーとリポジトリーレイヤーの間に位置するサービスレイヤーが必要なようです。私が確信していないのは正確にそれがどのように機能するはずであるかです...

サービスレイヤーに加えて、別のビジネスロジックレイヤーも必要ですか、それともサービスレイヤーの役割ですか?

リポジトリごとに1つのサービスが必要ですか?

UIがモデルオブジェクトをインスタンス化できる唯一の方法はサービスレイヤーですか、それともリポジトリは新しいモデルインスタンスをサービスに提供しますか?

入力が有効であること、および更新する項目が更新前にデータベースに存在することを確認するなどのチェックを行うサービスレイヤーにパラメーター、モデル、およびその他の検証を配置しますか?

モデル、リポジトリ、UIのすべてがサービスレイヤーを呼び出すことができますか、それともUIが消費するだけですか?

サービス層はすべて静的メソッドであると想定されていますか?

UIからサービスレイヤーを呼び出す一般的な方法は何ですか?

モデルとサービスレイヤーにはどのような検証が必要ですか?

これが既存のレイヤーのサンプルコードです。

public class GiftCertificateModel
{
    public int GiftCerticiateId {get;set;}
    public string Code {get;set;}
    public decimal Amount {get;set;}
    public DateTime ExpirationDate {get;set;}

    public bool IsValidCode(){}
}


public class GiftCertificateRepository
{
    //only way to access database
    public GiftCertificateModel GetById(int GiftCertificateId) { }
    public List<GiftCertificateModel> GetMany() { }
    public void Save(GiftCertificateModel gc) { }
    public string GetNewUniqueCode() { //code has to be checked in db }

    public GiftCertificateModel CreateNew()
    {
        GiftCertificateModel gc = new GiftCertificateModel();
        gc.Code = GetNewUniqueCode();
        return gc;
    }              
}

PDATE:現在、Webフォームと従来のADO.NETを使用しています。最終的にはMVCとEF4に移行したいと思っています。

PDATE: @ Lesterのすばらしい説明に感謝します。これで、リポジトリごとにサービスレイヤーを追加する必要があることがわかりました。このレイヤーは、UIまたは他のサービスがリポジトリと通信できる唯一の方法であり、ドメインオブジェクトに適合しない検証(例:リポジトリを呼び出す必要がある検証)を含みます。

public class GiftCertificateService()
{

    public void Redeem(string code, decimal amount)
    {
        GiftCertificate gc = new GiftCertificate();
        if (!gc.IsValidCode(code))
        {
            throw new ArgumentException("Invalid code");
        }

        if (amount <= 0 || GetRemainingBalance(code) < amount)
        {
            throw new ArgumentException("Invalid amount");
        }

        GiftCertificateRepository gcRepo = new GiftCertificateRepository();
        gcRepo.Redeem(code, amount);
    }

    public decimal GetRemainingBalance(string code)
    {
        GiftCertificate gc = new GiftCertificate();            
        if (!gc.IsValidCode(code))
        {
            throw new ArgumentException("Invalid code");
        }

        GiftCertificateRepository gcRepo = new GiftCertificateRepository();
        gcRepo.GetRemainingBalance(code);
    }

    public SaveNewGC(GiftCertificate gc)
    {
        //validates the gc and calls the repo save method
        //updates the objects new db ID
    }

}

質問

  1. モデルと同じ(場合によってはそれ以上の)プロパティ(量、コードなど)をサービスに追加しますか、それともGiftCertificateオブジェクトと直接パラメーターを受け入れるメソッドのみを提供しますか?

  2. Serviceコンストラクターが呼び出されたときにGiftCertificateエンティティのデフォルトのインスタンスを作成するか、または必要に応じて新しいインスタンスを作成するか(たとえば、エンティティの検証メソッドを呼び出す必要があるサービスの検証メソッドの場合)?また、デフォルトの作成に関する同じ質問リポジトリインスタンス...?

  3. サービスを介してリポジトリの機能を公開していることを知っていますが、エンティティからのメソッドも公開していますか(IsValidCodeなど)?

  4. UIがサービスを経由せずに新しいGiftCertificateオブジェクトを直接作成するだけでも問題ありません(たとえば、エンティティからパラメーター検証メソッドを呼び出す)。そうでない場合、それを強制する方法は?

  5. UIレイヤーで、新しいギフト券を作成する場合、UIレイヤーから直接モデル/サービス検証(IsValidExpirationDateなど)を呼び出しますかOR最初にオブジェクトをハイドレイトしますか? 、次にそれを渡して検証し、ある種の検証の要約をUIに返しますか?

また、UIレイヤーから利用する場合は、最初にUIからモデル/サービス検証メソッドを呼び出してユーザーフィードバックを提供し、次に同じチェックを内部で再度実行する利用メソッドを呼び出しますか?

Iから引き換え操作を実行するためにサービスを呼び出す例:

string redeemCode = RedeemCodeTextBox.Text;

GiftCertificateService gcService = new GiftCertificateService();
GiftCertificate gc = new GiftCertificate(); //do this to call validation methods (should be through service somehow?)

if (!gc.IsValid(redeemCode))
{
    //give error back to user
}

if (gcService.GetRemainingBalance(redeemCode) < amount)
{
    //give error back to user
}

//if no errors
gcService.Redeem(code,amount);

Iから新しいギフト証明書を作成する例:

GiftCertificateService gcService = new GiftCertificateService();
GiftCertificate gc = new GiftCertificate();

if (!gc.IsValidExpDate(inputExpDate))
{
    //give error to user..
}

//if no errors...
gc.Code = gcService.GetNewCode();
gc.Amount = 10M;
gc.ExpirationDate = inputExpDate;
gcService.SaveNewGC(gc);
//method updates the gc with the new id...

GCの作成方法、および検証がエンティティ/サービス間でどのように分離されているかについて、何かがおかしいと感じています。ユーザー/消費者は、どの検証がどの場所にあるかについて心配する必要はありません...アドバイス?

52
jpshook

S#arp Architeture を見てください。これは、ASP.NETMVCアプリケーションを構築するためのベストプラクティスのアーキテクチャフレームワークのようなものです。一般的なアーキテクチャパターンでは、エンティティごとに1つのリポジトリがあり、データアクセスのみを担当し、リポジトリごとに1つのサービスがあり、ビジネスロジックと、コントローラとサービス間の通信のみを担当します。

S#arpアーキテクチャに基づく質問に答えるには:

サービス層に加えて、別のビジネスロジック層も必要ですか、それともサービス層の役割ですか?

モデルはフィールドレベルの検証(例:必須のフィールド属性の使用)を担当する必要がありますが、コントローラーは保存前にデータを検証できます(例:保存前の状態の確認)。

リポジトリごとに1つのサービスレイヤーが必要ですか?

はい-リポジトリごとに1つのサービスが必要です(リポジトリごとに1つのサービスレイヤーではありませんが、それを意味していると思います)。

UIがモデルオブジェクトをインスタンス化できる唯一の方法はサービスレイヤーですか、それともリポジトリは新しいモデルインスタンスをサービスに提供しますか?

リポジトリとサービスは、必要に応じて、単一のエンティティ、エンティティのコレクション、またはデータ転送オブジェクト(DTO)を返すことができます。コントローラーはこれらの値をモデルの静的コンストラクターメソッドに渡し、モデルのインスタンスを返します。

ex DTOの使用:

_GiftCertificateModel.CreateGiftCertificate(int GiftCerticiateId, string Code, decimal Amount, DateTime ExpirationDate)
_

入力が有効であること、および更新する項目が更新前にデータベースに存在することを確認するなどのチェックを行うサービスレイヤーにパラメーター、モデル、およびその他の検証を配置しますか?

モデルはフィールドレベルの値を検証します。必須フィールド、年齢または日付範囲などをチェックして、入力が有効であることを確認します。サービスは、モデル値の外部をチェックする必要がある必要な検証を行う必要があります。ギフト券がまだ利用されていないことを確認し、ギフト券の対象となるストアのプロパティを確認します)。

モデル、リポジトリ、UIはすべてサービスレイヤーを呼び出すことができますか、それともUIが消費するだけですか?

コントローラと他のサービスは、サービス層を呼び出す唯一のものでなければなりません。リポジトリに電話をかけるのはサービスだけでなければなりません。

サービスレイヤーはすべて静的メソッドであると想定されていますか?

それらは可能ですが、そうでない場合は保守と拡張が簡単です。エンティティ/サブクラスごとに1つのサービスがある場合、エンティティへの変更とサブクラスの追加/削除は簡単に変更できます。

UIからサービスレイヤーを呼び出す一般的な方法は何ですか?

サービス層を呼び出すコントローラーの例:

_giftCertificateService.GetEntity(giftCertificateId); (which in turn is just a call to the giftCertificateRepository.GetEntity(giftCertificateId)

giftCertificateService.Redeem(giftCertificate);
_

モデルとサービスレイヤーでどのような検証を行う必要がありますか?

すでに上記で答えました。

[〜#〜]更新[〜#〜]

WebFormsを使用しているため、いくつかの概念を理解するのは少し難しいかもしれませんが、私が説明しているのは一般的なMVCパラダイムであるため、私が言及したすべてが当てはまります。データアクセスはリポジトリを介して分離されているため、データアクセス用のADO.NETは重要ではありません。

モデル(金額、コードなど)と同じ(場合によってはさらに多くの)プロパティをサービスに追加しますか、それともGiftCertificateオブジェクトとダイレクトパラメータを受け入れるメソッドのみを提供しますか?

サービスは、サービスの名前が示すとおりに、つまりコントローラーが呼び出すことができるアクションとして見る必要があります。モデルで定義されているプロパティは既にモデルで使用できるため、必要ありません。

Serviceコンストラクタが呼び出されたときにGiftCertificateエンティティのデフォルトのインスタンスを作成するか、または必要に応じて新しいインスタンスを作成するか(たとえば、エンティティの検証メソッドを呼び出す必要があるサービスの検証メソッドの場合) ?また、デフォルトのリポジトリインスタンスの作成に関する同じ質問...?

コントローラとサービスには、それぞれサービスとリポジトリのプライベートフィールドが必要です。すべてのアクション/メソッドをインスタンス化する必要はありません。

サービスを介してリポジトリの機能を公開していることは知っていますが、エンティティからのメソッドも公開していますか(例:IsValidCodeなど)?

ここで何を意味するのかよくわかりません。サービスがエンティティを返す場合、エンティティのこれらのメソッドはすでに公開されています。彼らがDTOを返す場合、それはあなたが特定の情報だけに興味があることを意味します。

検証については、モデルに対して直接行われる検証と、サービスで行われる他のタイプの検証があるため、なぜあなたが少し心配しているのかがわかります。私が使用した経験則では、検証でデータベースへの呼び出しが必要な場合は、サービス層で行う必要があります。

UIは、サービスを経由せずに直接新しいGiftCertificateオブジェクトを作成するだけで問題ありません(たとえば、エンティティからパラメーター検証メソッドを呼び出す場合)。そうでない場合、どのように強制しますか?

UIレイヤーで、新しいギフト券を作成する場合、UIレイヤーから直接モデル/サービス検証(IsValidExpirationDateなど)を呼び出しますかOR最初にオブジェクトをハイドレイトし、次にそれを渡して検証し、次に何らかの検証サマリーをUIに返しますか?

これらの2つの質問について、シナリオを見てみましょう。

ユーザーは情報を入力して新しい証明書を作成し、送信します。フィールドレベルの検証があるため、テキストボックスがnullの場合、または金額が負の場合、検証エラーがスローされます。すべてのフィールドが有効であると仮定すると、コントローラーはサービスgcService.Save(gc)を呼び出します。

サービスは他のビジネスロジックをチェックします。たとえば、ストアが既に発行したギフト券が多すぎるかどうかなどです。複数のエラーコードがある場合、ステータスの列挙型を返すか、エラー情報とともに例外をスローします。

最後に、サービスはgcRepository.Save(gc)を呼び出します。

42
Lester
  1. エンティティごとにリポジトリを作成する必要はありません 詳細はこちらを参照

    通常、ドメイン内のアグリゲートごとにリポジトリを定義します。つまり、エンティティごとにリポジトリはありません!単純な注文入力システムを見ると、エンティティOrderがOrder集計のルートである可能性があります。したがって、注文リポジトリがあります。

  2. リポジトリごとに1つのサービスが必要ですか? -> 1つのサービスで複数のリポジトリを使用する可能性があるため、常にではありません。

  3. サービスはモデルインスタンスを作成します。リポジトリはモデルと対話することはありません。実際、モデルが後で使用するエンティティを返します。

  4. 入力/範囲などの種類の検証をUIレベルで処理し(uはjavascriptまたはその他のライブラリを使用できます)、サービスにビジネスの側面のみを処理させます。同じことをする属性の利点を得ることができます。

  5. UI-> Service-> Repository、リポジトリがサービスを呼び出している場合、thrは何か間違ったIMOである必要があります。


変更をコーディングし、

  1. モデルとリポジトリを分離します。

    public class GiftCertificateModel
    {
    }
    public class GiftCertificateRepository
    {
       //Remove Model related code from here, and just put ONLY database specific code here, (no business logic also). Common methods would be Get, GetById, Insert, Update etc. 
    
        Since essence of Repository is to have common CRUD logic at one place soyou don't have to write entity specific code. 
        You will create entity specific repository in rare cases, also by deriving base repository.
    
    }
    
    public class GiftCertificateService()
    {
        //Create Model instance here
        // Use repository to fill the model (Mapper)
    
    }
    
3
paragy

GiftCertificateServiceというサービスを作成できます。

そうすれば、GiftCertificateModelの責任に属さないタスクをそのサービスに調整できます。 (WCFサービスと混同しないでください)。

サービスはすべてのタスクを制御するため、UI(または可能性のある呼び出し元)はサービスで定義されたメソッドを使用します。

次に、サービスはモデルのメソッドを呼び出し、リポジトリを使用し、トランザクションを作成します。

例: (提供したサンプルコードに基づく):

public class GiftCertificateService 
{
   public void CreateCertificate() 
   {
      //Do whatever needs to create a certificate.
      GiftCertificateRepository gcRepo = new GiftCertificateRepository();
      GiftCertificateModel gc = gcRepo.CreateNew();
      gc.Amount = 10.00M;
      gc.ExpirationDate = DateTime.Today.AddMonths(12);
      gc.Notes = "Test GC";
      gcRepo.Save(gc);
   }
}

IはCreateCertificateメソッドを呼び出し(引数を渡すなど)、メソッドは何かを返す場合もあります。

注:クラスをUIで動作させたい場合は、コントローラークラス(MVCを実行している場合)またはプレゼンタークラス(MVVMを実行していて、すべてをViewModel内に配置したくない場合)を作成して使用します。そのクラスのGiftCertificateService。

0
Nikos Baxevanis