DDD: где поставить логику сохранения и когда использовать сопоставление ORM

Мы внимательно изучаем наши шаблоны веб-приложений (Java). Раньше мы страдали от чрезмерно анемичной объектной модели и чрезмерно процедурного разделения между контроллерами, службами и DAO, с простыми объектами ценности (в основном просто мешками данных), перемещающимися между ними. Мы использовали декларативный (XML) управляемый ORM (Hibernate) для сохранения. Управление всеми сущностями происходило в DAO.

При попытке перейти к более богатой модели домена мы сталкиваемся с тем, как лучше всего разрабатывать слой persistence. Я потратил много времени на чтение и размышление о шаблонах проектирования Driven Design. Тем не менее, я хотел бы получить совет.

Во-первых, вещи, о которых я более уверенно:

  • У нас будут "тонкие" контроллеры спереди, которые имеют дело только с форматами обработки HTTP и HTML, валидацией, логикой пользовательского интерфейса.

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

  • У нас будет более богатая модель домена, которая содержит состояние, отношения и логику, присущие объектам в этой модели домена.

Вопрос о настойчивости. Раньше наши сервисы были бы введены (через Spring) с помощью DAO и будут использовать методы DAO, такие как find() и save() для выполнения сохранения. Однако более богатая модель домена, по-видимому, подразумевает, что объекты должны знать, как сохранять и удалять себя, и, возможно, что услуги более высокого уровня должны знать, как найти (запрос для) объектов домена.

Здесь возникает несколько вопросов и неопределенностей:

  • Мы хотим внедрить DAO в объекты домена, чтобы они могли выполнять "this.someDao.save(this)" в методе save()? Это немного неудобно, так как объекты домена не являются одноточиями, поэтому нам понадобятся фабрики или пост-строительная установка DAO. При загрузке объектов из базы данных это становится беспорядочным. Я знаю, что для этого можно использовать Spring AOP, но я не мог заставить его работать (используя Play! Framework, еще одну линию экспериментов), и это кажется довольно грязным и волшебным.

  • Не будем ли мы полностью разделять DAO (репозитории?) наравне с службами бизнес-логики без состояния? Это может иметь определенный смысл, но это означает, что если "сохранить" или "удалить" являются неотъемлемыми операциями объекта домена, объект домена не может выразить их.

  • Мы просто обойдемся с DAO полностью и используем JPA, чтобы позволить сущности управлять собой.

В этом заключается следующая тонкость: довольно удобно отображать объекты, используя JPA. Игра! framework дает нам хороший класс базовых объектов тоже с такими операциями, как save() и delete(). Однако это означает, что наши объекты модели домена довольно тесно связаны с структурой базы данных, и мы передаем объекты с большой логикой постоянства, возможно, вплоть до уровня представления. Если ничего другого, это сделает модель домена менее пригодной для повторного использования в других контекстах.

Если мы хотим избежать этого, нам потребуется какое-то отображение DAO - либо с использованием простого JDBC (или, по крайней мере, Spring JdbcTemplate), либо с использованием параллельной иерархии объектов базы данных и "бизнес-сущностей", DAO навсегда копируют информацию из одной иерархии в другую.

Каков правильный выбор дизайна здесь?

Martin

Ответы

Ответ 1

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

Я предлагаю вам сохранить существующую систему впрыска DAO (хорошая вещь для модульного тестирования) и оставить слой persistence как есть, пытаясь переместить некоторую бизнес-логику в свои объекты, где она подходит. Хорошей отправной точкой для этого является определение агрегатов и создание сводных корней. Они часто содержат больше бизнес-логики, чем другие сущности.

Однако это не означает, что объекты домена должны содержать всю логику (особенно не логику, необходимую многим другим объектам в приложении, которые часто принадлежат Сервисам).

Ответ 2

Я не эксперт по Java, но я использую NHibernate в своем .NET-коде, поэтому мой опыт должен быть непосредственно переводимым в мир Java.

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

public void SayHello(int id, String helloString)
{
    SomeDomainObject target = domainObjectRepository.findById(id); //This uses Hibernate to load the object.

    target.sayHello(helloString); //There is a single domain object method invocation per application service method.

    domainObjectRepository.Save(target); //This one is optional. Hibernate should already know that this object needs saving because it tracks changes.
}

Любые изменения объектов, содержащихся в DomainObject (также добавление объектов в коллекции), будут обрабатываться с помощью Hibernate.

Вам также понадобится какой-то AOP для перехвата вызовов метода службы приложений и создания сеанса Hibernate до того, как этот метод выполнит и сохранит изменения после завершения метода без исключений.

Есть действительно хороший пример, как сделать DDD в Java здесь. Он основан на проблеме выборки из "Blue Book" Эрика Эванса. Код примера логического кода приложения здесь.