Как REST API подходит для домена, основанного на командах / действиях?

24

В этой статье автор утверждает, что

Иногда требуется представить в API операцию, которая по своей природе не является RESTful.

и это

Если API имеет слишком много действий, то это указывает на то, что он был разработан с точки зрения RPC, а не с использованием принципов RESTful, или что данный API, естественно, лучше подходит для модели типа RPC.

Это отражает то, что я читал и слышал и в других местах.

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

Пример I: Завершение работы виртуальной машины через интерфейс REST

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

1. Заплатить государственную собственность ресурса

PATCH /api/virtualmachines/42
Content-Type:application/json  

{ "state": "shutting down" }

(В качестве альтернативы PUTна подресурсе /api/virtualmachines/42/state.)

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

2. PUT или POST для свойства действия ресурса

PUT /api/virtualmachines/42/actions
Content-Type:application/json  

{ "type": "shutdown" }

Результат точно такой же, как в первом примере. Состояние будет обновлено до «выключения» немедленно и, возможно, в конечном итоге до «выключения».

Оба дизайна RESTful?

Какой дизайн лучше?

Пример II: CQRS

Что если у нас есть домен CQRS со многими такими «действиями» (или командами), которые потенциально могут привести к обновлениям нескольких агрегатов или не могут быть сопоставлены с операциями CRUD на конкретных ресурсах и подресурсах?

Должны ли мы пытаться моделировать столько команд, сколько конкретных создает или обновляет конкретные ресурсы, где это возможно (следуя первому подходу из примера I), и использовать «конечные точки действия» для остальных?

Или мы должны сопоставить все команды конечным точкам действия (как во втором подходе примера I)?

Где мы должны провести черту? Когда дизайн становится менее RESTful?

Модель CQRS лучше подходит для API, подобного RPC?

Согласно приведенному выше тексту, как я понимаю.

Как вы можете видеть из моих многочисленных вопросов, я немного запутался в этой теме. Можете ли вы помочь мне лучше понять это?

leifbattermann
источник
«создание действия» не выглядит как RESTful, за исключением случаев, когда выполняемое действие имеет собственный идентификатор ресурса впоследствии. В противном случае изменение свойства «state» через PATCH или PUT имеет больше смысла. Что касается CQRS, у меня пока нет хорошего ответа.
Фабиан Шменглер
3
@Laiv В этом нет ничего плохого. Это академический вопрос, я хотел бы получить более глубокое понимание этого вопроса.
leifbattermann

Ответы:

19

В первом случае (закрытие виртуальных машин) я бы не рассмотрел ни одну из альтернатив OP Rfulful. Конечно, если вы используете модель зрелости Ричардсона в качестве критерия, они оба являются API уровня 2, потому что они используют ресурсы и глаголы.

Однако ни один из них не использует элементы управления гипермедиа, и, на мой взгляд, это единственный тип REST, который отличает дизайн RESTful API от RPC. Другими словами, придерживайтесь уровня 1 и 2, и в большинстве случаев у вас будет API в стиле RPC.

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

{
    "links": [{
        "rel": "shut-down",
        "href": "/vms/1234/fdaIX"
    }, {
        "rel": "power-off",
        "href": "/vms/1234/CHTY91"
    }],
    "name": "Ploeh",
    "started": "2016-08-21T12:34:23Z"
}

Если клиент желает завершить работу Ploehвиртуальной машины, он должен перейти по ссылке с shut-downтипом отношения. (Обычно, как указано в RESTful Web Services Cookbook , вы бы использовали IRI или более сложную схему идентификации для типов отношений, но я решил сохранить пример как можно более простым.)

В этом случае есть немного другой информации, чтобы предоставить с действием, поэтому клиент должен просто сделать пустой POST для URL в href:

POST /vms/1234/fdaIX HTTP/1.1

(Поскольку у этого запроса нет тела, было бы заманчиво смоделировать его как запрос GET, но запросы GET не должны иметь видимых побочных эффектов, поэтому POST более корректен.)

Аналогичным образом, если клиент хочет отключить виртуальную машину, он перейдет по power-offссылке.

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

Чтобы пример был как можно более понятным, я намеренно скрыл URL-адреса в каждой ссылке. Когда хостинг - сервер получает входящий запрос, он будет знать , что fdaIXсредства закрыты , и CHTY91средство отключения питания .

Обычно я просто кодирую действие в самом URL, чтобы URL были /vms/1234/shut-downи /vms/1234/power-off, но при обучении это стирает различие между типами отношений (семантика) и URL (подробности реализации).

В зависимости от того, какие клиенты у вас есть, вы можете сделать URL-адреса RESTful не взломанными .

CQRS

