2010-08-17 1 views
1

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

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

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

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

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

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

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

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

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

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

ответ

0

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

+0

Вы выигрываете по умолчанию, mogsie. Я предполагаю, что это способ использования приложений ReST - на самом деле это не спецификация, выходящая за рамки RFC 2616. Если поведение явно не определено для HTTP 1.1, то это все догадывается, что такое «правильный», способ сделать что-то. Как вы полагаете, для клиента, вероятно, просто «здравый смысл» избежать отправки нескольких запросов PUT, не дожидаясь ответа между ними. Я как бы надеялся на некоторое обсуждение от других, которые столкнулись с одной и той же проблемой, но я думаю, что это не совсем самая захватывающая тема. :) Спасибо что нашли время ответить. –

+0

На самом деле я столкнулся с проблемой. У нас был клиент, который время от времени обновлял фон (условный «GET») ресурсов, и иногда это происходило одновременно с тем, что _same_ клиент сделал «PUT», потому что пользователь нажал «сохранить». Таким образом, мы сделали единственную нормальную вещь, и это должно было ввести мьютекс в клиенте, чтобы все взаимодействие с одним URI было поставлено в очередь. Я не могу вспомнить, имел ли сервер это, но это имеет смысл и там. – mogsie

0

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

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

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

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

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

Некоторые из духа REST - это разделить материал, и клиент может решить, когда делать операции CRUD.

BTW: Существует великолепный интерфейс Java-скрипта, который отлично сочетается с REST apis: extJS. Он работал хорошо для нас во внутренних приложениях (я бы не стал публичным веб-сайтом с extJS из-за стиля внешнего вида &).

+0

Чтобы уточнить: URL-адрес _does_ включает в себя «objectId», который уникально идентифицирует ресурс в базе данных. Однако объекты JSON изначально извлекаются с сервера (и после этого повторно отправляются клиентом обратно на сервер после модификаций) опускают фактические идентификаторы базы данных. Я думаю, что причины этого должны быть очевидными: не полагаться на клиента для сохранения ценностей, которые фактически бессмысленны, насколько это необходимо. Как я уже сказал, это затрудняет слияние, но кажется правильным. –

+0

«Некоторые из духа REST - это разделить материал, и клиент может решить, когда делать операции CRUD». - Я не уверен, что понимаю, что вы здесь говорите.Если я опишу простой подход, который я описал, то это означает, что клиент не может использовать API ReST, но он не желает этого нового (возможно, неочевидного) ограничения. Если ограничение клиента не более чем на один выдающийся PUT за определенный ресурс действительно является лучшим решением, то я ожидал, что кто-то где-то явно сделал бы это заявление. Эта _can't_ будет новой проблемой ... –

+0

1-й комментарий: если URL-адрес содержит идентификатор, да, вы можете отбросить идентификатор внутри представления ресурса (в вашем случае JSON). Тем не менее вы можете сделать это контрактом вашего интерфейса REST, что клиент должен передать «полные представления» ресурса. До сих пор это не было проблемой для API (клиент + сервер), с которыми я работал или реализовал. –