В хранилище или не в хранилище

10

Когда я впервые узнал о Domain Driven Design, меня также познакомили с репозиторием и шаблонами рабочих единиц, которые когда-то казались первоклассными для крутых ребят, которые бросали SQL-запросы, например, пещерные люди, в базы данных. Чем глубже я углубился в эту тему, тем больше я узнал, что они больше не нужны, поскольку ORM, такие как EF и NHibernate , реализуют как единицу работы, так и репозитории в одном API, называемом сеансом или контекстом.

Теперь я не уверен, что делать. В хранилище или нет в хранилище. Я действительно понимаю аргумент, что такие дырявые абстракции только чрезмерно усложняют вещи, добавляя абсолютно ничего, что может упростить доступ к данным, однако неправильно связывать все возможные аспекты моего приложения, например, с Entity Framework . Обычно я следую нескольким простым правилам:

  1. Доменный уровень - это сердце системы, содержащее сущности, сервисы, репозитории ...
  2. Уровень инфраструктуры предоставляет реализации доменных интерфейсов инфраструктуры, например, файлов, баз данных, протоколов.
  3. На прикладном уровне размещается корень композиции, который связывает вещи и организует все.

Мои решения обычно выглядят так:

Domain.Module1
Domain.Module2
    IModule2Repo
    IModule2Service
    Module2
Infrastructure.Persistence
    Repositories
        EntityFrameworkRepositoryBase
MyApp
    Boostrapper
        -> inject EntityFrameworkRepositoryBase into IRepository etc.

Я сохраняю свой уровень домена чистым, используя объект, IRepository<'T>который также является проблемой домена, не зависящей от чего-либо еще, что говорит мне, как получить доступ к данным. Когда я сейчас сделаю конкретную реализацию того, IModule2Serviceчто требует доступа к данным, мне придется внедрить DbContextи тем самым связать его непосредственно с уровнем инфраструктуры. (При переходе к проекту Visual Studio это может оказаться очень сложным из-за циклических зависимостей! )

Кроме того, что может быть альтернативой депозитариям и актам работ ? CQRS? Как можно абстрагироваться от чистой инфраструктурной структуры?

Acrotygma
источник
1
Кроме того, если вы говорите о «Репозитории», как это описано в DDD, то EF и nHibernate не являются репозиториями. Конечно, они частично абстрагируют механизм доступа к данным, но хранилище гораздо больше, чем это .
Эрик Кинг,
github.com/gregoryyoung/mr
Даниэль Литтл

Ответы:

4

Инженерия это все о компромиссах. И так же разработка программного обеспечения. На данный момент, я полагаю, что только другой вариант, который был бы проще, это работать напрямую с ORM. Но, как вы сказали, это может заблокировать вас в определенных рамках персистентности.

Поэтому вы должны спросить себя: «Стоит ли дополнительная сложность отделять ваш код от постоянства». Каждый раз, когда я слышу, как люди говорят, что они хотят отделить свой код от настойчивости, я спрашиваю: «Сколько раз в вашей карьере вы меняли структуру постоянства?»

Проблемы, которые я вижу с репозиториями, заключаются в том, что они усложняют общие изменения. Например. добавление нового способа запроса агрегата. И они делают необычные изменения (предположительно) легче. Например. изменение реализации репозиториев.

Кроме того, есть аргумент модульного тестирования, на который я говорю: «Если постоянная среда не позволяет вам смоделировать базу данных, ни в памяти, ни локально, тогда вообще не стоит использовать эту среду».

Euphoric
источник
1
Смысл репозитория в том, чтобы отделить приложение от постоянства, и в этом нет никакой сложности. И я изменяю детали персистентности, по крайней мере, один раз, потому что доступ к БД - это последнее, что я кодирую, до тех пор, пока я не использую это в репозиториях памяти. Кроме того, существует очень тонкая ловушка, когда вы непосредственно используете детали персистентности. Ваши бизнес-объекты будут иметь тенденцию быть совместимыми с ним, а не агностиком. И это потому, что богатый, правильно инкапсулированный объект не может быть напрямую восстановлен из любого хранилища (кроме памяти) без (некоторых уродливых) обходных путей
MikeSW
О добавлении нового способа запроса совокупного корня или любой сущности, поэтому появился CQRS. Лично я храню репозиторий для целей домена и использую обработчики запросов для реальных запросов. И эти обработчики очень тесно связаны с базой данных и очень эффективны в том, что они делают.
MikeSW
3

