From my experience I think that having classes/models without behaviour only in my application, next to their repositories is not good OOP. But, this was the way I implemented the repository pattern. I just make everywhere I need an repository instance, to perform some actions. The result of this approach was that all my domain classes didn't have behaviour.
They were just objects holding data with no methods. My teacher said to me that I was using thin models and that I should strive to make fat models. In response to that feedback, I implemented some business logic in the classes, but I ran into some problems:
Example:
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);
}
}
As you can see in the example above, I first handle some domain logic, to perform actions on the class itself and then I persist it to the database using a repository. But why am I even adding the posts to the class itself in the AddPost-method, when I handle it with a repository right after?
Is this just because you can now actually see the changes you made directly on the screen, in-memory? Or is business logic's purpose only to validate parameter's input, as shown in the movie Rate method? But these kind of exceptions can also be thrown in the repository if the repository method also checks if a digit is between 0 and 10. But i think a repository shouldn't be concerned about that. The repository only needs to translate input to database information, am I right?
But having said that, I don't understand exactly the need of performing changes on the object itself, when you handle it with a repository. Instead of that you could to this(anywhere in your application, to display the user object):
postRepo.AddPost(2, 1, "Nice movie!", DateTime.Now);
User user = userRepo.GetById(2);
What are the pros and cons of this difference?
Best Answer
This is kind of like asking:
The reason is that Business Classes and Repositories solve different problems, and therefore are different Concerns in the application. As such, they need to be in separate classes.
A Repository's main purpose is to provide a layer of abstraction between persistence and your code. Switching database vendors, or even storage mediums (database, flat file, web service, etc) shouldn't matter outside of your Repository classes.
The purpose of a Business Class is to enforce business logic.
The purpose of separating business logic from persistence logic is so you can apply business logic without worrying about persistence. Maybe you've got a data import. Unit tests then don't need a database just to validate business rules.
Think of the requirements you have now:
None of these have anything to do with inserting, updating, selecting or deleting data in the database. In fact, these same rules could be applied if you switch persistence to an XML file.
Now, consider these Business Classes:
First, a silly stub for the Movie:
Now we know a Movie Rating is composed of three things: A user; a movie, and a number rating.
The User class:
The MovieRating class:
I've put comments in the C# code to illustrate how the Business classes (User, Movie and MovieRating) enforce business logic.
Noteworthy features of this code:
The constructor for the
MovieRating
class is markedinternal
restricting who can create instances of this class to code inside the same Assembly as the class.The
RateMovie
method on theUser
class ispublic
and is the only thing that creates the MovieRating objects. This ensures that you have correctly linked the right User with the movie when adding it to the private movie ratings collectionThe
User.movieRatings
field is private so the User class has full control over how MovieRating's are createdThe
User.MovieRatings
property is anIEnumerable<MovieRating>
so that client code must call theRateMovie
method on theUser
class in order to rate a movie for that user.The minimum and maximum ratings are codified as constants on the MovieRating class
A static
IsValidRating
method is public so any code, regardless of whether or not a MovieRating object is available, has one central place to know if a rating is valid or not. Think form field validators in the presentation/web layer of your application.The
RateMovie
method finds an existing rating and changes it, or creates a new MovieRating object if one doesn't exist (requirement #4)None of these features have anything to do with how data is inserted or updated