Как объединить websockets и http для создания REST API, который обновляет данные?

Я подумываю о создании REST API как с websockets, так и с http, где я использую websockets, чтобы сообщить клиенту, что новые данные доступны или напрямую предоставить новые данные клиенту.

Вот несколько разных идей, как это могло бы работать:
ws = websocket

Идея A:

  • Дэвид получает всех пользователей с помощью GET /users
  • Jacob добавляет пользователя с POST /users
  • Сообщение ws отправляется всем клиентам с информацией о том, что существует новый пользователь.
  • Дэвид получает сообщение от ws и вызывает GET /users

Идея B:

  • Дэвид получает всех пользователей с помощью GET /users
  • Дэвид регистрируется, чтобы получить обновления ws, когда изменение сделано для /users
  • Jacob добавляет пользователя с POST /users
  • Новый пользователь отправляется в David by ws

Идея C:

  • Дэвид получает всех пользователей с помощью GET /users
  • Дэвид регистрируется, чтобы получить обновления ws, когда изменение сделано для /users
  • Jacob добавляет пользователя с POST /users и получает id 4
  • Дэвид получает id 4 нового пользователя по ws
  • Дэвид получит нового пользователя с GET /users/4

Идея D:

  • Дэвид получает всех пользователей с помощью GET /users
  • Дэвид регистрируется для получения обновлений ws, когда изменения выполняются с /users.
  • Jacob добавляет пользователя с POST /users
  • Дэвид получает сообщение ws о том, что изменения выполняются с помощью /users
  • Дэвид получает только дельту, вызывая GET /users?lastcall='time of step one'

Какая альтернатива является лучшей и какие плюсы и минусы?
Это еще лучше "Идея Е"?
Нужно ли нам использовать REST или достаточно для всех данных?

Изменить
Чтобы решить проблемы с получением данных из синхронизации, мы могли бы предоставить заголовок
" If-Unmodified-Since "
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Unmodified-Since
или" E-Tag "
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag
или оба с запросами PUT.

Ответ 1

Идея B для меня самая лучшая, потому что клиент специально подписывается на изменения в ресурсе и получает инкрементные обновления с этого момента.

Нужно ли нам использовать REST или достаточно для всех данных?

Пожалуйста, проверьте: WebSocket/REST: клиентские соединения?

Ответ 2

Я не знаю Java, но я работал с Ruby и C на этих проектах...

Забавно, я считаю, что самым простым решением является использование JSON, где API REST просто добавляет данные method (т.е. method: "POST") в JSON и перенаправляет запрос тому же обработчику, который использует Websocket.

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

Это помогает инкапсулировать код и сохранять его DRY во время доступа к одному и тому же API, используя как REST, так и Websockets.

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

Удачи!

P.S. (Pub/Sub)

Что касается Pub/Sub, я считаю, что лучше всего иметь "крючок" для любых вызовов API API (обратный вызов) и отдельный модуль Pub/Sub, который обрабатывает эти вещи.

Я также считаю более удобным для записи всего данных в службу Pub/Sub (вариант B) вместо простого ссылочного номера (опция C) или сообщение "доступно обновление" (варианты A и D).

В целом, я также считаю, что отправка всего списка пользователей неэффективна для более крупных систем. Если у вас нет 10-15 пользователей, вызов базы данных может быть перебором. Рассмотрите администратор Amazon, призывающий список всех пользователей... Brrr....

Вместо этого я бы разделил это на страницы, скажем, 10-50 пользователей на странице. Эти таблицы могут быть заполнены с использованием нескольких запросов (Websocket/REST, не имеет значения) и легко обновляются с использованием живых сообщений Pub/Sub или перезагружаются, если соединение было потеряно и восстановлено.

ИЗМЕНИТЬ (REST vs. Websockets)

Как для REST vs. Websockets... Я нахожу, что вопрос о необходимости - это в основном подмножество вопроса "кто клиент?"...

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

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

По той же причине (как и другие), Websockets обычно имеют преимущество в отношении производительности... насколько большой край REST зависит от транспортного уровня REST (HTTP/1.1, HTTP/2 и т.д.).

Обычно эти вещи незначительны, когда приходит время предлагать публичную точку доступа API, и я считаю, что реализация обоих вариантов, вероятно, теперь и остается.

