KISS my ... единица работы

Я видел много, много реализаций UoW + Repository. Всякий раз, когда кто-то строился поверх Entity Framework, я бы усмехался при добавленной сложности.

Уверенная сложность покупает вас (иногда) полную развязку из Entity Framework, которая в теории могла бы возможно разрешить замену EF, скажем, NHibernate или даже «понизить», к решению ADO.NET более низкого уровня.

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

Поэтому мне нравится держать его глупым простым (KISS) и делать только минимум, необходимый для включения в класс моего класса DbContext - вот реальный IUnitOfWork, который я написал в недавнем проекте:

  

Интерфейс IUnitOfWork

public interface IUnitOfWork
{
    int SaveChanges();
    IDbSet<TEntity> Repository<TEntity>() where TEntity : class;
}

37 голосов | спросил Mathieu Guindon 21 stEurope/Moscowp30Europe/Moscow09bEurope/MoscowSun, 21 Sep 2014 17:05:41 +0400 2014, 17:05:41

2 ответа


20

Я бы сказал второй ответ TopinFrassi, что это более или менее разумный подход, и реализация отличная. Однако есть некоторые потенциальные проблемы, которые я буду расширять из комментариев craftworkgames и вашего ответа. Поскольку я не могу найти ничего, что требует критики в том, как оно написано, это будет полностью сконцентрировано.

Есть две основные проблемы при передаче интерфейса, который утверждает, что он может предоставить

IDbSet<TEntity> Repository<TEntity>() where TEntity : class;
  • Вы обещаете ужасную партию
  • Вы раскрываете это за пределами непосредственно связанного с доступом к данным кода (даже если он находится в классах услуг, эти классы вряд ли будут напрямую связаны с DA, за исключением, возможно, очень маленького приложения).

И могут возникнуть следующие проблемы:

  1. Предоставление гораздо большего, чем потребности потребителя. Любой класс, который должен иметь дело с SalesRep s, также не должен быть передан способ выполнения произвольных операций crud на Customer s. Это является нарушением принципа разделения интерфейса.
  2. Сложность насмешки. Интерфейс, который может обеспечить много, означает многому насмешку. Вероятно, вы сможете сузить то, что на самом деле нужно издеваться над отдельными тестами, но часто практичность этого заключается в том, чтобы связать с деталями реализации SUT, которые вы на самом деле не пытаетесь протестировать, что делает ваш тесты более хрупкие.
  3. Протекающая абстракция. Абстракции - это нечто большее, чем просто возможность смены реализаций, что означает, что даже если вы не заинтересованы в этом, проблема с протекающей абстракцией по-прежнему остается проблемой. В этом случае вы подвергаете большую EF-версию каждому классу, который когда-либо должен иметь дело с объектами: IQueryable. Он производит хорошее впечатление от IEnumerable, но затем генерирует исключения для большого количества аргументов в своих расширениях LINQ, которые IEnumerable будет успешно работать. Необходимо помнить, что код, который нужно преобразовать в SQL, не является проблемой, которую вы должны раскрывать на протяжении всего уровня сервиса.
  4. Отсутствие сущностных методов. Помимо предоставления всем вашим классам обслуживания слишком много общих методов, вы также можете предоставить им недостаточно конкретных методов. Вполне вероятно, что у вас появятся запросы или команды для определенного типа сущности, который вы хотите сделать в нескольких местах, в том числе в нескольких классах услуг, но которые являются длинными /сложными, что повторение всего запроса или команды будет DRY.

Решение

Эта последняя проблема - это тот, который больше всего кричит о конкретном ответе, поэтому давайте начнем с этого. Скажем, мы написали класс сервиса с частным методом, который делает сложный запрос для нас. Теперь мы пишем еще один класс обслуживания, и хотя функциональность не так похожа, мы обнаруживаем, что нам нужен тот же запрос, что и в этом приватном методе.

Мы делаем очевидную вещь и вытаскиваем запрос в свой класс:

public class FirstFooService()
{
    public FirstFooService(IUnitOfWork unitOfWork, FooQueryHelper queryHelper) { //... }

    //...
}

public class SecondFooService()
{
    public SecondFooService(IUnitOfWork unitOfWork, FooQueryHelper queryHelper) { //... }

    //...
}

Но теперь это действительно похоже на беспорядок. Мы полагаемся на две разные вещи: блок работы и вспомогательный запрос - для доступа к данным на двух разных уровнях абстракции. Какой уровень мы действительно хотим?

Хорошо, я думаю, что ответ прост. Мы добавили FooQueryHelper специально, потому что мы обнаружили, что хотим получить абстракцию от прямого доступа к IUnitOfWork. Более того, если мы рассмотрим вышеперечисленные проблемы, FooQueryHelper может решить все четыре из них. Он решает 1,2 и 4 по своей природе, и он может просто решить 3, сохраняя свои возвращаемые типы как IEnumerableа не IQueryable и не подвергая никаким методам, которые используют произвольный Func или Expression для использования в качестве фильтра.

Все, что осталось, - это извлечь методы IUnitOfWork, которые мы потребляем на FooQueryHelper, сужая их и делая их более конкретными, чтобы они были на правильном уровне абстракции, когда это было возможно. Вероятно, вы можете увидеть, как начинается punchline, что мы на самом деле заканчиваем, это не FooQueryHelper, а FooRepository. Это может соответствовать стандарту общего шаблона репозитория, который вы найдете здесь повсюду. Если вы посмотрите на проблемы с корнем, вы увидите, что они также решены: теперь все зависит от гораздо меньшего, более целевого интерфейса, а проблемы с доступом к данным сохраняются в классах доступа к данным, а не распространяются на все службы, которые волнуют сущности.

Заключение

Но как насчет KISS? Возможно, вы уже знали об общем репозитории, и вы хотели избежать его из-за его явно ненужной сложности и косвенности. Ну, да, как всегда есть компромисс - в небольшом простом приложении, в какой момент дальнейший СОКРАЩЕНИЕ вашего кода потребляет больше ресурсов для разработки, чем он когда-либо купит вас? Это решение, которое вам нужно будет сделать. Но, надеюсь, я привел несколько причин, кроме «Я могу поменять свой ORM», почему вы можете выбрать этот более сложный шаблон.

Также обратите внимание, что общий репозиторий - это, по сути, не все, что сложно или громоздко реализовать. Это действительно начинается как тонкая оболочка над IDbContext. Но не вводите в заблуждение! Репозиторий должен не просто быть адаптером из IDbSet на интерфейс, которым вы управляете, это должен быть полномасштабный слой абстракции! Поэтому, если вы обнаружите, что добавляете больше методов, это, вероятно, оправдывает выбор этого шаблона.

ответил Ben Aaronson 24 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowWed, 24 Sep 2014 02:52:13 +0400 2014, 02:52:13
4

Ваш код кажется мне очень хорошим, мне больше нечего сказать, но я удалю переопределение OnModelCreating, так как он не имеет никаких целей.

ответил IEatBagels 23 rdEurope/Moscowp30Europe/Moscow09bEurope/MoscowTue, 23 Sep 2014 18:55:21 +0400 2014, 18:55:21

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132