web-dev-qa-db-ja.com

リポジトリがある場合のクラスのドメイン/ビジネスロジックの目的は何ですか?

私の経験から、私のアプリケーションでのみ、振る舞いのないクラス/モデルをそれらのリポジトリの隣に置くことは、良いOOPではないと思います。しかし、これがリポジトリパターンを実装する方法でした。私はいくつかのアクションを実行するために、リポジトリインスタンスが必要なところすべてに作成します。このアプローチの結果、すべてのドメインクラスに動作がありませんでした。

それらはメソッドを持たないデータを保持するオブジェクトにすぎません。先生は、私は薄いモデルを使っていて、太ったモデルを作ろうと努力すべきだと言った。そのフィードバックに応えて、クラスにいくつかのビジネスロジックを実装しましたが、いくつかの問題が発生しました。

例:

public class Movie
{
    private MovieRepository movieRepo = new MovieRepository(new MovieDbContext());
    private PostRepository postRepo = new PostRepository(new PostDbContext());

    public decimal Rating { get; set; }
    public List<Post> Posts { get; set; }

    public void Rate(User user, Movie movie)
    {
        if(movie.Rating < 0 || movie.Rating > 10) 
        {
            throw new Exception("The rating must be a digit between 0 and 10");
        }
        this.Rating = movie.Rating;
        movieRepo.RateMovie(user.Id, movie.Id, (int)movie.Rating);
    }

    public void AddPost(User user, Movie movie, string text)
    {
        int maxId = 0;
        foreach (Post p in Posts)
        {
            if (p.Id > maxId)
            {
                maxId = p.Id;
            }
        }

        Post post = new Post(maxId + 1, user, text, DateTime.Now);
        this.Posts.Add(post);

        postRepo.AddPost(user.Id, movie.Id, text, DateTime.Now);
    }


}

上の例でわかるように、まずドメインロジックを処理してクラス自体にアクションを実行し、次にリポジトリを使用してデータベースに永続化します。しかし、直後にリポジトリでそれを処理するときに、AddPostメソッドでクラス自体に投稿を追加するのはなぜですか?

これは、実際に画面上、メモリ内で直接行った変更を実際に確認できるからですか?または、ムービーレートメソッドに示されているように、ビジネスロジックの目的はパラメーターの入力を検証することだけですか?しかし、リポジトリメソッドが数字が0と10の間にあるかどうかもチェックする場合、これらの種類の例外はリポジトリでもスローされる可能性があります。リポジトリは入力をデータベース情報に変換する必要があるだけですよね?

しかし、そうは言っても、リポジトリーでオブジェクトを処理するときに、オブジェクト自体に変更を加える必要性を正確には理解していません。その代わりに、これを行うことができます(アプリケーション内の任意の場所で、ユーザーオブジェクトを表示します)。

postRepo.AddPost(2, 1, "Nice movie!", DateTime.Now);
User user = userRepo.GetById(2);

この違いの長所と短所は何ですか?

6
Maikkeyy

リポジトリがある場合にクラスのドメイン/ビジネスロジックの目的は何ですか?

これは一種の質問のようなものです:

ガレージがある場合、carsの目的は何ですか?

その理由は、ビジネスクラスとリポジトリはさまざまな問題を解決するため、アプリケーションではさまざまな懸念事項であるためです。そのため、それらは別々のクラスにある必要があります。

リポジトリの主な目的は、永続性とコードの間の抽象化レイヤーを提供することです。データベースベンダーやストレージメディア(データベース、フラットファイル、Webサービスなど)の切り替えは、Repositoryクラスの外では問題になりません。

ビジネスクラスの目的は、ビジネスロジックを実施することです。

ビジネスロジックを永続性ロジックから分離する目的は、永続性を気にすることなくビジネスロジックを適用できるようにすることです。多分あなたはデータのインポートを持っています。ユニットテストでは、ビジネスルールを検証するためだけにデータベースは必要ありません。

現在の要件について考えてみましょう。

  1. 映画の評価にはユーザーが必要です
  2. 映画の評価には映画が必要です
  3. A 映画の評価は0から10の間でなければなりません
  4. ユーザーが以前に映画を評価したことがある場合、評価は変更されます
  5. ユーザー映画を評価していない場合、評価が追加されます
  6. ユーザーにはユーザー名が必要です
  7. A ユーザーは0以上の映画の評価を持っています
  8. A ユーザー映画を評価できます