Ответ 3

Другой вариант - использовать Firebase Cloud Messaging:

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

Как это работает?

Реализация FCM включает два основных компонента для отправки и прием:

  • Доверенная среда, такая как облачные функции для Firebase или сервер приложений, на которых можно создавать, настраивать и отправлять сообщения.
  • Клиентское приложение iOS, Android или Web (JavaScript), которое получает сообщения.

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

Ответ 4

Как правило, вы можете взглянуть на текущие веб-фреймворки реального времени, такие как MeteorJS, которые решают именно эту проблему.

Meteor в конкретных работах более или менее похож на ваш пример D с подписками на определенные данные и дельтами, которые отправляются после изменений только для затронутых клиентов. Их используемый протокол называется DDP, который дополнительно отправляет дельтам не как накладные HTML, а на необработанные данные.

Если веб-узлы недоступны, можно использовать резервные копии, например длительный опрос или события, отправленные сервером.

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

Ответ 5

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

Серверная сторона:

socket.on('getUsers', () => {
    // Get users from db or data model (save as user_list).
    socket.emit('users', user_list );
})
socket.on('createUser', (user_info) => {
    // Create user in db or data model (save created user as user_data).
    io.sockets.emit('newUser', user_data);
})

Клиентская сторона:

socket.on('newUser', () => {
    // Get users from db or data model (save as user_list).
    socket.emit('getUsers');
})
socket.on('users', (users) => {       
    // Do something with users
})

Это использует socket.io для node. Я не уверен, каков ваш точный сценарий, но это будет работать в этом случае. Если вам нужно включить конечные точки REST, которые тоже будут хороши.

Ответ 6

Подводя итог вашим идеям:

A: отправка сообщения всем клиентам, когда пользователь редактирует данные на сервере. Затем все пользователи запрашивают обновление всех данных.
. Эта система может делать много ненужных вызовов сервера от имени клиентов, которые не используют данные. Я не рекомендую производить весь этот дополнительный трафик, поскольку обработка и отправка этих обновлений могут стать дорогостоящими.

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

C: Пользователи, подписавшиеся на обновления данных, отправляют информацию о том, какие данные были обновлены, а затем забирают ее снова. . Это худшее из A и B, поскольку у вас будут дополнительные круглые поездки между вашими пользователями и серверами, чтобы уведомить их о том, что им нужно сделать запрос на информацию, которая может быть не синхронизирована.

D: Пользователи, подписавшиеся на обновления, уведомляются о любых изменениях, а затем запрашивают последнее изменение, внесенное на сервер.
-Это представляет все проблемы с C, но включает в себя возможность того, что после синхронизации вы можете отправлять данные, которые будут бессмысленны для ваших пользователей, что может просто привести к сбою клиентского приложения для всего, что мы знаем.я >

Я думаю, что этот вариант E был бы лучше:
Каждый раз, когда данные изменяются на сервере, отправляйте содержимое всех данных клиентам, которые подписались на него. Это ограничивает трафик между вашими пользователями и сервером, а также дает им меньше шансов получить данные синхронизации. Они могут получать устаревшие данные, если их соединение падает, но по крайней мере вы не отправляли бы их что-то вроде Delete entry 4, когда вы не уверены, получили ли они сообщение, что запись 5 просто переместилась в слот 4.

Некоторые соображения:

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

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

Ответ 7

Я лично использовал Idea B в производстве и очень доволен результатами. Мы используем http://www.axonframework.org/, поэтому каждое изменение или создание объекта публикуется как событие во всем приложении. Эти события затем используются для обновления нескольких моделей чтения, которые в основном являются простыми таблицами Mysql, поддерживающими один или несколько запросов. Я добавил некоторые перехватчики процессоров событий, которые обновляют эти модели чтения, чтобы они публиковали события, которые они только что обработали, после того, как данные были переданы в БД.

