Сокращение хранилищ в совокупности корней
в настоящее время у меня есть репозиторий почти для каждой таблицы в базе данных, и я хотел бы еще больше выровнять себя с 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
? Или это противоречит лучшим практикам.
5 ответов:
вы можете иметь любой метод, который вы хотите в своем репозитории :) в обоих случаях, которые вы упоминаете, имеет смысл вернуть пользователя с заполненным списком телефонов. Обычно объект пользователя не будет полностью заполнен всей дополнительной информацией (скажем, все адреса, номера телефонов), и у нас могут быть разные методы для получения объекта пользователя, заполненного различной информацией. Это называется ленивой загрузкой.
User GetUserDetailsWithPhones() { // Populate User along with Phones }
для обновления, в этом случае, пользователь обновляется, а не сам номер телефона. Модель хранения может хранить телефоны в разных таблицах, и таким образом вы можете подумать, что только телефоны обновляются, но это не так, если вы думаете с точки зрения DDD. Что касается читаемости, то в то время как строка
UserRepository.Update(user)
в одиночку не передает то, что обновляется, код выше это будет ясно, что обновляется. Также это, скорее всего, будет частью вызова метода переднего плана, который может означать то, что есть обновляется.
для таблиц поиска, и на самом деле даже в противном случае, полезно иметь 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. } }
поиск общего репозитория Entity Framework, и вы бы штрафовать много хорошей реализации. Используйте один из них или напишите свой собственный.
ваш пример в репозитории Aggregate Root отлично подходит, т. е. любая сущность, которая не может разумно существовать без зависимости от другого, не должна иметь своего собственного репозитория (в вашем случае телефона). Без этого рассмотрения вы можете быстро найти себя со взрывом репозиториев в 1-1 сопоставлении с таблицами БД.
вы должны смотреть на использование шаблона Unit of Work для изменений данных, а не самих репозиториев, поскольку я думаю, что они вызывают у вас некоторую путаницу вокруг намерениях, когда дело доходит до сохранения изменений обратно в БД. В решении EF единица работы по существу является оболочкой интерфейса вокруг вашего контекста EF.
Что касается вашего репозитория для поиска данных, мы просто создаем ReferenceDataRepository, который становится ответственным за данные, которые конкретно не принадлежат объекту домена (страны, цвета и т. д.).
если телефон не имеет смысла без пользователя, это сущность (если вы заботитесь о его идентичности) или объект значения и всегда должны быть изменены через пользователя и извлечены/обновлены вместе.
подумайте о совокупных корнях как определителях контекста-они рисуют локальные контексты, но сами находятся в глобальном контексте (ваше приложение).
Если вы следуете доменному дизайну, репозитории должны быть 1: 1 на совокупные корни.
Никакие извинения.бьюсь об заклад, это проблемы, с которыми вы столкнулись:
- технические трудности-несоответствие импеданса объектного отношения. Вы боретесь с сохранением целых графов объектов с легкостью, а Entity framework kind a не помогает.
- модель домена ориентирована на данные (в отличие от поведения). из-за этого вы теряете знания об иерархии объектов (ранее упомянутые контексты), и волшебным образом все становится совокупным корнем.
Я не уверен, как это исправить первая проблема, но я заметил, что исправление второго исправляет первое достаточно хорошо. Чтобы понять, что я имею в виду с поведением центричным, дайте этой статье попробовать.
P. s. сокращение репозитория до aggregate root не имеет смысла.
П. С. С. Избежать"CodeRepositories"
. Что приводит к центрическая -> сведения-процессуального кодекса.
P. p. p. s избегите блока картины работы. Агрегатные корни должны определять границы транзакций.
Это старый вопрос, но подумал, что стоит опубликовать простое решение.
- контекст EF уже дает вам как единицу работы (отслеживает изменения), так и репозитории (ссылка в памяти на материал из БД). Дальнейшее абстрагирование не является обязательным.
- удалите DBSet из вашего класса контекста, так как телефон не является агрегатным корнем.
- вместо этого используйте свойство навигации "телефоны" для пользователя.
static void updateNumber(int userId, строка oldNumber, строка 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(); } }
Если сущность телефона имеет смысл только вместе с пользователем aggregate root, то я также думаю, что имеет смысл, что операция по добавлению новой записи телефона является ответственностью объекта домена пользователя через определенный метод (поведение DDD), и это может иметь смысл по нескольким причинам, причина immidiate заключается в том, что мы должны проверить объект пользователя, так как сущность телефона зависит от его существования и, возможно, сохранить блокировку транзакций проверяет, чтобы убедиться, что никакой другой процесс не удалил корневую совокупность, прежде чем мы закончим проверку операции. В других случаях с другими видами корневых агрегатов может потребоваться агрегировать или вычислить некоторое значение и сохранить его в свойствах столбца корневого агрегата для более эффективной обработки другими операциями позже. Примечание хотя я предлагаю объект домена пользователя есть метод, который добавляет телефон это не означает, что он должен знать о существовании базы данных или EF, один из отличная особенность EM и Hibernate заключается в том, что они могут отслеживать изменения, внесенные в классы сущностей прозрачно, а это также означает добавление новых связанных объектов по их свойствам коллекции навигации.
также, если вы хотите использовать методы, которые извлекают все телефоны независимо от пользователей, владеющих ими, вы все равно можете, хотя это через репозиторий пользователей вам нужен только один метод возвращает всех пользователей как IQueryable, тогда вы можете сопоставить их, чтобы получить все пользовательские телефоны и сделать уточненный запрос с этим. Так что вам даже не нужен PhoneRepository в этом случае. Кроме того, я бы предпочел использовать класс с методом расширений для IQueryable, который я могу использовать где угодно, а не только из класса репозитория, если я хочу абстрагировать запросы за методами.
только одно предостережение для возможности удаления телефонных сущностей только с помощью объекта домена, а не репозитория телефона вам нужно убедиться, что идентификатор пользователя является частью первичного ключа телефона или, другими словами, первичный ключ записи телефона является составным ключ состоит из идентификатора пользователя и некоторых других свойств (я предлагаю автоматически сгенерированный идентификатор) в телефонной сущности. Это имеет смысл интуитивно, поскольку запись телефона "принадлежит" записи Пользователя, и ее удаление из коллекции навигации пользователя будет равно ее полному удалению из базы данных.