Когда дело доходит до CQRS, Грег Янг и Уди Дахан соглашаются с тем, что CQRS не является архитектурой верхнего уровня . Поэтому я бы с осторожностью относился к тому, чтобы сделать RESTful API слишком похожим на CQRS, потому что это означало бы, что клиенты становятся частью вашей архитектуры.

Часто движущей силой настоящего (уровня 3) API RESTful является то, что вы хотите иметь возможность развивать свой API, не нарушая клиентов и не имея контроля над клиентами. Если это ваша мотивация, то CQRS не будет моим первым выбором.

Марк Симанн
источник
Вы имеете в виду, что первые примеры не RESTful, потому что они не используют элементы управления гипермедиа? Но я даже не опубликовал никаких ответов, только URL-адреса запросов и тела.
leifbattermann
4
@leifbattermann Они не RESTful, потому что они используют тело сообщения для сообщения намерения; это явно RPC. Если вы использовали ссылки для доступа к этим ресурсам, то зачем вам нужно сообщать о намерениях через тело?
Марк Симанн
В этом есть смысл. Почему вы предлагаете POST? Разве действие не идемпотентно? В любом случае, как вы сообщите своему клиенту, какой метод HTTP использовать?
leifbattermann
2
@ guillaume31 DELETEмне кажется странным, потому что после выключения виртуальная машина все еще будет существовать, только в состоянии «выключить» (или что-то в этом роде).
leifbattermann
1
Ресурс не обязательно должен отражать виртуальную машину VM, он может представлять собой экземпляр ее исполнения.
guillaume31
6

Завершение работы виртуальной машины через интерфейс REST

На самом деле это довольно известный пример, выдвинутый Тимом Бреем в 2009 году .

Рой Филдинг, обсуждая проблему, поделился этим наблюдением :

Я лично предпочитаю системы, которые рассматривают отслеживаемое состояние (например, состояние питания) как не редактируемые.

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

Сет Лэдд имел ключевое понимание проблемы

Мы превратили «Бег» из простого состояния человека в настоящее Существительное, которое можно создавать, обновлять и обсуждать.

Вернем это к перезагрузке машины. Я бы сказал, что вы должны выполнить POST для / vdc / 434 / cluster / 4894 / server / 4343 / перезагрузок. После публикации у вас есть URI, представляющий эту перезагрузку, и вы можете ПОЛУЧИТЬ его для обновления статуса. С помощью магии гиперссылок представление перезагрузки связано с перезагруженным сервером.

Я думаю, что чеканка пространства URI - это дешево, а URI - даже дешевле. Создайте коллекцию действий, смоделированных как Существительные, и POST, PUT и DELETE!

RESTful-программирование - это бюрократия Вогона в веб-масштабе. Как вы делаете что-нибудь RESTful? Придумайте новые документы для этого и оцифруйте документы.

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

Глядя на свои примеры

PATCH /api/virtualmachines/42
Content-Type:application/json  

{ "state": "shutting down" }

Ничего страшного; вы на самом деле не рассматриваете сам запрос как отдельный отдельный информационный ресурс, но вы все равно можете управлять им.

Вы немного пропустили свое представление об изменениях.

Однако с помощью PATCH вложенный объект содержит набор инструкций, описывающих, как ресурс, находящийся в данный момент на исходном сервере, должен быть модифицирован для создания новой версии.

Например, инструкции JSON Patch форматируют инструкции, как если бы вы непосредственно изменяли документ JSON

[
    { "op": "replace", "path": "state", "value": "shutting down" }
]

В вашей альтернативе идея близка, но не совсем верна. PUTявляется полной заменой состояния ресурса по целевому URL , поэтому вы, вероятно, не выберете орфографию, которая выглядит как коллекция, как цель представления одного объекта.

POST /api/virtualmachines/42/actions

Соответствует выдумке, что мы добавляем действие в очередь

PUT /api/virtualmachines/42/latestAction

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

Обратите внимание, что, поскольку мы обсуждаем написание URI - REST не волнует; /cc719e3a-c772-48ee-b0e6-09b4e7abbf8bявляется совершенно громоздким URI для REST. Читаемость, как и с именами переменных, является отдельной проблемой. Использование написания, соответствующего RFC 3986 , сделает людей намного счастливее.

CQRS

Что если у нас есть домен CQRS со многими такими «действиями» (или командами), которые потенциально могут привести к обновлениям нескольких агрегатов или не могут быть сопоставлены с операциями CRUD на конкретных ресурсах и подресурсах?

Грег Янг о CQRS

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

Когда большинство людей говорят о CQRS, они на самом деле говорят о применении шаблона CQRS к объекту, который представляет сервисную границу приложения.

Учитывая, что вы говорите о CQRS в контексте HTTP / REST, кажется разумным предположить, что вы работаете в этом последнем контексте, так что давайте продолжим.

