Зрелый инженер-программист обычно знает несколько подходов к решению одной и той же проблемы программирования и делает выбор на основе анализа компромиссов.

Чем больше цель, тем тщательнее программист должен подходить к анализу компромиссов, чтобы избежать внесения технического долга в проект.

Когда дело доходит до написания уровня доступа к данным, помимо хорошо известного шаблона репозитория, есть несколько других доступных вариантов. Даже сам репозиторий имеет несколько типов реализации. Знание их всех важно для разработчика, чтобы принять наиболее правильное решение.

Прямое использование DbContext

Entity Framework - это технология объектно-реляционного сопоставления, которая реализует различные шаблоны доступа к данным, такие как Unit of Work, Repository и т. Д. При работе с Entity Framework разработчики создают объект DbContext, который содержит все необходимое для выполнения операций CRUD с базой данных.

Разработчики могут напрямую внедрить класс DbContext в любое место проекта, где им необходимо взаимодействовать с базой данных - контроллеры, службы приложений или домена, код инфраструктуры и т. Д.

Плюсы:

  • Простота. ApplicationContext необходимо только зарегистрировать в контейнере DI, прежде чем его можно будет использовать.

Минусы:

  • Дублирование кода и жесткое обслуживание. Пользовательские запросы будут все больше и больше дублироваться по мере роста проекта. Например, в разных частях проекта может быть несколько копий запроса Users.FirstOrDefault (u = ›u.UserId == userId). Если новое требование вынуждает разработчика выполнять фильтрацию по дополнительному свойству (IsActive, Status и т. Д.), Необходимо найти и обновить несколько мест в коде.
  • Замена Entity Framework другим ORM, таким как Dapper или NHibernate, будет сложной задачей, потому что после рефакторинга в приложении не останется нетронутого места. Так что риск проблем с регрессией высок.

Репозиторий на объект

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

Плюсы:

  • Логика приложения отделена от деталей доступа к данным. При необходимости переключения источника данных следует переписывать только классы репозитория.
  • Пользовательские запросы можно найти только в классах репозитория. Их можно повторно использовать в различных частях логики приложения. Каждый раз, когда возникает новое требование, например фильтрация по дополнительному свойству, необходимо обновить отдельное местоположение.

Минусы:

  • Репозиторий может значительно увеличиться в размере, если с сущностью связано много логики. Если репозиторий станет большим, вполне вероятно, что в конструктор будет добавлено много зависимостей. Чем их больше, тем сложнее будет модульное тестирование кода репозитория.
  • Запросы на чтение и запись являются частью одного и того же класса репозитория, поэтому чтение нельзя масштабировать отдельно от записи.

Репозитории только для чтения и только для записи

Один из недостатков смешивания операций чтения и записи в приведенном выше примере может быть легко решен путем создания репозиториев только для чтения и только для записи. Репозитории только для чтения будут содержать все настраиваемые запросы, которые не изменяют состояние.

public class UserReadOnlyRepository : IUserReadOnlyRepository 
{ 
    ... 
}
public class OrderReadOnlyRepository : IOrderReadOnlyRepository
{ 
    ... 
}

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

Репозиторий с IQueyrable

Код класса репозитория увеличивается за счет настраиваемых запросов. Один из способов избежать этого - открыть интерфейс IQueryable из репозитория. У клиентов есть возможность писать свои собственные запросы, поэтому нет необходимости иметь репозиторий для каждой сущности. Одной реализации интерфейса IRepository будет достаточно с универсальными методами вставки, удаления и обновления.

Плюсы:

  • Одной реализации репозитория будет достаточно независимо от количества сущностей в проекте.
  • Репозиторий будет относительно небольшим, потому что пользовательские запросы будут написаны в контроллере, службах и т. Д., А не в самом репозитории.

Минусы:

  • Опять же, у нас та же проблема, что и в самом первом примере: пользовательские запросы не будут объединены в одном месте, поэтому существует высокий риск дублирования. Кроме того, переход на новую ORM потребует рефакторинга различных частей приложения.
  • Открытие интерфейса IQueryable из репозитория - это пример утечки проблем на уровень приложения. Чтобы писать запросы, клиент класса репозитория должен знать специфику ORM или базы данных.

Команды и запросы

Каждый метод репозитория представляет бизнес-сценарий: создать пользователя, получить премиум-пользователей, активировать пользователя, создать заказ и т. Д. Как насчет того, чтобы инкапсулировать каждый сценарий в отдельный объект, а не хранить их в большом классе репозитория или распространять их по всему проекту? ? Объекты, отвечающие за чтение данных, будут называться запросами, а объекты, отвечающие за запись данных, будут называться командами.

Плюсы:

  • Объекты обработчиков команд и запросов следуют принципу единой ответственности: поэтому они могут быть повторно использованы, легко поддаются модульному тестированию, просты для понимания.
  • Каждая команда или объект обработчика запросов могут обращаться к базе данных по-разному. Например, 95% всех объектов могут использовать Entity Framework, а остальные 5% используют Dapper из-за высоких требований к производительности для бизнес-сценариев, которые они инкапсулируют.
  • Разделение операций чтения и записи на отдельные объекты лучше для удобства обслуживания (ясно, какой объект изменяет состояние приложения, а какой нет) и масштабируемости (чтение можно легко распараллелить).

Минусы:

  • Количество классов будет значительно больше по сравнению со всеми предыдущими реализациями уровня доступа к данным.

Заключение

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

Другие мои статьи