The Repository Pattern – Part 2
I previously wrote about using the Repository Pattern with Linq to Fluent NHibernate and MySQL. Since that time, I have had the opportunity to refine and extend this approach. I have also noticed quite a few hits on the old post and thought that sharing these refinements would be useful. If you haven’t read the first post, then go read that first…I’ll wait here.
The Repository Interfaces
I refined the repository interfaces a bit to enable the keyed repository to have a generic key. Previously, I had coded an ‘IIntKeyedRepository’ and an ‘IGuidKeyedRepository’. This can be simplified a bit by having a second order generic interface:
public interface IReadOnlyRepository<TEntity> where TEntity:class { IQueryable<TEntity> All(); TEntity FindBy(Expression<Func<TEntity, bool>> expression); IQueryable<TEntity> FilterBy(Expression<Func<TEntity, bool>> expression); } public interface IRepository<TEntity> : IReadOnlyRepository<TEntity> where TEntity:class { bool Add(TEntity entity); bool Add(IEnumerable<TEntity> items); bool Update(TEntity entity); bool Delete(TEntity entity); bool Delete(IEnumerable<TEntity> entities); } public interface IKeyedRepository<TKey, TEntity> : IRepository<TEntity> where TEntity : class, IKeyed<TKey> { TEntity FindBy(TKey id); } public interface IKeyed<TKey> { TKey Id { get; } }
Now all of the keyed entities implement the ‘IKeyed’ interface and the repository is of type ‘IKeyedRepository’. This is a bit simpler and more generic.
Repository Time Bombs
I previously had created an entity that looked like the following:
public class Truck { public virtual int Id { get; private set; } public virtual string Name { get; set; } public virtual string Type { get; set; } public virtual string PlateNumber { get; set; } public virtual Driver Driver { get; private set; } public virtual IList<Location> Locations { get; private set; } }
After some testing, I soon realized the ‘Locations’ property was a “time bomb” waiting to happen. Once the resulting list become large, accessing this property is very slow. This “return every thing in the list” query is not scalable and has been removed.
This does not mean that I have removed all the NHibernate relational mappings between my entities. I am using the following rules:
- If the relationship goes in the direction where only a single object is returned (the ‘one’ end of a ‘one-to-many’) the mapping is safe to keep.
- If the relationship goes in the direction where there is a guaranteed limited small number of items in the list the mapping is probably safe to keep.
- If the relationship can return an unbounded number of items (the ‘many’ end of the ‘one-to-many’) the mapping is not safe and is removed.
Entity Validation
A common task is to validation an entity. This validation may be done at a number of layers. I strongly believe that my entities (domain model) should be as anemic as possible. Implementing the validation logic in the entity goes against the single responsibility principle. I prefer to have my validation classes stand on their own using the following interface:
public interface IValidator<T> { bool IsValid(T entity); IEnumerable<string> BrokenRules(T entity); }
To create a concrete example, let’s say we have a ‘User’ entity defined by the following class:
public class User : IKeyed<int> { public virtual int Id { get; private set; } public virtual string UserName { get; set; } public virtual string FirstName { get; set; } public virtual string LastName { get; set; } public virtual string Email { get; set; } }
Then the validator for this entity will look something like the following:
public class UserValidator : IValidator<User> { private readonly IKeyedRepository<int, User> _userRepo; public UserValidator(IKeyedRepository<int, User> userRepo) { _userRepo = userRepo; } public bool IsValid(User entity) { return BrokenRules(entity).Count() > 0; } public IEnumerable<string> BrokenRules(User entity) { if(string.IsNullOrEmpty(entity.UserName)) { yield return "User name is required."; } else { if(_userRepo.FindBy(x=>x.UserName==entity.UserName)!=null) { yield return "User name is already taken."; } } if(string.IsNullOrEmpty(entity.FirstName)) { yield return "First name is required."; } if(string.IsNullOrEmpty(entity.LastName)) { yield return "Last name is required."; } if(string.IsNullOrEmpty(entity.Email)) { yield return "Email address is required."; } else { const string patternStrict = @"^(([^<>()[].,;:s@""]+" + @"(.[^<>()[].,;:s@""]+)*)|("".+""))@" + @"(([[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}" + @".[0-9]{1,3}])|(([a-zA-Z-0-9]+.)+" + @"[a-zA-Z]{2,}))$"; Regex regexStrict = new Regex(patternStrict); if(!regexStrict.IsMatch(entity.Email)) { yield return "Email must be valid format."; } } yield break; } }
This validator takes as a constructor parameter a reference to the ‘IKeyedRepository’ for the ‘User’ entities. This is used to ensure the ‘UserName’ property is unique in the database. The rest of the validation ensures the require properties exist and are in the expected format.
Service Wrapper
Passing around your generic ‘IRepository’ instances maybe fine for small applications. However, you soon end up with MVC controllers constructors that look like the following:
public HomeController( ILogger logger, IKeyedRepository<int, User> userRepo, IKeyedRepository<int, Truck> truckRepo, IKeyedRepository<int, Location> locationRepo, IUnitOfWork unitOfWork) { _logger = logger; _userRepo = userRepo; _truckRepo = truckRepo; _locationRepo = locationRepo; }
Even though we are using a IOC container to manage the life-time of these parameters, this is quickly becoming un-manageable.
Also, there usually exist cross-cutting queries that a single repository cannot handle. For instance find the current location of a truck given a user. Implementing these queries in higher level (presentation or business logic) layers can cause issues. This also usually ends up violating the DRY principle as the queries are replicated in various places. These queries need to have a home.
For this reason, it is a good idea to create a service layer around the generic repository implementations. Assume that we have the following implemented using our favorite ORM:
public class Repository<T> : IKeyedRepository<int, T> where T : class, IKeyed<int> { // implementation goes here... }
The goal is to create a service layer that provides access to the desired repositories and the implementation of cross-cutting queries. Here is an example:
public class DataService { private readonly IKeyedRepository<int, User> _userRepo; private readonly IKeyedRepository<int, Truck> _truckRepo; private readonly IKeyedRepository<int, Location> _locationRepo; private readonly IUnitOfWork _unitOfWork; public UserService Users { get; private set; } public TruckService Trucks { get; private set; } public LocationService Locations { get; private set; } public DataService(IKeyedRepository<int, User> userRepo, IKeyedRepository<int, Truck> truckRepo, IKeyedRepository<int, Location> locationRepo, IUnitOfWork unitOfWork) { _userRepo = userRepo; _truckRepo = truckRepo; _locationRepo = locationRepo; _unitOfWork = unitOfWork; Users = new UserService(userRepo); Trucks = new TruckService(truckRepo); Locations = new LocationService(locationRepo); } public Location GetCurrentLocation(int truckId) { return _locationRepo .FilterBy(x => x.Id == truckId) .OrderByDescending(c => c.Timestamp) .FirstOrDefault(); } public void Commit() { _unitOfWork.Commit(); } }
The ‘DataService’ class now accepts most of the parameters that were previously injected into the ‘HomeController’. So what have we gained? Since we probably have more than one MVC controller and those other controllers probably had the same large list of constructor parameters we have simplified our controllers and increased the maintainability of our application.
The ‘DataService’ exposes three properties (‘Users’, ‘Trucks’ and ‘Locations’), one additional query (‘GetCurrentLocation’) and the ‘Commit’ method. The additional query is not cross cutting, but you could easily imagine one that did have cross-cutting concerns. The following is an example of one of the properties (the others in the download):
public class UserService { private readonly IKeyedRepository<int, User> _userRepo; private readonly UserValidator _validation; public UserService(IKeyedRepository<int, User> userRepo) { _userRepo = userRepo; _validation = new UserValidator(userRepo); } public IList<User> All() { return _userRepo.All().ToList(); } public User FindBy(Expression<Func<User, bool>> expression) { return _userRepo.FindBy(expression); } public IList<User> FilterBy(Expression<Func<User, bool>> expression) { return _userRepo.FilterBy(expression).ToList(); } public bool Add(User entity, out IEnumerable<string> brokenRules) { if(!_validation.IsValid(entity)) { brokenRules = _validation.BrokenRules(entity); return false; } brokenRules = null; return _userRepo.Add(entity); } public bool Add(IEnumerable<User> items, out IEnumerable<string> brokenRules) { foreach (User item in items) { if(!_validation.IsValid(item)) { brokenRules = _validation.BrokenRules(item); return false; } } brokenRules = null; return _userRepo.Add(items); } public bool Update(User entity, out IEnumerable<string> brokenRules) { if(!_validation.IsValid(entity)) { brokenRules = _validation.BrokenRules(entity); return false; } brokenRules = null; return _userRepo.Update(entity); } public bool Delete(User entity) { return _userRepo.Delete(entity); } public bool Delete(IEnumerable<User> entities) { return _userRepo.Delete(entities); } public User FindBy(int id) { return _userRepo.FindBy(id); } }
The ‘UserService’ essentially implements the ‘IKeyedRepository<int, User>’ interface with two small (but important) differences.
- All the methods in ‘IKeyedRepository’ that return ‘IQueryable’ now return ‘IList’. This means that the power of LINQ to build up queries stops in this layer. This is an important architecture decision. Higher layers are not going to be able to filter the query results further. The reason for this choice is that we want to force all ‘querying’, ‘filtering’ or ‘paging’ code down into this service layer. This help enforce the DRYness of our application.
- Validation logic has been added to the ‘Add’ and ‘Update’ methods. This validation logic is provided by ‘IValidator’ implementations.
Summary
The above refinements and extensions to the previously discussed repository pattern coalesce into a pattern that I have found useful. I hope this additional information is useful to you. If you have additional refinements or suggestions, please feel free to share with the other readers.
This is an excellent post! Thanks very much (found via twitter)
Great post!!
Really nice post,thank you
Hi Bob. Great tutorial.
I’ve followed the same path.
At the beginning I had implemented a repository for aggregate root than I had realized it was time consuming and not really smart.
Now I am using a generic repository.
Since I didn’t want to “contaminate” my domain model with validation rules I thought implementing a service layer, so I can put my validation rules (business rules) there.
Do you have downloadable code for this?
Thanks for the great post.
Alberto
Hi Alberto,
Thanks for taking the time to read and comment. I have posted all the code for this example on codeplex: http://gpsnerd.codeplex.com.
Bob
Thanks for sharing Bob.
Alberto
This is a really good series… I can see you are introducing many architectural patterns however one key thing I noticed… it would be important to return the type of errors back to the ui for them to show appropriate error messages, I wrapped my service calls in try catch and threw errors of a specific type then let the ui layer show them as needed. How would you go about it?
Great posts in this series. It has been a great help.
I like the solid example about how the service layer ties together all the repositories, but how then is the service class created? Would you use DI to inject the DataService? Something like the following:
Bind().To()
Thanks
I am using an IOC to create and manage the lifetime of the DataService. I eventually posted all this code to CodePlex. Here is a link to the page containing the bindings:
http://gpsnerd.codeplex.com/SourceControl/changeset/view/03279dbfcef5#MvcApplication1%2fNinjectFramework%2fAppModule.cs
Thanks for visiting. I am glad you enjoyed the series.
Bob
I don’t know how to define the service layer. Repository layer is somewhat easy to be defined since each repository class relates to an entity in the database,but there’s nothing for me to rely on when defining a service class . Need help 🙂
I have been further refining this repository pattern implementation. I have posted the code on github (https://github.com/rcravens/GenericRepository). You may want to check out that implementation.
Sometimes in my applications, I don’t need a service layer. In these cases the application tends to be very simple and the mapping into entities provided by the repository layer is sufficient. The service layer provides an opportunity to inject in business logic between your application and your repository. In the example in the blog post, I use the service layer as an aggregator of sorts. Pulling together all the repositories to allow easier injection into the controller classes.
Great stuff Bob. That’s how is done!
Greate post as all you have already posted. Thanks for sharing your knowledge and experience.
I have a question regarding to se validator class corresponding to a domain object and it is to know in what layer it should be in the Business layer or in the domain layer.
Thanks in advance.
Hi, I downloaded code form code flex , and i run through vs2013. But its giving run time error. Can you please send me the link which has simple vehicle tracker code.