Вопрос о репозиториях и их методах сохранения для объектов домена

У меня несколько нелепый вопрос относительно DDD, шаблонов репозитория и ORM. В этом примере у меня есть 3 класса: Адрес, Компания и Лицо. Лицо является членом компании и имеет адрес. Компания также имеет адрес.

Эти классы отражают модель базы данных. Я удалил все зависимости моих моделей, чтобы они не были привязаны к конкретной библиотеке ORM, такой как NHibernate или LinqToSql. Эти зависимости обрабатываются внутри репозиториев.

Внутри одного из репозиториев есть метод SavePerson (Person), который вставляет / обновляет объект Person в зависимости от того, существует ли он уже в базе данных.

Поскольку у объекта Person есть Company, я в настоящее время сохраняю / обновляю значения свойства Company при выполнении этого вызова SavePerson. Я вставляю / обновляю все данные компании - имя и адрес - во время этой процедуры.

Однако мне действительно трудно думать о случае, когда данные компании могут измениться во время работы с человеком - я хочу только иметь возможность назначить компанию человеку или переместить человека в другую компанию. Не думаю, что когда-нибудь захочу создавать новую Компанию вместе с новым Человеком. Таким образом, вызовы SaveCompany вводят ненужные вызовы базы данных. При сохранении человека я должен просто иметь возможность обновить столбец CompanyId.

Но поскольку у класса Person есть свойство Company, я несколько склонен обновлять / вставлять его вместе с ним. Со строгой / чистой точки зрения, метод SavePerson должен сохранять объект Person целиком.

Каким будет предпочтительный способ? Просто вставляете / обновляете CompanyId свойства Company при сохранении человека или всех его данных? Или вы бы создали два разных метода для обоих сценариев (как бы вы их назвали?)

Кроме того, еще один вопрос: в настоящее время у меня есть разные методы для сохранения человека, адреса и компании, поэтому, когда я сохраняю компанию, я также вызываю SaveAddress. Предположим, я использую LinqToSql - это означает, что я не вставляю / не обновляю компанию и адрес в одном запросе Linq. Я предполагаю, что есть 2 вызова Select (проверка, существует ли компания, проверка, существует ли адрес). И затем два вызова Insert / Update для обоих. Даже больше, если будет введено больше классов составных моделей. Есть ли способ для LinqToSql оптимизировать эти вызовы?

public class Address
{
    public int AddressId { get; set; }
    public string AddressLine1 { get; set; }
    public string AddressLine2 { get; set; }
    public string City { get; set; }
    public string PostalCode { get; set; }        
}

public class Company
{
    public int CompanyId { get; set; }
    public string Name { get; set; }
    public Address Address { get; set; }
}


public class Person
{
    public int PersonId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
    public Company Company { get; set; }
    public Address Address { get; set; }

}

Изменить

См. Также этот дополнительный вопрос. Как объекты-значения хранятся в базе данных?


person kitsune    schedule 23.03.2009    source источник
comment
Нет ответа, потому что я похож на вас в этом, но считали ли вы, что если Person является вашим Агрегатом, Company в этом случае является объектом неизменного значения? Тогда ваш Репозиторий не будет заботиться о сохранении свойств Компании, а только о самой ассоциации.   -  person jlembke    schedule 23.03.2009
comment
Кроме того, перед разработкой общих репозиториев вы можете прочитать эту статью Грега Янга. codebetter.com/blogs/gregyoung/ архив / 2009/01/16 /   -  person jlembke    schedule 23.03.2009


Ответы (2)


В последнее время я сам использовал подход IRepository, который предлагает Кейт. Но вам не следует сосредотачиваться на этом шаблоне здесь. Вместо этого есть еще несколько частей из учебника DDD, которые можно применить здесь.

Используйте объекты значений для своих адресов

Во-первых, вы можете применить здесь концепцию Value Objects (VO). В вашем случае это будет Адрес. Разница между объектом значения и объектом сущности состоит в том, что сущности имеют идентичность; ВО нет. Идентичность ВО на самом деле является суммой его свойств, а не уникальной идентичностью. В книге Быстрая разработка доменного диска (это также бесплатную загрузку PDF-файла), он очень хорошо объясняет это, заявляя, что адрес на самом деле является просто точкой на Земле и не требует отдельной идентичности, подобной SocialSecurity, как человек. Эта точка на Земле представляет собой комбинацию улицы, номера, города, индекса и страны. Он может иметь значения широты и долготы, но по определению это даже ВО, потому что это комбинация двух точек.

Используйте Сервисы для объединения ваших сущностей в единую сущность, чтобы действовать.

Также не забывайте о концепции Сервисов в учебнике DDD. В вашем примере эта услуга будет:

public class PersonCompanyService
{
  void SavePersonCompany(IPersonCompany personCompany)
  {
    personRepository.SavePerson();
    // do some work for a new company, etc.
    companyRepository.SaveCompany();
  }
}

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

ORM обычно требуют идентификации, и точка.

Теперь, как бы вы записали VO адреса в базу данных? Очевидно, вы бы использовали IAddressRepository. Но поскольку большинство ORM (например, LingToSql) требуют, чтобы все объекты имели идентификатор, вот трюк: пометьте идентификатор как внутренний в вашей модели, чтобы он не отображался за пределами вашего уровня модели. Это собственный совет Стивена Сандерсона.

