Доступ к данным в DDD?

После прочтения книг Эвана и Нильссона я все еще не уверен, как управлять доступом к данным в проекте, управляемом доменом. Должны ли методы CRUD быть частью репозиториев, то есть OrderRepository.GetOrdersByCustomer(customer), или они должны быть частью сущностей: Customer.GetOrders(). Последний подход кажется больше ОО, но он будет распределять доступ к данным для одного типа сущности среди нескольких объектов, то есть Customer.GetOrders(), Invoice.GetOrders(), ShipmentBatch.GetOrders() и т. Д. А как насчет вставки и обновления?

6 ответов

Решение

Методы CRUD должны быть частью репозитория... ish. Но я думаю, вы должны спросить, почему у вас есть куча методов CRUD. Что они на самом деле делают? Для чего они на самом деле? Если вы на самом деле вызываете шаблоны доступа к данным, которые использует ваше приложение, я думаю, что это делает хранилище намного более полезным и избавляет вас от необходимости выполнять операцию дробовика, когда в вашем домене происходят определенные типы изменений.

CustomerRepo.GetThoseWhoHaventPaidTheirBill()

// or

GetCustomer(new HaventPaidBillSpecification())

// is better than

foreach (var customer in GetCustomer()) {
    /* logic leaking all over the floor */
}

Методы типа "Сохранить" также должны быть частью репозитория.

Если у вас есть агрегированные корни, это избавит вас от взрыва в репозитории или от рассредоточенной логики: у вас нет 4х # шаблонов доступа к данным сущностей, только те, которые вы фактически используете в агрегированных корнях.

Это мои $.02.

DDD обычно предпочитает шаблон хранилища по сравнению с шаблоном активной записи, на который вы намекаете с помощью Customer.Save.

Недостатком модели Active Record является то, что она в значительной степени предполагает единую модель персистентности, за исключением некоторого особенно навязчивого кода (в большинстве языков).

Интерфейс хранилища определяется на уровне домена, но не знает, хранятся ли ваши данные в базе данных или нет. С помощью шаблона репозитория я могу создать InMemoryRepository, чтобы я мог тестировать логику домена изолированно и использовать внедрение зависимостей в приложении, например, чтобы уровень обслуживания создавал экземпляр SqlRepository.

Для многих людей наличие специального репозитория просто для тестирования звучит глупо, но если вы используете модель репозитория, вы можете обнаружить, что вам действительно не нужна база данных для вашего конкретного приложения; иногда простой FileRepository сделает свое дело. Свадьба с самим собой в базе данных, прежде чем вы знаете, что вам это нужно, потенциально ограничивает. Даже если база данных необходима, гораздо быстрее запускать тесты для InMemoryRepository.

Если у вас нет особого подхода к доменной логике, вам, вероятно, не нужен DDD. ActiveRecord вполне подходит для множества проблем, особенно если у вас есть в основном данные и немного логики.

Давайте сделаем шаг назад на секунду. Эванс рекомендует, чтобы репозитории возвращали совокупные корни, а не только сущности. Итак, если предположить, что ваш Клиент является совокупным корнем, который включает в себя Заказы, то, когда вы выбирали клиента из его хранилища, заказы приходили вместе с ним. Вы могли бы получить доступ к заказам, перейдя по отношениям между Заказчиком и Заказами.

customer.Orders;

Поэтому, чтобы ответить на ваш вопрос, операции CRUD присутствуют в совокупных корневых репозиториях.

CustomerRepository.Add(customer);
CustomerRepository.Get(customerID);
CustomerRepository.Save(customer);
CustomerRepository.Delete(customer);

Я сделал это обоими способами, о которых вы говорите. Мой предпочтительный подход сейчас - это постоянный невежественный (или PONO - Plain Ole' .Net Object) метод, в котором ваши доменные классы беспокоятся только о том, чтобы быть доменными классами. Они ничего не знают о том, как они сохраняются или даже сохраняются. Конечно, иногда вы должны быть прагматичными по этому поводу и учитывать такие вещи, как Id (но даже тогда я просто использую супертип слоя, который имеет Id, поэтому у меня может быть одна точка, где живут такие вещи, как значение по умолчанию)

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

Одна вещь, на которую стоит обратить внимание, это метод вздутия, от которого могут пострадать репозитории. GetOrderbyCustomer.. GetAllOrders.. GetOrders30DaysOld.. и т. Д. И т. Д. Одним из хороших решений этой проблемы является рассмотрение шаблона Query Object. И тогда ваши репозитории могут просто принять объект запроса для выполнения.

Я также настоятельно рекомендую изучить что-то вроде NHibernate. Он включает в себя множество концепций, которые делают хранилища такими полезными (объекты Identity Map, Cache, Query...)

Даже в DDD я бы держал классы и процедуры доступа к данным отдельно от сущностей.

Причины,

  1. Улучшается тестируемость
  2. Разделение проблем и модульная конструкция
  3. Более удобные для сопровождения, когда вы добавляете сущности, подпрограммы

Я не эксперт, просто мое мнение.

Раздражающей вещью Нильсона "Применение DDD&P" является то, что он всегда начинает с "Я бы не делал этого в реальном приложении, но...", а затем следует его пример. Возвращаясь к теме: я думаю, что OrderRepository.GetOrdersByCustomer (клиент) - это путь, но есть также обсуждение в списке рассылки ALT.Net ( http://tech.groups.yahoo.com/group/altdotnet/). о DDD.

Другие вопросы по тегам