Сокращение репозиториев для суммирования корней

В настоящее время у меня есть репозиторий практически для каждой таблицы в базе данных, и я хотел бы еще больше увязать себя с 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, это кажется крайне неописуемым, потому что, читая код, я не знаю, что Im действительно обновляет, хотя Entity Framework сохраняет вкладку с измененными объектами внутри графика.

UserRepository.Update(user);
// Or
UserRepository.UpdatePhone(phone);

Наконец, предполагая, что у меня есть несколько таблиц поиска, которые на самом деле не привязаны ни к чему, например CountryCodes, ColorsCodes, SomethingElseCodes. Я мог бы использовать их для заполнения выпадающих списков или по любой другой причине. Являются ли эти автономные хранилища? Могут ли они быть объединены в какую-то логическую группировку/репозиторий, такую ​​как CodesRepository? Или это противоречит лучшим практикам.

Ответ 1

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

User GetUserDetailsWithPhones()
{
    // Populate User along with Phones
}

Для обновления в этом случае пользователь обновляется, а не сам номер телефона. Модель хранения может хранить телефоны в другой таблице, и таким образом вы можете подумать, что только телефоны обновляются, но это не так, если вы думаете с точки зрения DDD. Что касается читаемости, в то время как строка

UserRepository.Update(user)

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

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

Ответ 2

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

Вы должны взглянуть на использование шаблона Unit of Work для изменений данных, а не самих репозиториев, поскольку я думаю, что они вызывают у вас некоторую путаницу в отношении намерений, когда дело доходит до сохраняющихся изменений обратно в db. В EF-решении Unit of Work является, по сути, интерфейсной оболочкой вокруг вашего EF-контекста.

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

Ответ 3

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

Подумайте об общих корнях в качестве определений контекста - они нарисовали локальные контексты, но сами в глобальном контексте (ваше приложение).

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

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

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

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

P.s. Сокращение репозитория для агрегирования корня не имеет смысла.
P.p.s. Избегайте "CodeRepositories". Это приводит к центрированию данных → процедурный код.
P.p.p.s Избегайте шаблона работы. Совокупные корни должны определять границы транзакций.

Ответ 4

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

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

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();
        }

    }

Ответ 5

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

Также, если вы хотите использовать методы, которые извлекают все телефоны независимо от пользователей, которым они принадлежат, вы все равно можете, хотя через репозиторий User вам нужен только один метод, который возвращает всех пользователей в качестве IQueryable, тогда вы можете сопоставить их, чтобы получить все пользовательские телефоны и сделайте уточненный запрос с этим. Поэтому в этом случае вам даже не нужен PhoneRepository. Кроме того, я бы предпочел использовать класс с методом расширений для IQueryable, который я могу использовать в любом месте не только из класса репозитория, если я хотел бы абстрагировать запросы за методами.

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