Оптимистичная блокировка на стороне сервера в приложении ReSTful: обработка асинхронных запросов от одного и того же клиента - PullRequest
1 голос
/ 17 августа 2010

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

Само приложение разработано с учетом ReSTful принципов. Он использует Джерси для предоставления доступа к ресурсам и jQuery для представления этих ресурсов пользователю и обеспечения базовых взаимодействий (создание, обновление, удаление). Довольно просто.

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

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

Моя первая проблема состояла в том, как обновить управляемый объект из базы данных с помощью неуправляемого объекта, поступающего от клиента. Поскольку в данных, отправляемых клиенту, намеренно не указываются ключи базы данных, это было немного утомительно, поскольку сводилось к явному «слиянию» элементов из неуправляемого объекта в объект Hibernate. Это не мой вопрос, но если кто-нибудь знает элегантный способ сделать это, мне было бы очень интересно узнать об этом.

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

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

Самый простой подход, который я могу придумать для решения этой проблемы, состоит в том, чтобы переработать часть клиентского Javascript, чтобы гарантировать, что в любой момент времени от этого клиента будет только одна ожидающая операция «обновления» от этого клиента. (Обратите внимание, что если клиент другой обновляет один и тот же ресурс в одно и то же время, исключение для оптимистической блокировки вполне подойдет.) Однако я обеспокоен тем, что принудительное ограничение этого клиента может отклоняться от дух рест. Разумно ли ожидать, что данный клиент будет иметь не более одного невыполненного запроса «обновления» (PUT) к конкретному ресурсу в любой момент времени в приложении ReSTful?

Это кажется довольно распространенным сценарием, но я не смог найти однозначного ответа о том, как лучше всего с ним справиться. Другие идеи, которые я рассмотрел и отбросил, включают в себя как-то сериализацию запросов от одного и того же клиента (возможно, на основе сеанса HTTP), чтобы они обрабатывались по порядку, реализацию «очереди» обновлений для рабочего потока JPA / Hibernate и вставку новых ». версий »ресурса, отслеживая последнюю версию, а не обновляя какую-либо отдельную запись.

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

Ответы [ 2 ]

0 голосов
/ 17 августа 2010

Я столкнулся с теми же проблемами клиентов, выполняющих одновременные операции PUT для одного и того же URI одновременно. Это не имеет смысла для клиента, но я не думаю, что вы найдете какую-либо жесткую документацию, в которой говорится, что это запрещено, или в какой-то степени RESTful. Действительно, сервер не может ничего делать, кроме как сериализовать их и жаловаться на оптимистичный параллелизм, когда это происходит. Хорошо ведущий себя клиент должен избегать этого, синхронизируя все операции на каждом ресурсе, даже безопасные операции, такие как GET или HEAD, чтобы не загрязнять кэш устаревшими данными.

0 голосов
/ 17 августа 2010

Моя первая проблема состояла в том, как обновить управляемый объект из базы данных с помощью неуправляемого объекта, поступающего от клиента.Поскольку в данных, отправляемых клиенту, намеренно не указываются ключи базы данных, это было немного утомительно, поскольку сводилось к явному «слиянию» элементов из неуправляемого объекта в объект Hibernate.Это не мой вопрос, но если кто-нибудь знает об элегантном способе сделать это, мне было бы очень интересно услышать об этом.

Либо вы должны включить идентификатор в ресурс клиентаотправляет или использует PUT (например, PUT /object/{objectId}).Тогда вам не нужно объединять, а просто «заменить».Я предпочитаю всегда возвращаться и ожидать полных ресурсов.Это позволяет избежать слияния.

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

Как вы упоминали, вы можете сделать это на стороне клиента с помощью JavaScript.Вы можете ввести флаг «грязный», и и кнопка автосохранения, и кнопка сохранения-закрытия передают запрос на сервер только тогда, когда установлен флаг «грязный».Флаг 'dirty' переключается, когда пользователь что-то изменил.Я бы не позволил серверу сериализовать запросы, это усложняет ситуацию.

[...] Однако я обеспокоен тем, что навязывание этого ограничения клиенту может отклоняться от духа ReST.Разумно ли ожидать, что данный клиент будет иметь не более одного невыполненного запроса «обновления» (PUT) к конкретному ресурсу в любой момент времени в приложении ReSTful?

В некотором смыслеREST должен отделить вещи, и клиент может решить, когда выполнять CRUD-операции.

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

...