Как лучше всего вы представляете двунаправленную синхронизацию в REST API?

23

Предполагая систему, в которой есть веб-приложение с ресурсом, и ссылку на удаленное приложение с другим подобным ресурсом, как вы представляете двунаправленное действие синхронизации, которое синхронизирует «локальный» ресурс с «удаленным» ресурсом?

Пример:

У меня есть API, который представляет список задач.

GET / POST / PUT / DELETE / todos / и т. Д.

Этот API может ссылаться на удаленные сервисы TODO.

GET / POST / PUT / DELETE / todo_services / и т. Д.

Я могу манипулировать задачами из удаленного сервиса через мой API как прокси через

GET / POST / PUT / DELETE / todo_services / abc123 / и т. Д.

Мне нужна возможность выполнять двунаправленную синхронизацию между локальным набором задач и удаленным набором TODOS.

В некотором роде, можно сделать

POST / todo_services / abc123 / sync /

Но в идее «глаголы - это плохо», есть ли лучший способ представить это действие?

Эдвард М Смит
источник
4
Я думаю, что хороший дизайн API абсолютно зависит от очень конкретного понимания того, что вы подразумеваете под синхронизацией. «Синхронизация» двух источников данных обычно представляет собой очень сложную проблему, которую очень легко упростить, но очень трудно продумать во всех ее значениях. Сделайте это "двунаправленной" синхронизацией, и внезапно сложность станет намного выше. Начните с обдумывания очень сложных вопросов, которые возникают.
Адам Кроссленд
Справа - предположим, что алгоритм синхронизации спроектирован и функционирует в API «уровня кода» - как мне представить это через REST. Одностороннюю синхронизацию кажется гораздо проще выразить: я GET /todo/1/и POSTэто для /todo_services/abc123/ Но, 2 способа - я не беру набор данных и не помещаю его в ресурс, действие, которое я предпринимаю, фактически приводит к потенциальной модификации двух ресурсов. Я полагаю, я мог бы вернуться к тому, что «синхронизация todo» - это сами ресурсы POST /todo_synchronizations/ {"todos":["/todo/1/","/todo_services/abc123/1"],"schedule":"now"}
Эдвард М Смит
У нас все еще есть проблема телеги перед лошадью. Я хотел сказать, что вы не можете предполагать, что синхронизация работает и разрабатывает API. Разработка API будет зависеть от многочисленных соображений о том, как именно работает алгоритм синхронизации.
Адам Кроссленд
Это потенциально дает полезные результаты: GET /todo_synchronizations/1=>{"todos":["/todo/1/","/todo_services/abc123/1"],"schedule":"now","ran_at":"datetime","result":"success"}
Эдвард М Смит
2
Я согласен с @Adam. Знаете ли вы, как вы собираетесь реализовать свою синхронизацию? Как вы справляетесь с изменениями? У вас просто есть два набора элементов, которые вы хотите согласовать, или у вас есть журнал действий, которые вызвали расхождение этих двух наборов с момента последней синхронизации? Причина, по которой я спрашиваю, состоит в том, что может быть сложно обнаружить добавления и удаления (независимо от REST). Если у вас есть объект на стороне сервера, а у вас нет его на стороне клиента, вы должны спросить себя: «Клиент удалил его или сервер создал?» Только когда вы точно знаете, как ведет себя «ресурс», вы сможете точно представить его в REST.
Рэймонд Солтрелли

Ответы:

17

Где и какие ресурсы?

REST - это все, что касается адресации ресурсов без сохранения состояния и обнаружения. Он не должен быть реализован через HTTP, и при этом он не должен полагаться на JSON или XML, хотя настоятельно рекомендуется использовать формат данных гипермедиа (см. Принцип HATEOAS ), поскольку желательны ссылки и идентификаторы.

Итак, возникает вопрос: как можно думать о синхронизации с точки зрения ресурсов?

Что такое двунаправленная синхронизация? **

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

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

Шаг 1 - Локальный извлекает журнал транзакций

Местный: GET /remotehost/items/transactions?earliest=2000-01-01T12:34:56.789Z

