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 не подходит для этого).