EF 4.2 Первый код и проблемы проектирования DDD

У меня есть несколько проблем при попытке сначала выполнить DDD-разработку с помощью EF 4.2 (или EF 4.1). Я провел несколько исследований, но не нашел конкретных ответов на мои конкретные вопросы. Вот мои проблемы:

  • Домен не может знать о слое persistence, или, другими словами, домен полностью отделен от EF. Однако для сохранения данных в базе данных каждый объект должен быть присоединен или добавлен в контекст EF. Я знаю, что вы должны использовать фабрики для создания экземпляров совокупных корней, чтобы factory мог зарегистрировать созданный объект в контексте EF. Это, по-видимому, нарушает правила DDD, поскольку factory является частью домена, а не частью слоя сохранения. Как мне нужно создавать и регистрировать объекты, чтобы они сохранялись в базе данных при необходимости?

  • Должен ли агрегированный объект быть тем, кто создает дочерние объекты? Я имею в виду, если у меня есть Organization и что Organization имеет коллекцию объектов Employee, должен ли Organization иметь метод, например CreateEmployee или AddEmployee? Если нет, то где создается объект Employee, имея в виду, что корневой узел Organization принадлежит каждому Employee сущности.

  • При первом использовании кода EF идентификаторы (в виде столбцов идентификации в базе данных) каждого объекта автоматически обрабатываются и, как правило, никогда не изменяются кодом пользователя. Поскольку DDD заявляет, что домен отделен от незнания персистентности, кажется, что выявление идентификаторов является странным делом в этом домене, потому что это означает, что домен должен обрабатывать назначение уникальных идентификаторов вновь созданным объектам. Должен ли я беспокоиться об экспонировании свойств идентификатора объектов?

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

Спасибо заранее!

Ответы

Ответ 1

В 1: я не все, кто знаком с EF, но используя подход, основанный на использовании кода/конвенции, я бы предположил, что не слишком сложно сопоставлять POCO с геттерами и сеттерами (даже поддерживая это "DbContext с классом DbSet properties" в другом проекте не должно быть так сложно). Я бы не стал рассматривать POCOs как Агрегатный корень. Скорее они представляют собой "состояние внутри совокупности, которое вы хотите сохранить". Пример ниже:

// This is what gets persisted
public class TrainStationState {
  public Guid Id { get; set; }
  public string FullName { get; set; }
  public double Latitude { get; set; }
  public double Longitude { get; set; }

  // ... more state here
}

// This is what you work with
public class TrainStation : IExpose<TrainStationState> { 
  TrainStationState _state;

  public TrainStation(TrainStationState state) {
    _state = state;
    //You can also copy into member variables
    //the state that required to make this
    //object work (think memento pattern).
    //Alternatively you could have a parameter-less
    //constructor and an explicit method
    //to restore/install state.
  }

  TrainStationState IExpose.GetState() {
    return _state;
    //Again, nothing stopping you from
    //assembling this "state object"
    //manually.
  }

  public void IncludeInRoute(TrainRoute route) {
    route.AddStation(_state.Id, _state.Latitude, _state.Longitude);
  }
}

Теперь, что касается совокупного жизненного цикла, есть два основных сценария:

  • Создание нового агрегата. Вы можете использовать метод factory, factory, конструктор, конструктор и т.д., что подходит вашим потребностям. Когда вам нужно сохранить агрегат, запросите его состояние и сохраните его (обычно этот код не находится внутри вашего домена и довольно общий).
  • Получение существующего агрегата: вы можете использовать репозиторий, dao,... все, что вам подходит. Важно понимать, что то, что вы извлекаете из постоянного хранилища, - это состояние POCO, которое вам нужно вводить в нетронутый агрегат (или использовать его для заполнения его частными членами). Все это происходит за фасадом репозитория /DAO. Не смешивайте свои сайты вызовов с этим общим поведением.

В 2: Несколько вещей приходят на ум. Вот список:

  • Агрегатные корни - это границы последовательности. Какие требования согласованности вы видите между Организацией и Работником?
  • Организация COULD действует как factory Employee, не изменяя состояние организации.
  • "Собственность" - это не то, что содержит агрегаты.
  • Агрегатные корни обычно имеют методы, которые создают сущности внутри совокупности. Это имеет смысл, потому что корни отвечают за обеспечение согласованности в совокупности.

В 3: Назначьте идентификаторы извне, переходите через него, переходите. Это не означает, что они подвергаются их воздействию (только в состоянии POCO).

Ответ 2

  • Основная проблема с совместимостью с EF-DDD, похоже, заключается в сохранении частных свойств. Решение, предложенное Ивом, как представляется, является обходным решением для отсутствия мощности EF в некоторых случаях. Например, вы не можете действительно DDD с Fluent API, который требует, чтобы свойства состояния были общедоступными. Я обнаружил, что только сопоставление с .edmx файлами позволяет вам удалить объекты домена. Это не гарантирует, что вы создадите вещи для публикации или добавите атрибуты, зависящие от EF.

  • Сущности должны всегда создаваться каким-то совокупным корнем. Посмотреть большую публикацию Udi Dahan: http://www.udidahan.com/2009/06/29/dont-create-aggregate-roots/ Всегда загружая некоторую совокупность и создавая сущности оттуда, также решает проблему присоединения объекта к контексту EF. В этом случае вам не нужно прикладывать что-либо вручную. Он будет автоматически подключен, потому что агрегат, загруженный из репозитория, уже подключен и имеет ссылку на новый объект. В то время как интерфейс репозитория принадлежит домену, реализация репозитория принадлежит инфраструктуре и знает об EF, контекстах, подключении и т.д.

  • Я склонен рассматривать автогенерированные идентификаторы как детали реализации постоянного хранилища, которые должны быть рассмотрены объектом домена, но не должны отображаться. Таким образом, у меня есть свойство private ID, которое сопоставляется с автогенерированным столбцом и другим, общедоступным идентификатором, который имеет значение для Домена, например идентификатор удостоверения личности или номер паспорта для класса Person. Если таких значимых данных нет, тогда я использую тип Guid, который имеет отличную возможность создания (почти) уникальных идентификаторов без необходимости вызова базы данных. Таким образом, в этом шаблоне я использую эти Guid/MeaningfulID для загрузки агрегатов из репозитория, в то время как автогенерированные идентификаторы используются внутри базы данных, чтобы сделать бит быстрее соединений (Guid не подходит для этого).