Этот, на удивление, даже проще, чем ваш предыдущий пример. Причина этого проста: команды - это сообщения .

Джим Уэббер описывает HTTP как протокол приложений офиса 1950-х годов; работа выполняется путем приема сообщений и помещения их в почтовые ящики. Та же идея верна - мы получаем чистую копию формы, заполняем ее известной нам спецификой, доставляем ее. Та да

Должны ли мы пытаться моделировать столько команд, сколько конкретных создает или обновляет конкретные ресурсы, где это возможно (следуя первому подходу из примера I), и использовать «конечные точки действия» для остальных?

Да, поскольку «конкретные ресурсы» - это сообщения, а не сущности в доменной модели.

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

Модель CQRS лучше подходит для API, подобного RPC?

Не совсем - в частности, веб-кеши являются отличным примером «в конечном итоге согласованной модели чтения». Делая каждое из ваших представлений независимо адресуемыми, каждое со своими правилами кэширования, вы получаете бесплатное масштабирование. Относительно малообращенный подход к чтению RPC относительно невелик.

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

Преимущество обработки сообщения как его собственного уникального ресурса заключается в том, что вы можете использовать PUT, предупреждая промежуточные компоненты о том, что обработка сообщения идемпотентна, так что они могут участвовать в определенных случаях обработки ошибок. , (Обратите внимание: с точки зрения клиентов, если ресурсы имеют разные URI, то они являются разными ресурсами; тот факт, что все они могут иметь одинаковый код обработчика запросов на исходном сервере, является подробностью реализации, скрытой униформой интерфейс).

Филдинг (2008)

Я должен также отметить, что вышеупомянутое еще не полностью RESTful, по крайней мере, как я использую термин. Все, что я сделал, это описал сервисные интерфейсы, которые не более чем любой RPC. Чтобы сделать его RESTful, мне нужно добавить гипертекст, чтобы представить и определить сервис, описать, как выполнить отображение с использованием форм и / или шаблонов ссылок, и предоставить код для объединения визуализаций полезными способами.

VoiceOfUnreason
источник
2

Вы можете использовать 5 уровней типа мультимедиа, чтобы указать команду в поле заголовка типа содержимого запроса.

В примере с VM это будет что-то вроде этого

PUT /api/virtualmachines/42
Content-Type:application/json;domain-model=PowerOnVm

> HTTP/1.1 201 Created
Location: /api/virtualmachines/42/instance

затем

DELETE /api/virtualmachines/42/instance
Content-Type:application/json;domain-model=ShutDownVm

Или

DELETE /api/virtualmachines/42/instance
Content-Type:application/json;domain-model=PowerOffVm

Смотрите https://www.infoq.com/articles/rest-api-on-cqrs

guillaume31
источник
Имейте в виду, 5LMT был предложенным решением и не поддерживается стандартами . Я уже сталкивался со статьей CQRS и многому научился.
Питер Л
1

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

Есть два способа управления состоянием питания компьютера на моем столе:

  • Выключатель питания: немедленно прекращает подачу электричества к источнику питания, внезапно и беспорядочно останавливая весь компьютер.
  • Кнопка включения / выключения: Сообщает аппаратному обеспечению уведомлять программное обеспечение о том, что что-то извне хочет, чтобы все было отключено. Программное обеспечение выполняет упорядоченное завершение работы, уведомляет аппаратное обеспечение о том, что оно выполнено, и аппаратное обеспечение сигнализирует источнику питания, что оно может перейти в режим ожидания. Если выключатель питания включен, аппарат работает, а программное обеспечение находится в состоянии, когда оно не может реагировать на сигнал выключения, система не выключится, пока я не выключу выключатель питания. (Виртуальная машина будет вести себя точно так же; если программное обеспечение игнорирует сигнал выключения, машина продолжит работать, я должен принудительно отключить ее.) Если я хочу иметь возможность перезагрузить машину, я должен снова включите выключатель питания, а затем нажмите кнопку включения / выключения. (Многие компьютеры имеют возможность использовать длительное нажатие кнопки питания, чтобы перейти непосредственно в состояние ожидания, но эта модель на самом деле не нуждается в этом.) Эта кнопка может рассматриваться как тумблер, потому что нажатие на нее приводит к различному поведению в зависимости от состояния, когда она нажата. Если выключатель питания выключен, нажатие этой кнопки абсолютно ничего не делает.

