Масштабирование модели с богатым доменом

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

Я использовал это на практике в проекте с одним человеком среднего размера ( > 100 тыс. строк Java), и я обнаруживаю много преимуществ, в основном гибкость и релятивируемость, которые это предлагает в отношении подхода, ориентированного на базу данных. Я могу добавлять и удалять классы домена, ударять несколько кнопок и всю новую схему базы данных, а слой SQL выкатывается.

Тем не менее, я часто сталкиваюсь с проблемами, когда мне сложно согласовать логику богатого домена с тем фактом, что база данных SQL поддерживает приложение. В общем, это приводит к типичной проблеме "1 + N запросов", где вы выбираете N объектов, а затем выполняете нетривиальный метод для каждого объекта, который снова вызывает запросы. Оптимизация этого вручную позволяет выполнять этот процесс в постоянном количестве SQL-запросов.

В моем проекте я разрешаю системе подключать эти оптимизированные версии. Я делаю это, перемещая код в "модуль запроса", который содержит десятки запросов, специфичных для домена (например, getActiveUsers), из которых у меня есть оба -memory (наивные и не масштабируемые) и SQL-based (для использования в развертывании). Это позволяет мне оптимизировать горячие точки, но есть два основных недостатка:

  • Я фактически перемещаю часть своей логики домена в места, где она действительно не принадлежит, и фактически даже подталкивает ее к операторам SQL.
  • Процесс требует, чтобы я просматривал журналы запросов, чтобы узнать, где находятся горячие точки, после чего я должен реорганизовать код, уменьшая абстракцию уровня, опуская его в запросы.

Есть ли лучший, более чистый способ согласования Domain-Driven-Design и его богатой модели домена с тем фактом, что вы не можете иметь все свои сущности в памяти и поэтому ограничены базой данных базы данных?

Ответ 1

Существует два способа взглянуть на эту проблему: одна из них - техническая "что я могу сделать, чтобы загрузить мою интеллектуальную версию". Единственная действительно умная вещь, о которой я знаю, это динамические коллекции, которые частично загружаются с остальными загруженными по требованию, с возможностью предварительной загрузки деталей. Был интересный разговор на JavaZone 2008 об этом

Второй подход был больше моего внимания в то время, когда я работал с DDD; как я могу сделать свою модель так, чтобы она была более "загружаемой", не жертвуя слишком большой долей DDD. Моя гипотеза на протяжении многих лет заключалась в том, что многие модели DDD моделируют концепции домена, которые на самом деле являются суммой всех допустимых состояний домена во всех бизнес-процессах и разных состояниях, которые происходят в каждом бизнес-процессе с течением времени. Я считаю, что многие из этих проблем с загрузкой очень сильно уменьшаются, если модели домена нормализуются немного больше в терминах процессов/состояний. Обычно это означает, что объект "Заказ" отсутствует, потому что ordrer обычно существует в нескольких разных состояниях, которые имеют довольно разную семантику (ShoppingCartOrder, ShippedOrder, InvoicedOrder, HistoricalOrder). Если вы пытаетесь инкапсулировать это один объект Order, вы всегда получаете много проблем с загрузкой/строительством.

Но здесь нет серебряной пули.

Ответ 2

По моему опыту, это единственный способ сделать что-то. Если вы пишете систему, которая пытается полностью скрыть или абстрагировать слой персистентности, то вы не сможете оптимизировать вещи, используя специфику уровня персистентности.

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

Сначала напишите метод ListAllUsers, который делает все на уровне домена. Некоторое время это будет работать, тогда он начнет становиться слишком медленным.

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

Теперь вы можете написать слой, который проверяет эти интерфейсы и вызывает конкретный метод, если он существует.

Это не идеально, и у меня нет большого опыта в этом. Но мне кажется, что ключ заключается в том, чтобы убедиться, что, если вы используете метод наивного персистентности, тогда весь код должен работать. Любая оптимизация должна быть сделана в качестве дополнения к этому.

Ответ 3

Нет, не совсем. Не то, чтобы я все равно знал (хотя мне интересно услышать ответы сторонников DDD на противоположное).

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

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

Ответ 4

Я считаю, что вы должны рассматривать слой запроса как часть логики вашего домена. Вы должны позволить себе писать оптимизированные запросы, которые могут быть выполнены только с "интимными" знаниями вашего решения о сохранении. Не пытайтесь отвлечься от всего. Кроме того, пакетная обработка - это еще одна часть вашего приложения, которая также должна иметь возможность знать ваш домен. Мне не нужно стараться избегать пакетной обработки просто потому, что я не могу поместить его в мою модель домена. Однако вы можете комбинировать подходы: использовать запросы, чтобы узнать, какие объекты необходимо изменить, затем поочередно их идентификаторы и обрабатывать каждый самостоятельно, используя логику вашего домена.