Generic Repository and DDD - Revisited
Join the DZone community and get the full member experience.
Join For FreeGreg Young talks about the generic repository pattern and how to reduce the architectural seam of the contract between the domain layer and the persistence layer. The Repository is the contract of the domain layer with the persistence layer - hence it makes sense to have the contract of the repository as close to the domain as possible. Instead of a contract as opaque as Repository
, it is always recommended that the domain layer looks at something self revealing as CustomerRepository.getCustomerByName(String name)
that explicitly states out the participating entities of the domain. +1 on all his suggestions.
However, he suggests using composition, instead of inheritance to encourage reuse along with encapsulation of the implementation details within the repository itself .. something like the following (Java ized)
public class CustomerRepository implements ICustomerRepository { private Repository<Customer> internalGenericRepository; public IEnumerable<Customer> getCustomersWithFirstNameOf(string _Name) { internalGenericRepository.fetchByQueryObject( new CustomerFirstNameOfQuery(_Name)); //could be hql or whatever } }
Quite some time ago, I had a series of blogs on DDD, JPA and how to use generic repositories as an implementation artifact. I had suggested the use of the Bridge pattern to allow independent evolution of the interface and the implementation hierarchies. The interface side of the bridge will model the domain aspect of the repository and will ultimately terminate at the contracts that the domain layer will use. The implementation side of the bridge will allow for multiple implementations of the generic repository, e.g. JPA, native Hibernate or even, with some tweaking, some other storage technologies like CouchDB or the file system. After all, the premise of the Repository is to offer a transparent storage and retrieval engine, so that the domain layer always has the feel that it is operating on an in-memory collection.
// root of the repository interface public interface IRepository<T> { List<T> read(String query, Object[] params); } public class Repository<T> implements IRepository<T> { private RepositoryImpl repositoryImpl; public List<T> read(String query, Object[] params) { return repositoryImpl.read(query, params); } //.. }
Base class of the implementation side of the Bridge ..
public abstract class RepositoryImpl { public abstract <T> List<T> read(String query, Object[] params); }
One concrete implementation using JPA ..
public class JpaRepository extends RepositoryImpl { // to be injected through DI in Spring private EntityManagerFactory factory; @Override public <T> List<T> read(String query, Object[] params) { //.. }
Another implementation using Hibernate. We can have similar implementations for a file system based repository as well ..
public class HibernateRepository extends RepositoryImpl { @Override public <T> List<T> read(String query, Object[] params) { // .. hibernate based implementation } }
Domain contract for the repository of the entity Restaurant
. It is not opaque or narrow, uses the Ubiquitous language and is self-revealing to the domain user ..
public interface IRestaurantRepository { List<Restaurant> restaurantsByName(final String name); //.. }
A concrete implementation of the above interface. Implemented in terms of the implementation artifacts of the Bridge pattern. At the same time the implementation is not hardwired with any specific concrete repository engine (e.g. JPA or filesystem). This wiring will be done during runtime using dependency injection.
public class RestaurantRepository extends Repository<Restaurant> implements IRestaurantRepository { public List<Restaurant> restaurantsByEntreeName(String entreeName) { Object[] params = new Object[1]; params[0] = entreeName; return read( "select r from Restaurant r where r.entrees.name like ?1", params); } // .. other methods implemented }
One argument could be that the query string passed to the read()
method is dependent on the specific engine used. But it can very easily be abstracted using a factory that returns the appropriate metadata required for the query (e.g. named queries for JPA).
How does this compare with Greg Young's solution ?
Some of the niceties of the above Bridge based solution are ..
- The architecture seam exposed to the domain layer is NOT opaque or narrow. The domain layer works with
IRestaurantRepository
, which is intention revealing enough. The actual implementation is injected using Dependency Injection. - The specific implementation engine is abstracted away and once agian injected using DI. So, in the event of using alternative repository engines, the domain layer is NOT impacted.
- Greg Young suggests using composition instead of inheritance. The above design also uses composition to encapsulate the implementation within the abstract base class
Repository
.
However in case you do not want to have the complexity or flexibility of allowing switching of implementations, one leg of the Bridge can be removed and the design simplified
Opinions expressed by DZone contributors are their own.
Comments