public class Address
{
  // make your identity internal
  [Column(IsPrimaryKey = true
    , IsDbGenerated = true
    , AutoSync = AutoSync.OnInsert)]
  internal int AddressID { get; set; }

  // everything else public
  [Column]
  public string StreetNumber { get; set; }
  [Column]
  public string Street { get; set; }
  [Column]
  public string City { get; set; }
  ...
}
person eduncan911    schedule 23.03.2009
comment
Спасибо, я изучу это и сообщу! - person kitsune; 24.03.2009
comment
Хорошо, вопрос, я не использую классы LinqToSql напрямую, у меня есть собственные модели предметной области, которые не зависят от LinqToSql. Репозитории сопоставляют эти модели с моделями LinqToSql для сохранения и извлечения. Поскольку модель предметной области Address не будет иметь AddressId, как вы решите проблему доступа к данным? - person kitsune; 24.03.2009
comment
Думаю, мне придется сохранить значения в таблицах Person / Company! По мнению всех гуру DDD, объекты-значения должны быть неизменными. У них нет личности. Таким образом, адрес должен быть где-то сохранен, есть связь между человеком и адресом, но нет идентификатора для создания отношения ... - person kitsune; 24.03.2009
comment
Согласно моим книгам DDD: с объектами значений, когда я хочу, чтобы у человека был новый адрес, я бы создал новый объект адреса и указывал значения с помощью его метода конструктора. Старый адрес будет удален, есть способ изменить существующий адрес. - person kitsune; 24.03.2009
comment
Да, объекты-значения должны быть неизменными. Но если вы хотите использовать ORM, у вас действительно нет другого выбора - у него должен быть какой-то тип личности. Я действительно не хочу украшать свою модель атрибутами Linq, как описано выше. При необходимости их можно легко снять. - person eduncan911; 25.03.2009
comment
Это может противоречить моему описанному выше IAddressRepository, но в чистой модели концепция использования ORM с неизменяемыми виртуальными объектами заключается в управлении записью и извлечением всех виртуальных объектов для агрегированного корня во время его создания и сохранения в БД. Другими словами, вы всегда сохраняете коллекции адресов. - person eduncan911; 25.03.2009
comment
Возвращаясь к ORM, вы должны использовать внутренний модификатор, который я описал выше, чтобы дать Address () VO внутреннюю идентификацию. Какой вы бы использовали эту личность для ваших отношений EntityRef ‹T› в вашей модели. Это контролирует LinqToSql с внутренними идентификаторами, не раскрывая его публично. - person eduncan911; 25.03.2009
comment
Если вы хотите, чтобы абстракция LinqToSql была отделена от ваших репозиториев (идеальное состояние использования уровня инфраструктуры, как заявлено гуру DDD), тогда ваш код должен ожидать, что при каждом сохранении будут записываться все Addresses () из модели Customer. Вам нужно будет установить личность там. - person eduncan911; 25.03.2009
comment
Из того, что я собрал, NHibernate реализует объекты значений с компонентами и сохраняет их в агрегированном корне, поэтому, если у человека есть домашний и рабочий адрес, в таблице людей будут дополнительные столбцы (HomeAddress_AddressLine1 и т. Д.). LinqToSql на самом деле не поддерживает объекты значений. - person kitsune; 25.03.2009
comment
См. Это интервью о LinqToSql и объектах-значениях: infoq.com/interviews/jimmy-nilsson-linq - person kitsune; 25.03.2009
comment
Кстати, я действительно многому научился во время этого расследования, спасибо за ваш вклад - person kitsune; 25.03.2009

Исходя из моего недавнего опыта использования шаблона репозитория, я думаю, вам было бы полезно использовать общий репозиторий, теперь уже распространенный IRepository T. Таким образом вам не нужно будет добавлять методы репозитория, такие как SavePerson (Person person). Вместо этого у вас будет что-то вроде:

IRepository<Person> personRepository = new Repository<Person>();
Person realPerson = new Person();
personRepository.SaveOrUpdate(realPerson);

Этот метод также хорошо подходит для разработки через тестирование и имитации.

Я считаю, что вопросы о поведении в вашем описании будут касаться домена, возможно, вам следует иметь метод AddCompany в своем классе Person и изменить свойство Company на

public Company Company { get; private set; }

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

Вернувшись в репозиторий, взгляните на этот post для хорошего объяснения IRepository через LinqToSql. В блоге Майка есть много других сообщений о репозиториях. Когда вы все же решите выбрать ORM, я могу порекомендовать HHibernate вместо LinqToSql, последний теперь не функционирует, а у NHibernate есть отличное сообщество поддержки.

person Keith Bloom    schedule 23.03.2009
comment
Я очень много работал над реализацией общих репозиториев, пока не прочитал этот пост Грега Янга. Отличные моменты, которые мне не приходили в голову. codebetter.com/blogs/gregyoung/ архив / 2009/01/16 / - person jlembke; 23.03.2009
comment
Я не видел смысла в общих репозиториях - они действительно не имеют особого смысла в моем приложении ... - person kitsune; 23.03.2009
comment
@Jlembke, хороший пост от Грега Янга. IRepository отлично подходит для начала, но я понимаю необходимость контролировать обработку запросов. Мне нравится идея использования композиции с общим репозиторием, используемым в конкретном экземпляре. Это может хорошо работать с чем-то вроде IoC - person Keith Bloom; 24.03.2009
comment
@Kitsune, из интереса, как ты строишь свой репозиторий? Можешь показать и пример? - person Keith Bloom; 24.03.2009