Я сторонник репозитория, хотя я отошел от общих шаблонов репозитория. Вместо этого я привожу свои репозитории в соответствие с бизнес-функциями, которые они выполняют. Репозитории не нацелены на абстрагирование ORM, так как это не то, что я ожидаю изменить, и в то же время я избегаю делать репозиторий слишком гранулированным. (Т.е. CRUD) Вместо этого мои репозитории служат двум-трем ключевым целям:

  • Поиск данных
  • Создание данных
  • Жесткое удаление

Для извлечения данных хранилище всегда возвращается IQueryable<TEntity>. Для создания данных он возвращает TEntity. Репозиторий обрабатывает мою фильтрацию базового уровня, такую ​​как «активное» состояние авторизации для систем, которые используют шаблоны мягкого удаления, и «текущее» состояние для систем, которые используют исторические данные. Создание данных отвечает только за то, чтобы необходимые ссылки были разрешены и связаны, а объект был настроен и готов к работе.

Обновление данных является обязанностью бизнес-логики, работающей с соответствующими объектами. Это может включать в себя такие вещи, как правила проверки. Я не пытаюсь инкапсулировать это в метод репозитория.

Удаление в большинстве моих систем происходит при мягком удалении, поэтому это может привести к обновлению данных. (IsActive = false). В случае аппаратного удаления это будет одна строка в репозитории.

Почему репозитории? Тест-способность. Конечно, DbContext можно смоделировать, но проще смоделировать класс, который возвращаетIQueryable<TEntity>, Они также хорошо работают с шаблоном UoW, лично я использую шаблон Mehdime DbContextScope, чтобы охватить единицу работы на уровне, который я хочу (т.е. контроллеры в MVC), и позволить моим контроллерам и вспомогательным классам обслуживания использовать репозитории без необходимости передавать ссылки на UoW / dbContext вокруг. Использование IQueryable означает, что вам не нужно много методов-оболочек в репозитории, и ваш код может оптимизировать использование данных. Например, хранилищу не нужно предоставлять такие методы, как «Exists» или «Count», или пытаться обернуть сущности другими POCO в тех случаях, когда вам нужны подмножества данных. Им даже не нужно обрабатывать загружаемые опции для связанных данных, которые могут вам понадобиться или не понадобиться. Передав IQueryable, вызывающий код может:

.Any()
.Count()
.Include() // Generally avoided, instead I use .Select()
.Where()
.Select(x => new ViewModel or Anon. Type)
.Skip().Take()
.FirstOrDefault() / .SingleOrDefault() / .ToList()

Очень гибкий, и из тестового PoV мой смоделированный репозиторий просто должен возвращать списки заполненных объектов-сущностей.

Что касается общих репозиториев, я по большей части отошел от них, потому что, когда вы получаете репозиторий для каждой таблицы, ваши контроллеры / сервисы заканчивают ссылками на несколько репозиториев для выполнения одного бизнес-действия. В большинстве случаев только один или два из этих репозиториев фактически выполняют операции записи (при условии, что вы правильно используете свойства навигации), в то время как остальные поддерживают те, что с Reads. Я предпочел бы иметь что-то вроде OrdersRepository, которое способно считывать и создавать заказы, а также читать любые релевантные запросы и т. Д. (Облегченные объекты клиентов, продукты и т. Д.) Для справки при создании заказа, а не использовать 5 или 6 различных репозиториев. Это может нарушить пуристов DNRY, но я утверждаю, что цель репозитория - служить созданию заказов, которые включают соответствующие ссылки.Repository<Product>чтобы получить продукты, для которых на основании заказа мне нужен только объект с несколькими полями. В моем OrderRepository может быть .GetProducts()метод, возвращающий, IQueryable<ProductSummary>который, на мой взгляд, более приятный, чем метод, который в Repository<Product>итоге имеет несколько методов «Get», чтобы попытаться обслуживать различные области потребностей приложения, и / или некоторое сложное выражение фильтрации передачи.

Я выбираю более простой код, который легко отслеживать, тестировать и настраивать. Его можно использовать ненадлежащим образом, но я предпочел бы иметь что-то, где злоупотребления легко обнаружить и исправить, чем пытаться «заблокировать» код таким образом, чтобы его нельзя было злоупотребить, потерпеть неудачу и получить что-то, что кошмар, чтобы заставить фактически делать то, что платит клиент, чтобы сделать это в конце. :)

Стив Пи
источник