Для виртуальной машины оба они могут быть смоделированы как логические значения для чтения / записи:

  • power- При изменении на trueничего не происходит, кроме того, что делается пометка о том, что переключатель переведен в это состояние. При переключении falseна ВМ вводится в состояние немедленного отключения. Для полноты, если значение не изменилось после записи, ничего не происходит.

  • onoff- При изменении на trueничего не происходит, если powerесть false, в противном случае виртуальная машина получает команду на запуск. При изменении на falseничего не происходит, если powerесть false, в противном случае виртуальная машина получает команду уведомить программное обеспечение о необходимости упорядоченного завершения работы, а затем уведомить виртуальную машину о том, что она может перейти в состояние отключения питания. Опять же, для полноты, запись без изменений ничего не делает.

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

  • running- Значение только для чтения, trueкогда виртуальная машина работает, а falseкогда нет, определяется путем запроса гипервизора о ее состоянии.

Результатом этого является то, что если вы хотите, чтобы виртуальная машина запускалась, вы должны убедиться, что powerи onoffресурсы установлены true. (Вы можете разрешить powerпропуск шага, сделав его самовозвратом, так что если он установлен в значение false, то он становится trueпосле того, как виртуальная машина была проверенно проверена. Если это RESTly-чистая вещь, которую нужно сделать, это корм для другого обсуждения.) Если вы хотите, чтобы это сделать нормальное завершение работы, вы установили onoffв falseи вернуться позже , чтобы увидеть , если машина фактически остановилась, установка powerдля falseесли это не так.

Как и в реальном мире, у вас все еще есть проблема с указанием запустить виртуальную машину после того, как она была onoffизменена, falseно все еще runningпотому, что она находится в середине выключения. То, как вы справляетесь с этим, является политическим решением.

Blrfl
источник
0

Оба дизайна RESTful?

Так что если вы хотите думать спокойно, забудьте о командах. Клиент не говорит серверу выключить виртуальную машину. Клиент «закрывает» (в переносном смысле) свою копию представления ресурса, обновляя его состояние, а затем помещает это представление обратно на сервер. Сервер принимает это новое представление состояния и, в качестве побочного эффекта, фактически выключает виртуальную машину. Побочный эффект оставлен на усмотрение сервера.

Это меньше

Эй, сервер, клиент здесь, не могли бы вы закрыть виртуальную машину

и многое другое

Эй, сервер, клиент здесь, я обновил состояние виртуальной машины 42 до состояния выключения, обновил вашу копию этого ресурса и затем сделал то, что вы считаете подходящим

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

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

Сила этого в том, что он отделяет клиента от сервера с точки зрения управления потоком. Если позже сервер изменит свою работу с виртуальными машинами, клиенту все равно. Нет конечных точек команд для обновления. В RPC, если вы измените конечную точку API с shutdownна, shut-downвы сломали все свои клиенты, так как они теперь не знают команду для вызова на сервере.

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

Кормак Мулхолл
источник
Спасибо за ваш ответ. Вторая часть о разъединении клиента и сервера очень согласуется с моим собственным пониманием. Есть ли у вас ресурс / ссылка, которая поддерживает первую часть вашего ответа? Какое ограничение REST нарушается, если я использую ресурсы, методы HTTP, гипермедиа, сообщения с самоописанием и т. Д.?
leifbattermann
Нет проблем с использованием ресурсов, методов HTTP и т. Д. HTTP - это протокол RESTful. Там, где возникает проблема, используется то, что вы называете «конечными точками действия». В REST есть ресурсы, которые представляют понятия или вещи (такие как виртуальная машина 42 или мой банковский счет), и HTTP-глаголы используют для передачи состояния этих ресурсов между клиентами и серверами. Вот и все. Чего не следует делать, так это пытаться создавать новые команды, комбинируя нересурсные конечные точки с глаголами HTTP. Таким образом, virtualmachines / 42 / actions не является ресурсом и не должен существовать в системе RESTful.
Кормак
Или, другими словами, клиент не должен пытаться запускать команды на сервере (кроме ограниченных глаголов HTTP, связанных исключительно с передачей состояния ресурсов). Клиент должен обновить свою копию ресурса, а затем просто попросить сервер принять это новое состояние. Принятие этого нового состояния может иметь побочные эффекты (виртуальная машина 42 физически отключена), но это не касается клиента. Если клиент не пытается запускать команды на сервере, то между этими командами нет связи между клиентом и сервером.
Кормак Малхолл
Вы можете запустить команду на ресурсе ... Как бы вы сказали, скажем, «пополнить счет» и «снять» с банковского счета? Было бы использовать CRUD для чего-то, что не CRUD.
Конрад
Лучше использовать POST /api/virtualmachines/42/shutdownвместо того, чтобы иметь какой-либо "побочный эффект". API должен быть понятен пользователю. Как я узнаю, что, например DELETE /api/virtualmachines/42, виртуальная машина отключится? Побочным эффектом для меня является ошибка, мы должны спроектировать наши API так, чтобы они были понятными и информативными
Конрад,