Сокращение хранилищ для объединения корней - PullRequest
82 голосов
/ 01 марта 2011

В настоящее время у меня есть репозиторий почти для каждой таблицы в базе данных, и я хотел бы еще больше присоединиться к 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 ]

11 голосов
/ 01 марта 2011

Вам разрешено иметь любой метод, который вы хотите в своем хранилище :) В обоих случаях, о которых вы упомянули, имеет смысл вернуть пользователя с заполненным списком телефонов. Обычно пользовательский объект не будет полностью заполнен всей вспомогательной информацией (скажем, все адреса, номера телефонов), и у нас могут быть разные методы для наполнения пользовательского объекта различными видами информации. Это называется отложенной загрузкой.

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.
    }
}

Ищите Generic Repository Entity Framework, и у вас будет много хороших реализаций. Используйте один из них или напишите свой собственный.

9 голосов
/ 01 марта 2011

Ваш пример с хранилищем Aggregate Root прекрасно работает, т. Е. Любой объект, который не может разумно существовать без зависимости от другого, не должен иметь своего собственного хранилища (в вашем случае Phone).Без этого вы можете быстро оказаться в репозиториях со связью 1-1 в таблицах БД.

Вам следует взглянуть на использование шаблона единиц работы для изменений данных, а не на сами репозитории, как мне кажется.они вызывают у вас некоторую путаницу вокруг намерений, когда дело доходит до постоянных изменений в БД.В решении EF Единица работы по сути является интерфейсной оболочкой для вашего контекста EF.

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

5 голосов
/ 02 марта 2011

Если телефон не имеет смысла без пользователя, это сущность (если вы заботитесь о ее идентичности) или объект значения, и его всегда следует изменять через пользователя и извлекать / обновлять вместе.в качестве определителей контекста - они рисуют локальные контексты, но сами находятся в глобальном контексте (Ваше приложение).

Если вы следуете дизайну, управляемому доменом, предполагается, что репозитории должны быть 1: 1 для агрегатных корней.
Никаких оправданий.

Могу поспорить, что это проблемы, с которыми вы сталкиваетесь:

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

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

Ps Сокращение репозитория до объединенного корня не имеет смысла.
Pps Избегайте "CodeRepositories".Это приводит к центрированному на данных -> процедурному коду.
Ppps Избегайте единицы работы.Совокупные корни должны определять границы транзакций.

3 голосов
/ 17 июня 2015

Это старый вопрос, но стоит подумать о том, чтобы опубликовать простое решение.

  1. EF Context уже предоставляет вам и единицы работы (отслеживает изменения), и репозитории (ссылки в памяти на материал из БД). Дальнейшая абстракция не обязательна.
  2. Удалите DBSet из вашего контекстного класса, так как Phone не является совокупным корнем.
  3. Вместо этого используйте свойство навигации «Телефоны» для пользователя.

статический 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();
        }

    }
0 голосов
/ 20 сентября 2016

Если сущность Phone имеет смысл только вместе с совокупным корневым пользователем, то я бы также подумал, что имеет смысл, что операция по добавлению новой записи Phone является ответственностью объекта домена пользователя через определенный метод (поведение DDD).и это может иметь смысл по нескольким причинам. Непосредственная причина состоит в том, что мы должны проверить, существует ли объект User, так как сущность Phone зависит от его существования, и, возможно, оставить для него блокировку транзакции, выполняя дополнительные проверки, чтобы убедиться, что никакой другой процесс не удалил объект.Корневой агрегат, прежде чем мы закончим проверку операции.В других случаях с другими типами корневых агрегатов вы можете собирать или вычислять какое-то значение и сохранять его в свойствах столбцов корневого агрегата для более эффективной обработки другими операциями в дальнейшем.Обратите внимание, хотя я предлагаю, чтобы у объекта «Домен пользователя» был метод, который добавляет Телефон, это не значит, что он должен знать о существовании базы данных или EF, одна из замечательных особенностей EM и Hibernate заключается в том, что они могут отслеживать изменения, внесенные в объект.классы прозрачно, и это также означает добавление новых связанных сущностей по их свойствам коллекции навигации.

Кроме того, если вы хотите использовать методы, которые извлекают все телефоны, независимо от того, кто владеет ими, вы все равно могли бы использовать их, хотя через репозиторий User вам нужен только один метод, который возвращает всех пользователей как IQueryable, тогда вы можете отобразить их, чтобы получить всепользовательские телефоны и сделать уточненный запрос с этим.Так что вам даже не нужен PhoneRepository в этом случае.Кроме того, я бы предпочел использовать класс с методом расширений для IQueryable, который я могу использовать где угодно, а не только из класса Repository, если я хочу абстрагировать запросы за методами.используя объект домена, а не репозиторий Phone, необходимо убедиться, что UserId является частью первичного ключа Phone, или, другими словами, первичный ключ записи Phone представляет собой составной ключ, состоящий из UserId и некоторого другого свойства (я предлагаюавтоматически сгенерированная личность) в объекте Phone.Это имеет смысл, поскольку запись «Телефон» «принадлежит» записи пользователя, и ее удаление из коллекции навигации пользователя будет равносильно ее полному удалению из базы данных.

...