This is the first in a series of articles looking at the conversation scope introduced in CDI as part of Java EE 6. We’ll start by looking at existing scopes and how they introduce limitations for developers and how CDI conversations get around these limitations.
Why we need a CDI Conversation scope
Scopes limit the lifespan of a piece of data and the ability for different users to access that data. Most of us are familiar with the 3 common scopes available in web applications.
Original Web Application Scopes
- Data is available to all users of an application for the duration of the application
- Data is only available to the current user for the duration of the users session.
- Data is only available to the current user for the duration of the current request
The application scope was intended to contain data that was shared among all users of an application, Session was intended for holding data belonging to the individual users, and Request scoped data was limited to the current web request for the user and was not available from one request to the next.
Regarding statefulness, these scopes are either all or nothing with respect to requests. If you put an object in the request scope, it will not exist in the next request. If you put an object in the session scope it will be available in every single request that user makes until they leave the application. An object placed in Application scope will be available in every request made by every user until the application is restarted.
As developers, this puts us in a very tricky place since we don’t have a lot of options. While there are clear cases of when we should use Application or Session scoped data, 80% of our application will probably be using objects of a much shorter lifespan than the user session and application, but longer than a single request. Take for example a situation where we need to collect a lot of information over 3 pages and at the end save the data. What if we are editing an entity which allows us to click an AJAX button and modify the server side state that does not have any client side representation? How do we hold that state for when we want to save our objects later?
The fact is that most applications have a lot of state that needs carrying around from request to request and the 3 scopes we’ve been using for years are not a great solution for dealing with it.
Hold state in the session
We can hold the state in the session under a specific key name and fetch it during each request, but that leads to the following problems :
- Huge sessions if you never clear out the session objects when you are done with them
- Overwriting session objects if you have two windows/tabs open at the same time
- In a replicated environment this can have problems if you modify the object and forget to call setAttribute on the session for the bean. This can lead to out of date objects on some servers and bugs that are difficult to track down.
Hold state in the database
We could use the database to store the state in between requests so we have a stateless system without having to ferry data between the client and server and without having to hold it on the server. Again, this has a set of problems :
- It is slower than an in-memory fetch
- The database scales the least of all the tiers and will soon become a bottleneck
- You still need to do some work for optimistic transactions
- Our bottleneck multiplies when you bring Ajax into the mix as then we have dozens of requests per page each needing to hit the database
- If you have partially created objects in the database, you need to keep excluding those from queries which while not impossible is prone to being missed off queries and either breaking the application or skewing reporting results.
- If you save as a partially created object, you have to overcome (i.e. remove) database constraints that might not be met by a partially constructed object
- If you don’t save state as a partial object, it will require additional code to read and write the object to the database or other store.
Hold state in the client
Sending the state to the client means we generate the state in the initial request and ship it off to the user as part of the page. When they post back, the state is read back in and re-constructed server side. While this delivers a truly stateless application and is very scalable, it has the following problems :
- You have to make sure you get all the state on the client which can be difficult with more complex models (i.e. a Teacher with a set of students). Otherwise you need to go back to the database for the information.
- You can’t use GET urls unless you attach everything to the url as a parameter and keep up with the maintenance of the values included. Bundling everything in a POST is easier.
- You have to validate everything that is sent back to the server from a client that could be malicious
The validation issue is particularly interesting because in many cases, you would need to go and re-load the data again just to validate it to ensure the user hasn’t modified it. In some ways, this can be the worst of both worlds since you have to code for client side state handling, but still have to go to the database to validate your model.
Improving the current options
There are two possible modifications to the above examples that make them more palatable.
One is to use a key that is unique to the page to store objects in the session so objects can’t be over written in the session. The key is then passed from server to client and back again but you have to manage the propagation of the key from one page to the next.
We can also use a cache with the database to hold recent copies of data based on the notion that since this object was just used, it will be used again soon. In essence this is adding a stateful component to the stateless system in order to relieve the database.
If these two options sound like good ideas then you are going to love Conversations because fundamentally, that is what they are.
A conversation is a scope that isolates data in the session from different requests and holds state from one request to another for a duration that is shorter than the session. This is exactly what we want for our data, we want to use it across multiple requests, but we don’t want it to last forever, and we don’t want the data from one page overwriting the data in another in the session.
Conversations are like numbered buckets that are held by a conversation manager in the session. Objects can be put into a conversation bucket where it stays for a fixed duration since once the conversation times out, the bucket is removed from the manager and the objects in the bucket are freed up. This is like a smart stateful cache that knows how long each conversation needs to be held for (you can even change conversation durations on a per-conversation basis). An object cache just removes objects on a most recently used basis with no context of what the object is actually used for or how long it is needed. A Conversation ‘cache’ has context and knows what data it has and how long it is needed for.
We can enjoy the benefits of state on the server side without the worry of the user session growing too large, and as part of java EE 6, our other frameworks will be able to deal with conversations by default, such as the ability to propagate the conversation key from one page to the next as needed in JSF.
Since conversations ids are unique to the specific activity the user is performing, the user can carry out two similar activities such as editing two separate Person entities in different browser windows without worrying about the data being overwritten in the session since the data is isolated in their own conversation instance. This means we get properly functioning web applications that can deal with multiple windows/tabs at once…for free!
Furthermore, if you consider that EJBs can be put in a conversation, then you have a conversation cache containing objects that when needed, can passivate themselves to save resources which lets you stretch your server resources even further.
Next time, we’ll start using CDI conversations and see how they work and the third part will look at dealing with Persistence and conversations.