Грег Янг говорит об общем шаблоне репозитория и о том, как уменьшить архитектурный шов контракта между уровнем домена и уровнем персистентности. Репозиторий является контрактом доменного уровня с постоянным уровнем — поэтому имеет смысл иметь контракт репозитория как можно ближе к домену. Вместо такого непрозрачного контракта , всегда рекомендуется, чтобы уровень домена смотрел на что-то самообнаруживающееся, поскольку это явно указывает на участвующие объекты домена. +1 на все его предложения. Тем не менее, он предлагает использовать композицию вместо наследования для стимулирования повторного использования вместе с инкапсуляцией деталей реализации в самом репозитории … что-то вроде следующего (Java ized)Repository
CustomerRepository.getCustomerByName(String name)
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 } }
Некоторое время назад у меня была серия блоговна DDD, JPA и как использовать универсальные репозитории в качестве артефакта реализации. Я предложил использовать шаблон Bridge, чтобы обеспечить независимую эволюцию интерфейса и иерархий реализации. Интерфейсная сторона моста будет моделировать аспект домена репозитория и в конечном итоге будет заключать контракты, которые будет использовать уровень домена. Сторона реализации моста позволит использовать несколько реализаций универсального репозитория, например, JPA, собственный Hibernate или даже, с некоторыми изменениями, некоторые другие технологии хранения, такие как CouchDB или файловая система. В конце концов, предпосылка репозитория заключается в том, чтобы предложить прозрачный механизм хранения и поиска, чтобы у уровня домена всегда было ощущение, что он работает с коллекцией в памяти.
// 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); } //.. }
Базовый класс реализации стороны моста.
public abstract class RepositoryImpl { public abstract <T> List<T> read(String query, Object[] params); }
Одна конкретная реализация с использованием 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) { //.. }
Еще одна реализация, использующая Hibernate. Мы также можем иметь аналогичные реализации для репозитория на основе файловой системы.
public class HibernateRepository extends RepositoryImpl { @Override public <T> List<T> read(String query, Object[] params) { // .. hibernate based implementation } }
Контракт домена для хранилища объекта Restaurant
. Он не является непрозрачным или узким, использует вездесущий язык и является самообнаруживающимся для пользователя домена.
public interface IRestaurantRepository { List<Restaurant> restaurantsByName(final String name); //.. }
Конкретная реализация вышеуказанного интерфейса. Реализовано в терминах реализации артефактов паттерна Bridge. В то же время реализация не связана с каким-либо конкретным конкретным механизмом хранилища (например, JPA или файловой системой). Эта проводка будет выполняться во время выполнения с использованием внедрения зависимостей.
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 }
Одним из аргументов может быть то, что строка запроса, переданная read()
методу, зависит от конкретного используемого механизма. Но его очень легко абстрагировать, используя фабрику, которая возвращает соответствующие метаданные, необходимые для запроса (например, именованные запросы для JPA).
Как это соотносится с решением Грега Янга?
Некоторые из тонкостей вышеупомянутого решения на основе моста:
- Шов архитектуры, выставленный на уровне домена, НЕ является непрозрачным или узким. Доменный слой работает с
IRestaurantRepository
, который является намерением достаточно раскрывающимся. Фактическая реализация внедряется с использованием Dependency Injection. - Конкретный механизм реализации абстрагируется и вводится agian с использованием DI. Таким образом, в случае использования альтернативных механизмов репозитория, уровень домена НЕ затрагивается.
- 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