Event Sourcing и REST

17

Я сталкивался с дизайном Event Sourcing и хотел бы использовать его в приложении, где требуется клиент REST (точнее, RESTful). Однако мне не удается соединить их вместе, поскольку REST очень похож на CRUD, а источник событий основан на задачах. Мне было интересно, как вы можете создавать команды на основе запросов к REST-серверу. Рассмотрим этот пример:

С помощью REST вы можете поместить новое состояние в ресурс под названием File. В одном запросе вы можете отправить новое имя файла, вы можете изменить родительскую папку и / или изменить владельца файла и так далее.

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

  1. Определить на сервере , какие поля были изменены , и создать соответствующие команды ( RenameFileCommand, MoveFileCommand, ChangeOwnerCommand...) и отправка их по отдельности. Однако в этой настройке каждая команда может потерпеть неудачу, оставив других вне транзакции и, таким образом, из «атомарного» изменения ресурса.

  2. Отправка только одна команда ( UpdateFileCommand) и в обработчике команды, точнее , в совокупности, определяют , какие поля были изменены и передавать отдельные события вместо ( FileRenamedEvent, FileMovedEvent, OwnerChangedEvent...)

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

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

Примечание: приложение, написанное на Java и Axon Framework для событийного поиска.

рыжеволосый
источник
Конечно, не 3-й. Я бы сделал 1-й, но я должен подумать об этом.
inf3rno
Вы задаетесь вопросом о том, может ли один запрос HTTP привести к нескольким командам? Я хорошо понимаю? По моему мнению. это должно быть в состоянии сделать это, но я некоторое время не читал об DDD, поэтому я должен проверить пример кода о том, как реализовать это.
inf3rno
Я нашел пример, но это CRUD. github.com/szjani/predaddy-issuetracker-sample/blob/3.0/src/hu/… Я спрошу автора, каково его мнение, он знает больше о DDD, чем я.
inf3rno
1
Мое то, что вы должны использовать несколько команд в «единицу работы», если вы хотите немедленной согласованности. Если вы говорите о возможной последовательности, тогда вопрос не имеет смысла для меня. Еще одно возможное решение - отправить CompositeCommand, которая может содержать команды, которые вы хотите выполнить атомарно. Это может быть простая коллекция, важно только то, что автобус справится с этим правильно.
inf3rno
1
По его словам, вы должны попытаться достичь отношения 1: 1 между командами и HTTP-запросами. Если вы не можете сделать это (это неприятный запах), тогда вы должны использовать объем (я назвал это составным), чтобы сделать его атомарным.
inf3rno

Ответы:

11

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

Во-первых: хочет ли пользователь честно выполнять несколько изменений в файле одновременно? Переименование (которое может включать или не включать изменение пути?), Изменение владельца и, возможно, изменение содержимого файла (ради аргумента) кажутся отдельными действиями.

Давайте возьмем случай, когда ответ «да» - ваши пользователи действительно хотят внести эти изменения одновременно.

В этом случае, я настоятельно рекомендую против любой реализации , которая посылает несколько событий - RenameFileCommand, MoveFileCommand, ChangeOwnerCommand- для представления этого одного намерения пользователя.

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

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

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

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

Интересно, что в REST наблюдается движение к чему-то похожему на событийную архитектуру, которая называется «Отдых без PUT», рекомендованной в технологическом радаре Thoughtworks в январе 2015 года . Здесь есть гораздо более длинный блог об отдыхе без PUT .

По сути, идея в том, что POST, PUT, DELETE и GET хороши для небольших приложений, но когда вам нужно начать предполагать, как put, post и delete могут интерпретироваться на другом конце, вы вводите связывание. (например, «когда Я УДАЛЯЮ ресурс, связанный с моим банковским счетом, этот счет должен быть закрыт»). И предлагаемое решение состоит в том, чтобы рассматривать REST более подробно, используя источник событий. т.е. позволяет POST пользовательское намерение как отдельный ресурс события.

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

Что касается других приложений, которые используют совсем другой API для ваших ресурсов. Это пахнет неприятностями. Можете ли вы построить фасад старого API поверх вашего RESTful API и указать их на фасаде? т.е. предоставить сервис, который выполняет несколько обновлений файла последовательно через сервер REST?

Если вы не создадите интерфейс RESTful поверх старого решения, не построите фасад старого интерфейса поверх решения REST и не попытаетесь поддерживать оба API-интерфейса, указывающие на общий ресурс данных, у вас возникнут серьезные головные боли.

перфекционист
источник
Я могу видеть несоответствие, однако, с помощью REST в принципе можно обновить состояние нескольких полей, помещая новое состояние в ресурс (по некоторому представлению). Это то, как REST работает по определению - передача состояния представления, недостатком является то, что намерение пользователя теряется в процессе. Кроме того, REST является почти стандартом для внешнего API. Я просто хочу разработать приложение, чтобы я мог использовать оба. Удаление PUT похоже на обходной путь из-за ES. Из того, что я вижу, можно выполнить одну команду обновления, генерирующую несколько событий, если обработка команды и события выполняется в одной транзакции.
рыжий
Etag это то, что я хотел бы сделать в любом случае. Также ваша ссылка на «более длинную запись в блоге» такая же, как и первая ссылка.
рыжий
Я вернусь к этому после некоторого рассмотрения с большим количеством мыслей о PUT. Конечно, удаление PUT - это не просто «обходной путь для ES». Я исправил ссылку на блог.
перфекционист
4

Только что я наткнулся на следующую статью, в которой рекомендуется указывать имена команд в запросе к серверу в заголовке Content-Type (следуя 5 уровням типа носителя).

В этой статье они упоминают, что RPC-стиль плох для REST, и предлагают расширить Content-Type для указания имени команды:

Один из распространенных подходов - использование ресурсов в стиле RPC, например / api / InventoryItem / {id} / rename. Хотя это, по-видимому, устраняет необходимость в произвольных глаголах, это противоречит ресурс-ориентированному представлению REST. Нам нужно напомнить, что ресурс - это существительное, а HTTP-глагол - это глагол / действие, а самоописательные сообщения (один из принципов REST) ​​являются средством передачи других осей информации и намерений. На самом деле команды в полезной нагрузке HTTP-сообщения должно быть достаточно, чтобы выразить любое произвольное действие. Однако полагаться на тело сообщения имеет свои собственные проблемы, так как тело обычно доставляется в виде потока, и буферизация тела целиком до определения действия не всегда возможна и не является разумной.

PUT /api/InventoryItem/4454c398-2fbb-4215-b986-fb7b54b62ac5 HTTP/1.1
Accept:application/json, text/plain, */*
Accept-Encoding:gzip,deflate,sdch
Content-Type:application/json;domain-model=RenameInventoryItemCommand`

Статья находится здесь: http://www.infoq.com/articles/rest-api-on-cqrs

Вы можете прочитать больше о 5 уровнях медиа типа здесь: http://byterot.blogspot.co.uk/2012/12/5-levels-of-media-type-rest-csds.html


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

рыжеволосый
источник