これらのどれも、データベース内のデータの挿入、更新、選択、または削除に関連する何かを持ちません。実際、永続性をXMLファイルに切り替える場合にも、これらの同じルールを適用できます。

ここで、これらのビジネスクラスを検討してください。

まず、映画の愚かなスタブ:

public class Movie
{
    public int Id { get; set; }
    public string Title { get; set; }
}

これで、映画の評価は3つの要素で構成されていることがわかりました。映画、そしてナンバーレーティング。

Userクラス:

public class User
{
    public User(string username)
    {
        // Requirement #6
        if (string.IsNullOrEmpty(username))
            throw new ArgumentNullException("username");

        Username = username;

        // Requirement #7
        movieRatings = new Collection<MovieRating>();
    }

    // Requirement #6
    public string Username { get; private set; }

    // Requirement #7
    private ICollection<MovieRating> movieRatings;

    // Requirement #6
    public IEnumerable<MovieRating> MovieRatings
    {
        get { return movieRatings; }
    }

    // Requirement #4
    public MovieRating GetRating(Movie movie)
    {
        return MovieRatings.FirstOrDefault(rating => rating.Movie.Id == movie.Id);
    }

    // Requirement #8 and #1
    public MovieRating RateMovie(Movie movie, int rating)
    {
        // Requirement #2
        if (movie == null)
            throw new ArgumentNullException("movie");

        var movieRating = GetRating(movie);

        if (movieRating == null)
        {
            // Requirement #5
            movieRating = new MovieRating(this, movie, rating);
            movieRatings.Add(movieRating);
        }
        else
        {
            // Requirement #4
            movieRating.ChangeRating(rating);
        }

        return movieRating;
    }
}

MovieRatingクラス:

public class MovieRating
{
    // Requirement #8 and #1
    internal MovieRating(User user, Movie movie, int rating)
    {
        // Requirement #1
        if (user == null)
            throw new ArgumentNullException("user");

        // Requirement #2
        if (movie == null)
            throw new ArgumentNullException("movie");

        // Requirement #3
        if (IsValidRating(rating))
            throw new ArgumentOutOfRangeException("rating", "Rating must be between " + MIN_RATING + " and " + MAX_RATING);

        User = user;
        Movie = moview;
        Rating = rating;
    }

    public User User { get; private set; }
    public User Movie { get; private set; }
    public int Rating { get; private set; }

    // Requirement #3
    public const int MIN_RATING = 0;
    public const int MAX_RATING = 10;

    // Requirement #3
    public static bool IsValidRating(int rating)
    {
        return rating >= MIN_RATING && rating <= MAX_RATING;
    }

    // Requirement #4
    public void ChangeRating(int newRating)
    {
        // Requirement #3
        if (IsValidRating(newRating))
            throw new ArgumentOutOfRangeException("newRating", "Rating must be between " + MIN_RATING + " and " + MAX_RATING);

        Rating = newRating;
    }
}

ビジネスクラス(User、Movie、MovieRating)がビジネスロジックを実施する方法を示すために、C#コードにコメントを入れました。

このコードの注目すべき機能:

  • MovieRatingクラスのコンストラクターはinternalとマークされ、このクラスのインスタンスを作成して、クラスと同じAssembly内にコーディングできるユーザーを制限します。

  • RateMovieクラスのUserメソッドはpublicであり、MovieRatingオブジェクトを作成する唯一のメソッドです。これにより、非公開の映画の評価コレクションに追加するときに、適切なユーザーが映画に正しくリンクされていることが保証されます

  • User.movieRatingsフィールドはプライベートなので、UserクラスはMovieRatingの作成方法を完全に制御できます

  • User.MovieRatingsプロパティはIEnumerable<MovieRating>そのため、クライアントコードmustRateMovieクラスのUserメソッドを呼び出して、そのユーザー。

  • 最小および最大の評価は、MovieRatingクラスの定数としてコード化されています。

  • 静的IsValidRatingメソッドはパブリックであるため、MovieRatingオブジェクトが使用可能かどうかに関係なく、どのコードでも、評価が有効かどうかを確認する中心的な場所が1つあります。アプリケーションのプレゼンテーション/ Webレイヤーのフォームフィールドバリデーターを考えてください。

  • RateMovieメソッドは既存の評価を見つけて変更するか、存在しない場合は新しいMovieRatingオブジェクトを作成します(要件#4)。

  • これらの機能はどれも何もありませんデータの挿入または更新方法に関係します

12
Greg Burghardt