Удаленный: 200 ОК с телом, содержащим журнал транзакций, содержащий поля, подобные этим.

  • itemId - UUID для предоставления общего первичного ключа

  • updatedAt - отметка времени для предоставления координированной точки, когда данные были в последний раз обновлены (при условии, что история изменений не требуется)

  • fingerprint- SHA1-хэш содержимого данных для быстрого сравнения, если updateAtпрошло несколько секунд

  • itemURI - полный URI для элемента, чтобы разрешить поиск позже

Шаг 2 - Local сравнивает удаленный журнал транзакций с его собственным

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

Запросы извлечения выполняются для удаленного узла, так что данные существуют локально с использованием itemURI. Они не применяются локально, пока позже.

Шаг 3. Передайте локальный журнал транзакций синхронизации на удаленный

Local: PUT /remotehost/items/transactions с телом, содержащим локальный журнал транзакций синхронизации.

Удаленный узел может обрабатывать это синхронно (если он маленький и быстрый) или асинхронно (например, 202 ПРИНЯТО ), если он может вызвать большие накладные расходы. Если предположить синхронную операцию, то результатом будет либо 200 OK, либо 409 CONFLICT в зависимости от успеха или неудачи. В случае КОНФЛИКТА 409 , процесс должен быть запущен снова, поскольку на удаленном узле произошел сбой оптимистической блокировки (кто-то изменил данные во время синхронизации). Удаленные обновления обрабатываются в соответствии с собственной транзакцией приложения.

Шаг 4 - Обновление локально

Данные, полученные на шаге 2, применяются локально в рамках транзакции приложения.

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

Гэри Роу
источник
6

Я бы рассматривал операцию синхронизации как ресурс, к которому можно получить доступ (GET) или создать (POST). Учитывая это, URL API может быть:

/todo_services/abc123/synchronization

(Называя это «синхронизация», а не «синхронизация», чтобы было понятно, что это не глагол)

Затем сделайте:

POST /todo_services/abc123/synchronization

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

GET /todo_services/abc123/synchronization?id=12345
Laurent
источник
3
Этот простой ответ является ответом. Преврати свои глаголы в существительные и продолжай ...
HDave
5

Это сложная проблема. Я не верю, что REST является подходящим уровнем для реализации синхронизации. Надежная синхронизация по сути должна быть распределенной транзакцией. ОТДЫХ не инструмент для этой работы.

(Предположение: под «синхронизацией» вы подразумеваете, что любой ресурс может изменяться независимо от другого в любое время, и вы хотите иметь возможность перераспределять их без потери обновлений.)

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

Вы также можете рассмотреть Microsoft Sync Framework, если вам абсолютно необходимо поддерживать независимо меняющиеся хранилища данных. Это не будет работать через REST, но негласно.

codingoutloud
источник
5
+1 за "трудную проблему". Двунаправленная синхронизация - это одна из тех вещей, которую вы не понимаете, насколько это сложно, пока вы не погрузитесь в грязь.
Дэн Рэй
2

Apache CouchDB - это база данных, основанная на REST, HTTP и JSON. Разработчики выполняют основные операции CRUD по HTTP. Он также предоставляет механизм репликации, который является одноранговым с использованием только HTTP-методов.

Чтобы обеспечить эту репликацию, CouchDB должен иметь некоторые соглашения, специфичные для CouchDB. Ни один из них не против ОТДЫХА. Он предоставляет каждому документу (то есть ресурсу REST в базе данных) номер редакции . Это часть представления этого документа в формате JSON, но также и в заголовке HTTP ETag. Каждая база данных также имеет порядковый номер, который позволяет отслеживать изменения в базе данных в целом.

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

Вы можете использовать CouchDB в качестве REST API, который обеспечит вам синхронизацию «из коробки», или взглянуть на то, как он обеспечивает репликацию, чтобы обеспечить отправную точку для создания собственного алгоритма.

Дэвид V
источник
Я люблю CouchDB, и это наследник CouchBase + SyncGateway. +1
Леонид Усов
-1

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

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

Том Кларксон
источник