Как использовать шаблон репозитория и единицу работы при работе с несколькими хранителями данных?
Вопрос
У меня есть уникальная ситуация, когда я создаю систему на основе DDD, которая должна получить доступ как к Active Directory, так и в базе данных SQL в качестве настойчивости. Изначально это не было проблемой, потому что наш дизайн был установлен, где у нас была единица работы, которая выглядела так:
public interface IUnitOfWork
{
void BeginTransaction()
void Commit()
}
И наши репозитории выглядели так:
public interface IRepository<T>
{
T GetByID()
void Save(T entity)
void Delete(T entity)
}
В этой настройке наша нагрузка и сохранение будут обрабатывать сопоставление между как хранилищами данных, потому что мы сами написали это. Блок работы будет обрабатывать транзакции и будет содержать контекст данных LINQ в SQL, что репозитории будут использовать для настойчивости. Часть Active Directory обрабатывается службой домена, реализованной в инфраструктуре и потребляется репозиториями в каждом методе сохранения (). Сохранить () отвечал с взаимодействием с контекстом данных, чтобы выполнить все операции базы данных.
Теперь мы пытаемся адаптировать его к основу для объектов и воспользоваться POCO. В идеале нам не понадобится метод сохранения (), потому что объекты домена отслеживаются контекстом объекта, и нам просто нужно будет добавить метод сохранения () на единицу работы, чтобы контекст объекта сохранить изменения, и способ Чтобы зарегистрировать новые объекты с контекстом. Новый предложенный дизайн выглядит больше похоже на это:
public interface IUnitOfWork
{
void BeginTransaction()
void Save()
void Commit()
}
public interface IRepository<T>
{
T GetByID()
void Add(T entity)
void Delete(T entity)
}
Это решает проблему доступа к данным с помощью структуры объекта, но не решает проблему с нашей интеграцией Active Directory. Раньше он был в методе сохранения () на репозитории, но теперь у него нет дома. Единица работы не знает ничего, кроме контекста данных структуры объекта. Куда должна идти эта логика? Я утверждаю, что эта конструкция работает только, если у вас есть только один магазин данных, используя основу для объектов. Любые идеи, как наилучшим образом подходить к этой проблеме? Где я должен положить эту логику?
Решение
Я хотел вернуться и последую, что я узнал с тех пор, как я разместил это. Похоже, что если вы собираетесь вернуться к узору репозитория, хранимы данных оно сохраняется не имеет значения. Если есть две магазины данных, напишите им как в одном репозитории. Важно для того, чтобы сохранить фасад, который представляет собой шаблон репозитория: в сборе памяти. Я бы не сделал отдельных репозиториев, потому что это не похоже на настоящую абстракцию. Вы позволяете технологии под капотом диктовать дизайн в этот момент. Цитировать из dddstepbystep.com:
Что сидит за репозиторий? Почти все, что вам нравится. Да, вы слышали это правильно. Вы можете иметь базу данных, или у вас может быть много разных баз данных. Вы можете использовать реляционные базы данных или объектные базы данных. У вас могут быть в базе данных памяти или Singleton, содержащий список в пунктах памяти. У вас может быть слой отдыха или набор услуг SOA или файловая система, или в кэше памяти ... Вы можете иметь в значительной степени что-нибудь - ваше единственное ограничение - это то, что репозиторий должен быть в состоянии вести себя как коллекцию для вашего домена Отказ Эта гибкость - это ключевое различие между хранилищем и традиционными методами доступа к данным.
http://thinkdddd.com/assets/2/domain_driviven_design_-_step_by_step.pdf.
Другие советы
Сначала я предполагаю, что вы используете контейнер IOC. Я выступаю, вы делаете истинные репозитории для каждого типа сущности. Это означает, что вы будете обернуть каждый объект контекста в классе, который реализует что-то вроде:
interface IRepository<TEntity> {
TEntity Get(int id);
void Add(TEntity entity);
void Save(TEntity entity);
void Remove(TEntity entity);
bool CanPersist<T>(T entity);
}
CANPERSIST просто возвращает, поддерживает ли эта поддержку экземпляра репозитория, сохраняющую проходящую сущность и используется полиморфически с помощью UnitOfWork .Save, описанный ниже.
Каждый IRePository также будет иметь конструктор, который позволяет построить мерозию в режиме «транзакционный». Итак, для EF, у нас может быть:
public partial EFEntityARepository : IRepository<EntityA> {
public EFEntityARepository(EFContext context, bool transactional) {
_context = context;
_transactional = transactional;
}
public void Add(EntityA entity) {
_context.EntityAs.Add(entity);
if (!_transactional) _context.SaveChanges();
}
}
UnitOpwork должен выглядеть так:
interface UnitOfWork {
void Add(TEntity entity);
void Save(TEntity entity);
void Remove(TEntity entity);
void Complete();
}
Реализация UnitOfWork будет использовать инъекцию зависимости, чтобы получить экземпляры всех предложений. В UnitOfWork.save/add/remove UOW пройдет сущность аргумента в каремическую каремию каждого мерозота. Для любого true
Возвращающие значения, UnitOfWward будет хранить эту сущность в частной коллекции, специфичной для этого ирпозиота и к предполагаемой работе. В полной мере, UnitOfWork будет проходить все коллекции частных сущностей и вызывают соответствующую операцию на соответствующем ирпозиторе для каждого объекта.
Если у вас есть сущность, которая должна быть частично сохраняться по EF и частично сохраняется по AD, у вас будет два целованных класса для этого типа сущностей (они оба вернулись от Canpersist, когда передают экземпляр этого типа объекта).
Что касается поддержания атомности между EF и AD, то есть отдельная нетривиальная проблема.
IMO Я бы обернул звонки на обоих этих репо, в типе обслуживания класса. Тогда я бы использовал IOC / DI, чтобы ввести типы репо, в класс услуг. У вас будет 2 REPOS, 1 для ENT. Рамки и 1, что поддерживает объявление. Таким образом, каждое репо касается только его подложного хранилища данных и не нужно переходить.
То, что я сделал, чтобы поддержать несколько единиц типов рабочих мест, состоит в том, чтобы иметь IUNITOFWORKWORK BEOR FACTION. Я создаю другой тип, называемый IUNITOFWORKSCOPE, который является фактической единицей работы, и у него есть только метод фиксации.
namespace Framework.Persistance.UnitOfWork
{
public interface IUnitOfWork
{
IUnitOfWorkScope Get();
IUnitOfWorkScope Get(bool shared);
}
public interface IUnitOfWorkScope : IDisposable
{
void Commit();
}
}
Это позволяет мне вводить различные реализации устройства работы в службу и иметь возможность использовать их рядом.