Я пытаюсь разработать приложение, которое имеет сложный бизнес-домен и требует поддержки REST API (не только REST, но и ориентированного на ресурсы). У меня есть некоторые проблемы, связанные с поиском модели предметной области, ориентированной на ресурсы.
В DDD клиентам доменной модели необходимо пройти процедурный уровень «Службы приложений», чтобы получить доступ к любой бизнес-функциональности, реализованной сущностями и доменными службами. Например, есть сервис приложений с двумя методами для обновления сущности User:
userService.ChangeName(name);
userService.ChangeEmail(email);
API этой службы приложений предоставляет команды (глаголы, процедуры), а не состояния.
Но если нам также нужно предоставить RESTful API для того же приложения, то существует модель ресурсов пользователя, которая выглядит следующим образом:
{
name:"name",
email:"email@mail.com"
}
Ресурсно-ориентированный API предоставляет состояние , а не команды . Это вызывает следующие проблемы:
каждая операция обновления для REST API может отображаться на один или несколько вызовов процедур Application Services в зависимости от того, какие свойства обновляются в модели ресурсов.
каждая операция обновления выглядит как атомарная для клиента REST API, но она не реализована таким образом. Каждый вызов службы приложений разработан как отдельная транзакция. Обновление одного поля в модели ресурсов может изменить правила проверки для других полей. Поэтому нам нужно проверить все поля модели ресурсов вместе, чтобы убедиться, что все потенциальные вызовы службы приложений действительны, прежде чем мы начнем их делать. Одновременная проверка набора команд гораздо менее тривиальна, чем выполнение по одной. Как мы можем это сделать на клиенте, который даже не знает, что существуют отдельные команды?
Вызов методов службы приложений в другом порядке может иметь другой эффект, в то время как REST API делает его похожим, что нет никакой разницы (в пределах одного ресурса)
Я мог бы придумать более похожие проблемы, но в основном все они вызваны одним и тем же. После каждого вызова службы приложений состояние системы меняется. Правила того, что является действительным изменением, набором действий, которые субъект может выполнить при следующем изменении. Ресурсно-ориентированный API пытается сделать все это похожим на атомарную операцию. Но сложность преодоления этого разрыва должна куда-то уходить, и это кажется огромным.
Кроме того, если пользовательский интерфейс более ориентирован на команды, что часто имеет место, то нам придется сопоставлять команды и ресурсы на стороне клиента, а затем обратно на стороне API.
Вопросов:
- Должна ли вся эта сложность обрабатываться (толстым) слоем отображения REST-to-AppService?
- Или я что-то упустил в моем понимании DDD / REST?
- Может ли REST просто быть непрактичным для демонстрации функциональности моделей предметной области при определенной (довольно низкой) степени сложности?
источник
Ответы:
У меня была та же проблема, и я «решил» ее, по-разному моделируя ресурсы REST, например:
Так что я в основном разделил большой и сложный ресурс на несколько меньших. Каждый из них содержит несколько связную группу атрибутов исходного ресурса, которые, как ожидается, будут обрабатываться вместе.
Каждая операция на этих ресурсах является атомарной, даже если она может быть реализована с использованием нескольких сервисных методов - по крайней мере, в Spring / Java EE не является проблемой создание более крупной транзакции из нескольких методов, которые изначально предназначались для собственной транзакции (с использованием транзакции REQUIRED). распространения). Часто для этого специального ресурса все еще требуется дополнительная проверка, но он все еще вполне управляем, поскольку атрибуты (предположительно) являются связными.
Это также хорошо для подхода HATEOAS, потому что ваши более детальные ресурсы передают больше информации о том, что вы можете с ними делать (вместо того, чтобы использовать эту логику как на клиенте, так и на сервере, потому что ее трудно представить в ресурсах).
Конечно, он не идеален - если пользовательские интерфейсы не моделируются с учетом этих ресурсов (особенно ориентированных на данные пользовательских интерфейсов), это может создать некоторые проблемы - например, пользовательский интерфейс представляет большую форму всех атрибутов данных ресурсов (и их подресурсов) и позволяет вам отредактируйте их все и сохраните их сразу - это создает иллюзию атомарности, даже если клиент должен вызвать несколько операций с ресурсами (которые сами являются атомарными, но вся последовательность не атомарна).
Кроме того, такое разделение ресурсов иногда бывает нелегким или очевидным. Я делаю это в основном на ресурсах со сложным поведением / жизненными циклами, чтобы управлять их сложностью.
источник
Ключевой вопрос здесь заключается в том, как прозрачно вызывается бизнес-логика при выполнении вызова REST? Это проблема, которая не решается напрямую REST.
Я решил эту проблему, создав собственный слой управления данными через поставщика постоянных данных, такого как JPA. Используя метамодель с пользовательскими аннотациями, мы можем вызывать соответствующую бизнес-логику при изменении состояния объекта. Это гарантирует, что независимо от того, как состояние объекта изменяет бизнес-логику, вызывается. Он сохраняет вашу архитектуру СУХОЙ, а также вашу бизнес-логику в одном месте.
Используя приведенный выше пример, мы можем вызвать метод бизнес-логики validateName, когда поле имени изменяется с помощью REST:
С таким инструментом в вашем распоряжении, все, что вам нужно сделать, это соответствующим образом аннотировать ваши методы бизнес-логики.
источник
Вы не должны раскрывать модель предметной области. Вы должны показывать приложение ориентированным на ресурсы способом.
Совсем нет - отправляйте команды ресурсам приложения, которые взаимодействуют с моделью домена.
Да, хотя есть немного другой способ записать это, что может сделать вещи проще; каждая операция обновления API REST отображается на процесс, который отправляет команды одному или нескольким агрегатам.
Вы преследуете не тот хвост здесь.
Представьте себе: полностью уберите REST из картинки. Вместо этого представьте, что вы пишете интерфейс рабочего стола для этого приложения. Далее давайте представим, что у вас действительно хорошие требования к дизайну, и вы реализуете пользовательский интерфейс на основе задач. Таким образом, пользователь получает минималистский интерфейс, который идеально настроен для задачи, над которой он работает; пользователь указывает некоторые входные данные, а затем нажимает "VERB!" кнопка.
Что происходит сейчас? С точки зрения пользователя, это единственная атомарная задача, которую необходимо выполнить. С точки зрения domainModel, это количество команд, выполняемых агрегатами, где каждая команда выполняется в отдельной транзакции. Это абсолютно несовместимо! Нам нужно что-то посередине, чтобы преодолеть разрыв!
Что-то это «приложение».
На успешном пути приложение получает некоторое DTO и анализирует этот объект, чтобы получить сообщение, которое оно понимает, и использует данные в сообщении для создания правильно сформированных команд для одного или нескольких агрегатов. Приложение удостоверится, что каждая из команд, которые оно отправляет агрегатам, правильно сформировано (это антикоррупционный слой в работе), и оно загрузит агрегаты и сохранит агрегаты, если транзакция завершится успешно. Агрегат сам решит, допустима ли команда, учитывая ее текущее состояние.
Возможные результаты - все команды выполняются успешно - антикоррупционный уровень отклоняет сообщение - некоторые команды выполняются успешно, но затем один из агрегатов жалуется, и у вас есть непредвиденные обстоятельства для смягчения.
Теперь представьте, что у вас есть это приложение; как вы взаимодействуете с ним RESTful способом?
Принято - это обычное открепление, когда приложение откладывает обработку сообщения до тех пор, пока не ответит клиенту - обычно используется при принятии асинхронной команды. Но это также хорошо работает для этого случая, когда операция, которая должна быть атомной, нуждается в смягчении.
В этой идиоме ресурс представляет саму задачу - вы запускаете новый экземпляр задачи, публикуя соответствующее представление в ресурсе задачи, и этот ресурс взаимодействует с приложением и направляет вас в следующее состояние приложения.
В ДДД , в значительной степени в любое время координируют несколько команд, вы хотите , чтобы думать в терминах процесса ( так называемый бизнес - процесс, известный как сага).
В модели чтения есть аналогичное концептуальное несоответствие. Опять же, рассмотрим интерфейс на основе задач; если задача требует изменения нескольких агрегатов, то пользовательский интерфейс для подготовки задачи, вероятно, содержит данные из нескольких агрегатов. Если ваша схема ресурсов 1: 1 с агрегатами, это будет сложно организовать; вместо этого предоставьте ресурс, который возвращает представление данных из нескольких агрегатов, вместе с гипермедиа-элементом управления, который отображает отношение «запуск задачи» к конечной точке задачи, как обсуждалось выше.
Смотрите также: ОТДЫХ на практике от Джима Уэббера.
источник