Qual è lo scopo della logica di dominio / business nelle classi quando si hanno repository?

5

Dalla mia esperienza penso che avere classi / modelli senza comportamento solo nella mia applicazione, accanto ai loro repository non sia un buon OOP. Ma questo è il modo in cui ho implementato il modello di repository. Faccio in modo che ovunque io abbia bisogno di un'istanza di repository, per eseguire alcune azioni. Il risultato di questo approccio era che tutte le mie classi di dominio non avevano un comportamento.

Erano solo oggetti contenenti dati senza metodi. La mia insegnante mi ha detto che stavo usando modelli sottili e che dovevo sforzarmi di creare modelli grassi. In risposta a questo feedback, ho implementato alcune logiche di business nelle classi, ma ho riscontrato alcuni problemi:

Esempio:

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


}

Come puoi vedere nell'esempio sopra, prima gestisco alcune logiche di dominio, per eseguire azioni sulla classe stessa e poi lo mantengo nel database usando un repository. Ma perché sto addirittura aggiungendo i post alla classe stessa nel metodo AddPost, quando lo gestisco con un repository subito dopo?

Questo è solo perché ora puoi effettivamente vedere le modifiche apportate direttamente sullo schermo, in memoria? Oppure lo scopo della logica di business è solo quello di convalidare l'input del parametro, come mostrato nel metodo Rate del film? Ma questo tipo di eccezioni può anche essere gettato nel repository se il metodo del repository controlla anche se una cifra è compresa tra 0 e 10. Ma penso che un repository non dovrebbe preoccuparsi di ciò. Il repository ha solo bisogno di tradurre input in informazioni di database, ho ragione?

Ma detto questo, non capisco esattamente la necessità di eseguire modifiche sull'oggetto stesso, quando lo gestisci con un repository. Invece di ciò, è possibile (in qualsiasi punto dell'applicazione, per visualizzare l'oggetto utente):

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

Quali sono i pro e i contro di questa differenza?

    
posta Maikkeyy 25.01.2017 - 13:57
fonte

1 risposta

8

What is the purpose of domain/business logic in classes when having repositories?

Questo è un po 'come chiedere:

What is the purpose of cars when we have garages?

Il motivo è che le Business Classes e i Repository risolvono problemi diversi e quindi sono diverse preoccupazioni nell'applicazione. In quanto tali, devono essere in classi separate.

Lo scopo principale di un repository è fornire uno strato di astrazione tra persistenza e codice. Cambiare i fornitori di database o anche i supporti di memorizzazione (database, file flat, servizio Web, ecc.) Non dovrebbe avere importanza al di fuori delle vostre classi di repository.

Lo scopo di una Business Class è di applicare la logica aziendale.

Lo scopo di separare la logica aziendale dalla logica di persistenza è che è possibile applicare la logica di business senza preoccuparsi della persistenza. Forse hai un'importazione di dati. I test unitari quindi non hanno bisogno di un database solo per convalidare le regole aziendali.

Pensa ai requisiti che hai ora:

  1. Una valutazione del film deve avere un utente
  2. Una valutazione del film deve avere un film
  3. Una valutazione del film deve essere compresa tra 0 e 10
  4. Se un utente ha precedentemente valutato un film , la valutazione verrà modificata
  5. Se un utente non ha valutato un film , la valutazione verrà aggiunta
  6. Un utente deve avere un nome utente
  7. Un utente ha zero o più valutazioni del film
  8. Un utente può valutare i film

Nessuno di questi ha qualcosa da fare con l'inserimento, l'aggiornamento, la selezione o l'eliminazione di dati nel database. Di fatto, queste stesse regole potrebbero essere applicate se si cambia persistenza in un file XML.

Ora, considera queste Business Class:

Per prima cosa, uno stupido stub per il film:

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

Ora sappiamo che la valutazione di un film è composta da tre cose: un utente; un film e una valutazione numerica.

La classe utente:

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

La classe 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;
    }
}

Ho inserito commenti nel codice C # per illustrare in che modo le classi Business (Utente, Film e MovieRating) applicano la logica aziendale.

Funzionalità degne di nota di questo codice:

  • Il costruttore per la classe MovieRating è contrassegnato come internal che limita chi può creare istanze di questa classe per codificare all'interno dello stesso assieme della classe.

  • Il metodo RateMovie sulla classe User è public ed è l'unica cosa che crea gli oggetti MovieRating. Questo assicura che tu abbia correttamente collegato l'utente giusto con il film quando lo aggiungi alla raccolta di valutazioni di film privata

  • Il campo User.movieRatings è privato, quindi la classe User ha il controllo completo su come vengono creati i MovieRating

  • La proprietà User.MovieRatings è una IEnumerable<MovieRating> in modo che il codice client deve chiamare il metodo RateMovie sulla classe User per valutare un filmato per quell'utente.

  • Le classificazioni minima e massima sono codificate come costanti nella classe MovieRating

  • Un metodo statico IsValidRating è pubblico, quindi qualsiasi codice, indipendentemente dal fatto che un oggetto MovieRating sia disponibile o meno, ha una posizione centrale per sapere se una valutazione è valida o meno. Pensa ai validatori dei campi del modulo nel livello di presentazione / web della tua applicazione.

  • Il metodo RateMovie trova una valutazione esistente e la modifica, oppure crea un nuovo oggetto MovieRating se non esiste (requisito # 4)

  • Nessuna di queste funzioni ha qualsiasi da fare con il modo in cui i dati vengono inseriti o aggiornati

risposta data 25.01.2017 - 20:49
fonte

Leggi altre domande sui tag