I am working on a project that is in transition from proof-of-concept to something worthy of a pilot project. One of the key improvements for this phase of development is to move away from the current "persistence" mechanism, which uses a hash table held in memory and periodically dumped to a file, to a more traditional database back-end.
The application itself is designed with ReSTful principles in mind. It uses Jersey to provide access to resources and jQuery to present these resources to the user and enable basic interactions (create, update, delete). Pretty straight-forward.
I have used JPA and Hibernate suc开发者_如何学Ccessfully in the past for persisting server-side state in a relational database, so it seemed a natural choice in this case. With a few minor changes to the model entities, I was able to get basic read, create, and delete operations working in reasonable time. However, the update operation has proved more difficult.
The client-side part of the application automatically sends changes to the server a couple seconds after the user modifies the resource. In addition, there is a "save and close" button that the user can press to send the latest version of the resource to the server immediately before returning to the home page.
My first problem was how to update the managed entity from the database with the unmanaged object coming from the client. Since the data sent to the client deliberately omits database keys, this was a bit tedious as it came down to explicitly "merging" elements from the unmanaged object into the Hibernate object. This isn't my question, but if anyone knows of an elegant way to do this, I'd be very interested to hear about it.
My second problem, and the object of this post, occurs when I press the "save and close" button around the same time as the auto-save operation I mentioned earlier is going on. More often than not, I get an optimistic locking exception. This is because the second update operation is being handled in a separate thread while the first is still being processed.
The resource has an "updated" time-stamp that is set each time an update is processed, so an update to the database is pretty much guaranteed each time, even if nothing has changed. This in itself is probably an issue, but even after I fix it, there is still a "window of opportunity" where the user could make a modification and send it to the server while an auto-save is in progress, triggering the same problem.
The simplest approach I can think of to address this issue is to rework some of the client-side Javascript to ensure that there is only ever one outstanding "update" operation from that client at any point in time. (Note that if a different client happens to update the same resource at the same time, an optimistic locking exception is perfectly fine.) However, I'm concerned that forcing this limitation on the client may be deviating from the spirit of ReST. Is it reasonable to expect a given client to have no more than one outstanding "update" (PUT) request to a particular resource at any point in time in a ReSTful application?
This seems like a fairly common scenario, but I haven't been able to find a definitive answer about how best to handle it. Other ideas I've considered and discarded include somehow serializing requests from the same client (perhaps based on HTTP session) so that they are handled in order, implementing a "queue" of updates for a JPA/Hibernate worker thread, and inserting new "versions" of the resource while keeping track of the latest one rather than updating any single record.
Any thoughts? Does the "one outstanding update at a time" limitation on the client seem reasonable, or is there a better approach?
I've run into the same problems of clients performing simultaneous PUT operations towards the same URI at the same time. It doesn't make sense for a client to do so, but I don't think you'll find any hard documentation that says it's prohibited or any degree of being RESTful. Indeed, a server can't do anything other than serialize them and complain about optimistic concurrency when it does happen. A well behaved client would do wise to avoid these by synchronizing all operations on each resource, even safe operations like GET or HEAD, so you don't pollute your caches with stale data.
My first problem was how to update the managed entity from the database with the unmanaged object coming from the client. Since the data sent to the client deliberately omits database keys, this was a bit tedious as it came down to explicitly "merging" elements from the unmanaged object into the Hibernate object. This isn't my question, but if anyone knows of an elegant way to do this, I'd be very interested to hear about it.
Either you should include the id to the resource the client is sending or use PUT (e.g. PUT /object/{objectId}
). Then you don't need to merge but just "replace". I prefer to always return and expect complete resources. It avoid the yucky merging.
My second problem, and the object of this post, occurs when I press the "save and close" button around the same time as the auto-save operation I mentioned earlier is going on. More often than not, I get an optimistic locking exception. This is because the second update operation is being handled in a separate thread while the first is still being processed.
As you mentioned you can do this on client-side by JavaScript. You can introduce a 'dirty' flag and both the auto-save or the save-close button only transmit a request to server when the flag 'dirty' is set. The 'dirty' flag is toggled, when the user changed something. I would not let the server serialize requests, it complicates things.
[...] However, I'm concerned that forcing this limitation on the client may be deviating from the spirit of ReST. Is it reasonable to expect a given client to have no more than one outstanding "update" (PUT) request to a particular resource at any point in time in a ReSTful application?
Some of the spirit of REST is to decouple stuff and client can well decide when to do CRUD operations.
BTW: There is a great frontend java-script framework which goes very well with REST apis: extJS. It worked well for us for internal applications (I wouldn't do a public website with extJS because of the extJS-style look&feel).
精彩评论