HATEOAS REST API и дизайн, управляемый доменом, где разместить логику рабочего процесса?

Это задумано как дополнительный вопрос к RESTful API: Где я должен кодировать мой рабочий процесс? Краткое изложение вопроса (адаптированное, чтобы лучше соответствовать моему вопросу) будет выглядеть примерно так:

Каждый предметный объект содержит бизнес-логику, связанную с конкретным объектом в определенном ограниченном контексте (X). REST API содержит логику для преобразования результата запроса или команды в данные, отправляемые по сети (например, JSON). При использовании HATEOAS и гипермедиа мы хотим моделировать отношения между ресурсами с помощью ссылок. Но чтобы определить, какие ссылки должен возвращать REST API, часто приходится прибегать к бизнес-логике/правилам. Вопрос в том, где эти «правила рабочего процесса» относятся к приложению DDD? Возможно, они будут находиться в другом ограниченном контексте, имеющем дело только с правилами рабочего процесса (возможно, в «партнерских» отношениях с X), или они будут принадлежать X BC?


person Johan    schedule 24.05.2015    source источник
comment
У вас есть конкретный пример того, какая бизнес-логика необходима для разрешения ссылок?   -  person plalx    schedule 24.05.2015
comment
Возможно, не конкретный, но один пример, который я могу придумать, — это проблемы с авторизацией. Например, пользователю U может быть разрешено только читать и обновлять ресурс R, но не удалять его. В этих случаях ссылку на удаление не следует включать в ответ. Другим примером может быть VIP-клиент, которому предоставляются дополнительные ссылки по сравнению с не VIP-клиентом.   -  person Johan    schedule 24.05.2015
comment
Хорошим примером может быть приложение электронной коммерции, которое позволяет вам добавить что-то в корзину, потому что этого нет в наличии.   -  person Chris DaMour    schedule 27.05.2015


Ответы (3)


Я не эксперт по DDD (только через 100 страниц в книге Эрика Эвана), но я могу рассказать вам, что произошло в нашем случае с каталогом электронной коммерции.

Первоначально у нас был такой бизнес-поток в том же ограниченном контексте приложения на основе данных и ролей/разрешений запрашивающего пользователя, мы изменили переходы состояний (вы сказали ссылки, но на самом деле это суть, ссылки — это всего лишь один из способов представить переходы между состояниями), представленные пользователю. Это работало нормально, но выполнялось много повторяющейся логики. Затем мы добавили поисковое приложение с другим ограниченным контекстом, поскольку оно представляло одни и те же данные, но в разных коллекциях/группах, и мы не хотели, чтобы они рассинхронизировались.

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

Кое-что происходит на лету при запросе ресурса, практически на этапе сериализации. Мы нацелены на то, чтобы как можно больше переместить их в предварительно рассчитанные, но то, что мы не можем предварительно вычислить (только из-за масштаба), — это вещи, которые основаны конкретно на пользователе. Например, рекомендуемый поиск, который использует данные BI в поисковой системе для получения результатов. Мы могли бы предварительно рассчитать это для каждого отдельного пользователя, но сейчас цифры слишком велики для наших систем. Таким образом, мы отправляем расчетный ресурс основного приложения (из основного контекста) и передаем его через еще один партнерский контекст для дальнейшего уточнения.

другой вариант использования — некоторые ссылки представлены только аутентифицированному пользователю и, таким образом, скрыты от анонимных пользователей. Мы делаем это в основном контексте приложений, но это начинает немного мешать, потому что их присутствие указывает на то, что пользователь, стоящий за запросом, может делать в других приложениях (например, изменить пароль), и наш контекст на самом деле не является авторитетом того, что пользователь может сделать в каком-то другом приложении. Было бы лучше передать ресурс их контексту и позволить им обработать его, прежде чем мы вернем его пользователю. Одно простое решение, которое мы использовали для этого, заключалось в том, что вместо прямых ссылок на функции во внешнем контексте мы ссылаемся на корневой ресурс этого контекста и позволяем ему представлять переходы состояний. Это означает, что есть 2 запроса, но это очищает местоположения/полномочия логики.

person Chris DaMour    schedule 26.05.2015

Рабочий процесс на самом деле состоит из знаний предметной области и инфраструктуры ненависти.

Если приложение не разработано с использованием гипермедиа, вам может даже не понадобиться часть ненависти. Но знания предметной области все еще существуют, они просто живут в умах конечных пользователей. Вам нужно знание предметной области, чтобы проверить команду пользователя на случай, если он ее забудет.

Итак, для ненависти я разделил реализацию рабочего процесса на две части: модели предметной области и инфраструктура ненависти.

Модели предметной области рассказывают о предметной области.
Инфраструктура ненависти, с другой стороны, является специфической для ненависти, выведите их из предметной области.

Вот пример Java с использованием springframework-hateoas. Я поместил рабочий процесс в процедуру сопоставления моделей предметной области с ресурсами.

@Component
public class OrderResourceAssembler extends ResourceAssemblerSupport<Order, OrderResource> {

    @Override
    public OrderResource toResource(Order model) {

        OrderResource resource = mapToResource(model);
        resource.add(orderResourceHref(model).withSelfRel());//add self link
        if (model.isAvailableToCancel()) { //let the model tell
            //add cancel link
            resource.add(orderResourceHref(model).withRel("cancel")); 
        }
        if (model.isAvailableToPay()) { //let the model tell
            //add payment link
            resource.add(new Link("http://www.restfriedchicken.com/online-txn/" + model.getTrackingId(), "payment")); 
        }
        //etc


        return resource;
    }
    // omitted codes
}

Если модель не может сказать сама по себе, вы можете ввести объект спецификации, чтобы выполнить эту работу.

person Yugang Zhou    schedule 27.05.2015

Где бы ни находился ресурс, это то же самое место, где необходимо маршалировать доступ к нему.

Допустим, у вас есть более крупная система с тремя ограниченными контекстами: управление персоналом, привлечение клиентов и предоставление услуг.

ПОЛУЧИТЬ информацию о сотруднике от управления персоналом? Решение о том, показывать ли ссылку, которую можно использовать для POST или DELETE роли, должно решать руководство сотрудников.

ПОЛУЧИТЬ новый контракт на обслуживание от клиентов? Решение о том, отображать ли ссылку, которая может быть использована для публикации версии или POST, остается за клиентом. Конечно, руководство сотрудников контролирует распределение ролей, необходимых для работы с новыми контрактами на обслуживание, но прием клиентов отвечает за знание того, какие претензии и другие условия требуются, и в конечном итоге гарантирует, что все будет проверено.

Аналогично, ПОЛУЧИТЕ профиль программного обеспечения физической установки из предоставления услуг? Предоставление услуг должно убедиться, что все в порядке с другими службами/контекстами (учетная запись клиента оплачена, текущий пользователь в роли сетевых служб, устройство не заблокировано для обслуживания), прежде чем оно выдаст ссылку, по которой вы можете ПОСТАВИТЬ новое качество обслуживания.

Такого рода решения «рабочего процесса» являются неотъемлемой частью каждого контекста — их не следует выделять где-либо еще.

person tuespetre    schedule 10.06.2015