Публикация событий осуществляется через STOMP через веб-сокеты. Это сделано очень просто, вы используете поддержку Spring Web Socket (https://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html). Вот как я это написал:

@Override
protected void dispatch(Object serializedEvent, String topic, Class eventClass) {
    Map<String, Object> headers = new HashMap<>();
    headers.put("eventType", eventClass.getName());
    messagingTemplate.convertAndSend("/topic" + topic, serializedEvent, headers);
}

Я написал небольшой конфигуратор, который использует Springs bean factory API, чтобы я мог аннотировать мои обработчики событий Axon следующим образом:

@PublishToTopics({
    @PublishToTopic(value = "/salary-table/{agreementId}/{salaryTableId}", eventClass = SalaryTableChanged.class),
    @PublishToTopic(
            value = "/salary-table-replacement/{agreementId}/{activatedTable}/{deactivatedTable}",
            eventClass = ActiveSalaryTableReplaced.class
    )
})

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

var connectedClient = $.Deferred();

function initialize() {
    var basePath = ApplicationContext.cataDirectBaseUrl().replace(/^https/, 'wss');
    var accessToken = ApplicationContext.accessToken();
    var socket = new WebSocket(basePath + '/wss/query-events?access_token=' + accessToken);
    var stompClient = Stomp.over(socket);

    stompClient.connect({}, function () {
        connectedClient.resolve(stompClient);
    });
}


this.subscribe = function (topic, callBack) {
    connectedClient.then(function (stompClient) {
        stompClient.subscribe('/topic' + topic, function (frame) {
            callBack(frame.headers.eventType, JSON.parse(frame.body));
        });
    });
};

initialize();

Ответ 8

Со всей замечательной информацией все великие люди добавили передо мной.

Я обнаружил, что в конечном итоге нет правильного или неправильного, его просто сводится к тому, что подходит вашим потребностям:

позволяет взять CRUD в этом сценарии:

Только для WS:

Create/Read/Update/Deleted information goes all through the websocket.    
--> e.g If you have critical performance considerations ,that is not 
acceptable that the web client will do successive REST request to fetch 
information,or if you know that you want the whole data to be seen in 
the client no matter what was the event ,  so just send the CRUD events 
AND DATA inside the websocket.

WS ДЛЯ ОТПРАВКИ ИНФОРМАЦИИ О СОБЫТИИ + ОТДЫХ НА ПОТРЕБЛЕНИЕ ДАННЫХ СЕБЯ

Create/Read/Update/Deleted , Event information is sent in the Websocket,
giving the web client information that is necessary to send the proper 
REST request to fetch exactly the thing the CRUD that happend in server.

например. WS отправляет UsersListChangedEvent { "ListChangedTrigger:" ItemModified "," IdOfItem ":" XXXX # 3232 "," UserExtrainformation ":" Достаточно информации, чтобы клиент решил, имеет ли значение для него выбор из измененных данных "}

Я обнаружил, что использование WS [Только для использования данных событий] и REST [Использовать данные] лучше, потому что:

[1] Разделение между моделью чтения и записи. Представьте, что вы хотите добавить некоторую информацию о времени выполнения, когда ваши данные извлекаются при ее чтении из REST, что теперь достигнуто, потому что вы не смешиваете модели Write и Read, такие как 1.

[2] Допустим, что другая платформа, не обязательно веб-клиент, будет потреблять эти данные.   поэтому вы просто изменяете триггер Event от WS до нового способа и используете REST для   потребляют данные.

[3] Клиенту не нужно писать два способа чтения новых/измененных данных.   обычно есть код, который считывает данные при загрузке страницы, а не в   через websocket этот код теперь можно использовать дважды, один раз, когда страница   и второй, когда WS вызвало конкретное событие.

[4] Возможно, клиент не хочет извлекать нового пользователя, потому что показывается его только просмотр старых данных [например. пользователей], и новые изменения данных не в его интересах для извлечения?

Ответ 9

Чем больше я читал об этом вопросе, тем больше я думаю, что правильная вещь - реализовать реализацию REST через соединение с веб-соединениями. Резервное решение, в котором вы поддерживаете HTTP, также может быть решением.

https://hackernoon.com/rest-over-websockets-instead-of-http-81b0f0632295

Ответ 10

Я предпочитаю A, он позволяет клиенту гибко или не обновлять существующие данные.

также с этим методом, реализация и контроль доступа становятся намного проще.

например, вы можете просто транслировать событие userUpdated всем пользователям, это сохраняет список клиентов для конкретных передач, а Access Controls and Authentications, примененный для вашего маршрута REST, не придется менять снова на повторное использование, потому что клиент собирается повторите запрос GET.

Многие вещи зависят от того, какое приложение вы делаете.