SRPに従う場合、エンティティの検証と保存にはどのように対処すればよいですか?


10

私は最近、クリーンコードやSOLIDに関するさまざまなオンライン記事を読んでいますが、それについて読むほど、何も知らないように感じます。

ASP.NET MVC 3を使用してWebアプリケーションを構築しているUsersControllerとしましょう。たとえば、次のCreateようなアクションがあるとします。

public class UsersController : Controller
{
    public ActionResult Create(CreateUserViewModel viewModel)
    {

    }
}

そのアクションメソッドでは、入力したデータが有効な場合、ユーザーをデータベースに保存します。

さて、単一の責任の原則に従って、オブジェクトは単一の責任を持つべきであり、その責任はクラスによって完全にカプセル化されるべきです。そのすべてのサービスは、その責任と厳密に一致している必要があります。検証とデータベースへの保存は2つの別々の責任であるため、次のようにそれらを処理するために別々のクラスを作成する必要があると思います。

public class UsersController : Controller
{
    private ICreateUserValidator validator;
    private IUserService service;

    public UsersController(ICreateUserValidator validator, IUserService service)
    {
        this.validator = validator;
        this.service= service;
    }

    public ActionResult Create(CreateUserViewModel viewModel)
    {
        ValidationResult result = validator.IsValid(viewModel);

        if (result.IsValid)
        {
            service.CreateUser(viewModel);
            return RedirectToAction("Index");
        }
        else
        {
            foreach (var errorMessage in result.ErrorMessages)
            {
                ModelState.AddModelError(String.Empty, errorMessage);
            }
            return View(viewModel);
        }
    }
}

それは私にはある程度理にかなっていますが、これがこのようなものを処理するための正しい方法であるかどうかはまったくわかりません。たとえばCreateUserViewModelIUserServiceクラスに無効なインスタンスを渡すことは完全に可能です。組み込みのDataAnnotationsを使用できることはわかっていますが、十分でない場合はどうなりますか?ICreateUserValidatorデータベースをチェックして、同じ名前の別のユーザーがすでに存在するかどうかを確認する画像...

もう1つのオプションはIUserService、次のような検証を処理することです。

public class UserService : IUserService
{
    private ICreateUserValidator validator;

    public UserService(ICreateUserValidator validator)
    {
        this.validator = validator;
    }

    public ValidationResult CreateUser(CreateUserViewModel viewModel)
    {
        var result = validator.IsValid(viewModel);

        if (result.IsValid)
        {
            // Save the user
        }

        return result;
    }
}

しかし、私はここで単一責任の原則に違反していると感じています。

このようなことに対処するにはどうすればよいですか?


userクラスは検証を処理すべきではありませんか?SRPかどうかにかかわらず、userインスタンスが有効であるかどうかをインスタンスが認識してはならない理由はわかりません。それを判断するために他の何かに依存する必要があります。クラスには他にどのような責任がありますか?さらに、user変更が行われると検証が変更される可能性があるため、別のクラスにアウトソーシングすると、密結合されたクラスのみが作成されます。
sebastiangeiger

回答:


4

2番目の例では、単一責任原則に違反しているとは思いません。

  • UserServiceクラスが変更に一つだけの理由があります:あなたは、ユーザーの保存方法を変更する必要がある場合。

  • ICreateUserValidatorクラスは変更する唯一の一つの理由があります:あなたは、ユーザーを検証する方法を変更する必要がある場合。

私はあなたの最初の実装がより直感的であることを認めなければなりません。ただし、検証はユーザーを作成するエンティティによって行われる必要があります。作成者自身が検証の責任を負うべきではありません。(2番目の実装の場合のように)責任をバリデータクラスに委任する必要があります。したがって、2番目の設計にはSRPが欠けているとは思いません。


1
単一責任パターン?原則、たぶん?
yannis

はい、もちろん:)修正しました!
Guven

0

最初の例は、真のSRPに「近い」ようです。物事を結び付ける方法とViewModelを渡す方法を知るのはあなたのケースのコントローラの責任です。

IsValid / ValidationMessages全体をViewModel自体の一部にするほうが理にかなっているのではないでしょうか。私はMVVMに手を出してはいませんが、それはプレゼンテーションに直接関連していたため、Presenterがそのようなことを処理しても問題がなかった古いModel-View-Presenterパターンのようです。ViewModelがそれ自体の妥当性をチェックできる場合、UserServiceのCreateメソッドに無効なものを渡すことはできません。


私はいつも、ViewModelsはロジックが多すぎない単純なDTOである必要があると思っていました。ViewModelでデータベースをチェックするようなものを配置する必要があるかどうかわかりません...
Kristof Claes

それはあなたの検証が必要とするものに依存すると思います。ViewModelがIsValidブール値とValidationMessages配列を公開するだけの場合、それでもValidatorクラスを呼び出すことができ、検証の実装方法を気にする必要はありません。コントローラは、最初の例がいることを確認できたif (userViewModel.IsValid) { userService.Create(userViewModel); }。基本的ViewModelには、知っておくべきならば、それは有効ではなく、どのようにそれが有効だ知っています。
Wayne Molina
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.