В настоящее время у меня есть репозиторий практически для каждой таблицы в базе данных, и я хотел бы еще больше согласовать себя с DDD, уменьшив их только до агрегированных корней.
Предположим, что у меня есть следующие таблицы, User
и Phone
. У каждого пользователя может быть один или несколько телефонов. Без понятия совокупного корня я мог бы сделать что-то вроде этого:
//assuming I have the userId in session for example and I want to update a phone number
List<Phone> phones = PhoneRepository.GetPhoneNumberByUserId(userId);
phones[0].Number = “911”;
PhoneRepository.Update(phones[0]);
Концепцию совокупных корней легче понять на бумаге, чем на практике. У меня никогда не будет телефонных номеров, которые не принадлежат пользователю, так что имеет ли смысл отказаться от PhoneRepository и включить методы, связанные с телефоном, в UserRepository? Предполагая, что да, я собираюсь переписать предыдущий пример кода.
Могу ли я иметь в UserRepository метод, который возвращает номера телефонов? Или он всегда должен возвращать ссылку на пользователя, а затем проходить отношения через пользователя, чтобы добраться до номеров телефонов:
List<Phone> phones = UserRepository.GetPhoneNumbers(userId);
// Or
User user = UserRepository.GetUserWithPhoneNumbers(userId); //this method will join to Phone
Независимо от того, каким образом я приобрету телефоны, если я модифицировал один из них, как мне их обновить? Мое ограниченное понимание состоит в том, что объекты под корнем должны обновляться через корень, что подтолкнет меня к выбору № 1 ниже. Хотя это будет отлично работать с Entity Framework, это кажется крайне не описательным, потому что, читая код, я понятия не имею, что я на самом деле обновляю, хотя Entity Framework отслеживает измененные объекты в графе.
UserRepository.Update(user);
// Or
UserRepository.UpdatePhone(phone);
И, наконец, если предположить , у меня есть несколько справочных таблиц, которые на самом деле не привязанные ни к чему, например CountryCodes
, ColorsCodes
, SomethingElseCodes
. Я мог бы использовать их для заполнения раскрывающихся списков или по любой другой причине. Это автономные репозитории? Можно ли их объединить в какую-то логическую группу / хранилище, например CodesRepository
? Или это противоречит передовой практике.
Ответы:
Вы можете использовать любой метод в своем репозитории :) В обоих случаях, которые вы упомянули, имеет смысл вернуть пользователя с заполненным списком телефонов. Обычно пользовательский объект не может быть полностью заполнен всей вспомогательной информацией (скажем, всеми адресами, номерами телефонов), и у нас могут быть разные методы для заполнения пользовательского объекта другой информацией. Это называется отложенной загрузкой.
User GetUserDetailsWithPhones() { // Populate User along with Phones }
Для обновления в этом случае обновляется пользователь, а не сам номер телефона. Модель хранилища может хранить телефоны в разных таблицах, и поэтому вы можете подумать, что обновляются только телефоны, но это не так, если вы думаете с точки зрения DDD. Что касается читабельности, то строка
сам по себе не передает, что обновляется, приведенный выше код прояснил бы, что обновляется. Кроме того, это, скорее всего, будет частью вызова метода внешнего интерфейса, который может обозначать, что обновляется.
Для таблиц поиска и даже в противном случае полезно иметь GenericRepository и использовать его. Пользовательский репозиторий может наследовать от GenericRepository.
public class UserRepository : GenericRepository<User> { IEnumerable<User> GetUserByCustomCriteria() { } User GetUserDetailsWithPhones() { // Populate User along with Phones } User GetUserDetailsWithAllSubInfo() { // Populate User along with all sub information e.g. phones, addresses etc. } }
Найдите Generic Repository Entity Framework, и вы получите много хороших реализаций. Используйте один из них или напишите свой.
источник
Ваш пример с репозиторием Aggregate Root в порядке, т.е. любой объект, который не может разумно существовать без зависимости от другого, не должен иметь своего собственного репозитория (в вашем случае Phone). Без этого рассмотрения вы можете быстро обнаружить бурный рост репозиториев в сопоставлении 1-1 с таблицами БД.
Вам следует обратить внимание на использование шаблона Unit of Work для изменения данных, а не на сами репозитории, поскольку я думаю, что они вызывают у вас некоторую путаницу в отношении намерений, когда дело доходит до сохранения изменений обратно в базу данных. В решении EF единица работы, по сути, является оболочкой интерфейса для вашего контекста EF.
Что касается вашего репозитория для данных поиска, мы просто создаем ReferenceDataRepository, который становится ответственным за данные, которые конкретно не принадлежат объекту домена (страны, цвета и т. Д.).
источник
Если телефон не имеет смысла без пользователя, это сущность (если вы заботитесь об ее идентичности) или объект значения, который всегда должен изменяться пользователем и извлекаться / обновляться вместе.
Подумайте об агрегированных корнях как о средствах определения контекста - они рисуют локальные контексты, но сами находятся в глобальном контексте (ваше приложение).
Если вы следуете дизайну, основанному на домене, предполагается, что репозитории должны быть 1: 1 на совокупные корни.
Никаких оправданий.
Бьюсь об заклад, это проблемы, с которыми вы столкнулись:
Я не уверен, как исправить первую проблему, но я заметил, что исправление второй исправляет первую достаточно хорошо. Чтобы понять, что я имею в виду, говоря о поведении, попробуйте эту статью .
Ps сворачивать репозиторий до агрегатного рута нет смысла.
Pps Избегайте
"CodeRepositories"
. Это приводит к ориентированному на данные -> процедурному коду.Ppps Избегайте шаблона единицы работы. Совокупные корни должны определять границы транзакции.
источник
Это старый вопрос, но подумал, что стоит опубликовать простое решение.
static void updateNumber (int userId, string oldNumber, string newNumber)
static void updateNumber(int userId, string oldNumber, string newNumber) { using (MyContext uow = new MyContext()) // Unit of Work { DbSet<User> repo = uow.Users; // Repository User user = repo.Find(userId); Phone oldPhone = user.Phones.Where(x => x.Number.Trim() == oldNumber).SingleOrDefault(); oldPhone.Number = newNumber; uow.SaveChanges(); } }
источник
Если объект Phone имеет смысл только вместе с совокупным корневым пользователем, то я также думаю, что имеет смысл, что операция по добавлению новой записи Phone является обязанностью объекта домена User посредством определенного метода (поведение DDD), и это может имеет смысл по нескольким причинам, непосредственная причина заключается в том, что мы должны проверить, существует ли объект User, поскольку объект Phone зависит от его существования и, возможно, сохранить блокировку транзакции на нем, выполняя больше проверок, чтобы убедиться, что никакой другой процесс не удалил корневой агрегат раньше мы закончили проверку операции. В других случаях с другими типами корневых агрегатов вы можете захотеть агрегировать или вычислить какое-то значение и сохранить его в свойствах столбца корневого агрегата для более эффективной обработки другими операциями позже.
Кроме того, если вы хотите использовать методы, которые извлекают все телефоны независимо от пользователей, владеющих ими, вы все равно можете, хотя через репозиторий пользователей вам нужен только один метод, который возвращает всех пользователей как IQueryable, тогда вы можете сопоставить их, чтобы получить все телефоны пользователей и выполнить уточненный запрос с этим. Так что в этом случае вам даже не понадобится PhoneRepository. Кроме того, я бы предпочел использовать класс с методом расширений для IQueryable, который я могу использовать где угодно, а не только из класса репозитория, если я хочу абстрагировать запросы за методами.
Только одно предостережение в отношении возможности удалять объекты Phone, используя только объект домена, а не репозиторий Phone, вам необходимо убедиться, что UserId является частью первичного ключа Phone или, другими словами, первичный ключ записи Phone является составным ключом. состоящий из UserId и некоторого другого свойства (я предлагаю автоматически сгенерированный идентификатор) в объекте Phone. Это интуитивно понятно, поскольку запись телефона «принадлежит» записи пользователя, и ее удаление из коллекции навигации пользователя равносильно ее полному удалению из базы данных.
источник