Sunday, January 18, 2009

Generic Repository and DDD - Revisited

Greg 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.FindAllMatching(QueryObject o), 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.

4 comments:

Travelling Greg said...

Bringing in the extra interface to meet the bridge pattern is fine.

I however have had extremely limited success introducing the extra level of indirection as I find that persistence concerns tend to leak to the domain representation (in particular the repository + the query objects that it uses). An example would be using say SQL directly from your repository ... or using HQL ... you end up switching to a new ORM that no uses OQL (you are still translating all of your queries unless you do a whole lot of up front work (or use someone else's) criteria/composite query object API.

//this is acknowledged in ...


"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)."

This feels like a lot of work and/or complexity/learning curve to save what is generally considered a small amount of risk later in the project's life cycle.

As such I have given up with trying to make "generic" query objects etc and have made the decision that in the unlikely case that I decide to completely switch persistence providers that I will be better off doing it at the domain contract boundary anyways.

This is especially true with things like couchdb/bigtable as how you search etc often have differing semantics.

There are however some situations where a bridge pattern is absolutely a good idea. I have just not personally been in these situations (I have also only replaced persistence in a DDD based system once maybe the next 5-10 years will show me that there is more risk associated with this than I thought originally).

Travelling Greg said...

just to be clear (saw it may not read clearly) the use of generic criteria/query objects feels like too much work to me ... but they would be transparent (or nearly transparent) to move. Named queries are pretty easy to deal with.

I would just wonder if named queries are worth the time in terms of what they would later save as I would have to translate all the named queries anyways. Is where are stored that important?

Dimitrios Menounos said...

I have been digging into the generic repository too. My ideas are on my blog as well.

http://zoomblab.blogspot.com/2009/01/data-access-using-command-pattern.html

Also I have an implementation of this, as part of another project, which works beautifully with Spring + Hibernate. It is open source so if one wants to check the code here it is:

http://code.google.com/p/oojo/

My opinion is that you either abstract queries completely or adopt JPA as the standard persistence mechanism and be done with it. It was meant to be used as such after all...

iron9light said...

I wish the query object is Linq or Squeryl, if Linq and Squeryl can work for NoSql or any other kind of DB. They are good typed abstraction for the query logic(better than string).
Someone should try it any show me it's possible :-)