Mark McGranaghan and I have recently been working on new functionality for Ring, a web application library for Clojure. Ring is similar to Rack on Ruby, and provides a simple, functional interface for handling HTTP requests.
We've recently added support for urlencoded parameters and cookies, and next we plan on adding session support to the mix. What we'd like is to get some feedback from the Clojure community on how best to implement session support.
Currently we have two possible designs:
Design 1: The session is stored as an mutable atom map in (request :session). To update the session, use the standard Clojure swap! function, e.g.
The reasoning behind this approach is that sessions are mutable maps associated with the request, and thus the most natural way to represent them is via an (atom {}) on the request.
Design 2: The session is an immutable map in (request :session). To update the session, add the updated session to the response, e.g.
The reasoning behind this approach is that a more functional design has more in common with other parts of Ring, and allows handlers to be pure functions, even if they use sessions.
I favour design 1, whilst Mark favours design 2.
I've also had an email suggesting that instead of the session being completely replaced by the session map on the response, it is instead only updated. This could be achieved by adding an update function to the :session key, or perhaps to merge the response session with the existing session, removing any key with a value of nil.
Please let us know what you think is the best approach listed here, or if you have an entirely new design we've not thought of.
> Design 1: > The session is stored as an mutable atom map in (request :session). To > update the session, use the standard Clojure swap! function, e.g.
> Design 2: > The session is an immutable map in (request :session). To update the > session, add the updated session to the response, e.g.
I prefer design 1. As much as I would like to think of (request -> response) as a pure function, there are often other things going on such as talking to a database. What if the session is changed by two requests that are being handled simultaneously? Ring can't automatically re-try because then a database update may be repeated.
With the same issue in mind, I propose design 3. Like design 1 except (ref {}) instead of (atom {}) because I may want to coordinate changes between the session and some other transaction-aware state.
On Feb 3, 9:21 am, Eric Lavigne <lavigne.e...@gmail.com> wrote:
> With the same issue in mind, I propose design 3. Like design 1 except > (ref {}) instead of (atom {}) because I may want to coordinate changes > between the session and some other transaction-aware state.
Sessions can be implemented with many storage engines, and not all of them will have transactions. Even those that do support transactions, such as SQL databases, do not tie in with Clojure's STM mechanism, so making the session storage a ref would only work for in-memory session storage.
In my view, we need to take a lowest-common-denominator approach: if two threads try to write the same session data at the same time, then the last one to write wins. This approach will work with every sort of storage engine, and will be sufficient for most session uses. Should something more fine grained be required, a value can be added to the session that references a more sophisticated concurrency primitive.
On Wed, Feb 3, 2010 at 8:27 AM, James Reeves <weavejes...@googlemail.com> wrote: > Hi folks,
> Mark McGranaghan and I have recently been working on new functionality > for Ring, a web application library for Clojure. Ring is similar to > Rack on Ruby, and provides a simple, functional interface for handling > HTTP requests.
> We've recently added support for urlencoded parameters and cookies, > and next we plan on adding session support to the mix. What we'd like > is to get some feedback from the Clojure community on how best to > implement session support.
> Currently we have two possible designs:
> Design 1: > The session is stored as an mutable atom map in (request :session). To > update the session, use the standard Clojure swap! function, e.g.
> The reasoning behind this approach is that sessions are mutable maps > associated with the request, and thus the most natural way to > represent them is via an (atom {}) on the request.
> Design 2: > The session is an immutable map in (request :session). To update the > session, add the updated session to the response, e.g.
> The reasoning behind this approach is that a more functional design > has more in common with other parts of Ring, and allows handlers to be > pure functions, even if they use sessions.
> I favour design 1, whilst Mark favours design 2.
> I've also had an email suggesting that instead of the session being > completely replaced by the session map on the response, it is instead > only updated. This could be achieved by adding an update function to > the :session key, or perhaps to merge the response session with the > existing session, removing any key with a value of nil.
> Please let us know what you think is the best approach listed here, or > if you have an entirely new design we've not thought of.
The last idea is on the right track. In any case, any reference type surrounding the session data isn't really the session data of record, it will be backed by something else (cookies/db etc), so such use of references is not right.
OTOH, the Clojure model of sending change into state can help encapsulate how it is stored, and possibly avoid some read-modify-write and last-one-wins issues.
One idea is to pass commute-like and deref-like functions to the handling code for sending change to, and accessing, the session state. The state will be an immutable value as usual. This encapsulates everything about the session data.
A second option is the request has the 'current' session state value, and people can (functionally) append to the response session-commuting fns and args, to be applied to the session state on successful response.
In either case, moving to a commute model is a good idea, even if not all backends can support it (in which case they devolve to last-one-wins).
Whatever you guys chose, do not go the immutable road. Compojure took that approach and now many people (including me) are stuck with situations where they need to update session in a middle and pass it somewhere else, and they can't. Session is a data storage, just like a database.
One of the strong points of compojure is the layers of middleware. Unfortunately there's no way to communicate between the layers, because current session updating mechanism makes it impossible. You cannot use the session to signal to the middleware that this particular case requires different handling.
So one has to resort to communicating through other shared data storages like database and then practically duplicate the session mechanism.
On Feb 3, 4:27 pm, Vagif Verdi <vagif.ve...@gmail.com> wrote:
> Whatever you guys chose, do not go the immutable road. Compojure took > that approach and now many people (including me) are stuck with > situations where they need to update session in a middle and pass it > somewhere else, and they can't. Session is a data storage, just like a > database.
One thing that would help us a lot with choosing the right interface for sessions is examples of session use from real application code. We have considered simple examples like incrementing a counter and logging in / logging out (http://gist.github.com/289993), but a concrete example of read-write-read session usage would be really helpful. Would you be willing to show us a case were you think stateful sessions would be particularly nice?
> One of the strong points of compojure is the layers of middleware. > Unfortunately there's no way to communicate between the layers, > because current session updating mechanism makes it impossible. You > cannot use the session to signal to the middleware that this > particular case requires different handling.
> So one has to resort to communicating through other shared data > storages like database and then practically duplicate the session > mechanism.
It is possible to communicate between purely functional middleware layers. The way one does this is by assoc'ing keys into the request and/or response maps, and then get'ing and acting on those values later in the request/response ring [1]. For example, to alter the the behavior of middleware "wrap-second" from "wrap-first"
This type of middleware communication will work in some cases but may not in others. In particular, it works best when the actions in question either happen all on the way down the middleware stack from the adapter to the handler, or all on the way up. Would this kind of communication be helpful for the problems that you are having? Again, specific examples would be great.
On Feb 3, 3:55 pm, Mark McGranaghan <mmcgr...@gmail.com> wrote:
> One thing that would help us a lot with choosing the right interface > for sessions is examples of session use from real application code. We > have considered simple examples like incrementing a counter and > logging in / logging out (http://gist.github.com/289993), but a > concrete example of read-write-read session usage would be really > helpful. Would you be willing to show us a case were you think > stateful sessions would be particularly nice?
There were a few corner cases with immutable sessions discussed in compojure group:
Solution is.. not to run it more than once. :)) But imagine chained function calls. Those session-assocs will clash.
> It is possible to communicate between purely functional middleware > layers. The way one does this is by assoc'ing keys into the request > and/or response maps, and then get'ing and acting on those values > later in the request/response ring [1].
Ah my bad. I shouldn't have said "impossible". Obviously it is possible, because i do that too. But it feels like a kludge. Sometimes you return body of html. Other times you have to return response object where body is one of its value-key parts along with some other keys.
>Would this kind of > communication be helpful for the problems that you are having? Again, > specific examples would be great.
Here's the problem i am having with passing a session:
Session is a long term data storage. This means that it should be available be read / write from anywhere in the chain of processing the request. In haskell you can pass session through a chain of functions in a State monad. Any function in a chain can read or write thus affecting others.
If we can achieve same thing in compojure it would be great, not because it is not possible to operate other way. But because it would be much more simpler.
BTW i do not see any point in trying to make session handling "pure". We are dealing with fundamentally impure operation: handling a web request. It practically always depends on more than input variables. If it is a static file it depends on what's on the harddrive. Most cases it reads/writes data to database. In fact, if not the performance price for hitting database, no one would bother with sessions. It is quite simple to store session data in database. The reason we are holding that data in a session is not that it is somehow different from other data that we store in db. The reason is that we need that data in every request. We simply caching.
Actually, I was completely wrong in that instance. I'm not sure what I was thinking, but it is perfectly possible to clear functional sessions. The problem is limited only to the Compojure session middleware, and is not indicative of an inherent flaw in the functional session design.
This again is something that's limited to Compojure, and not necessarily indicative of a problem with the functional approach. The session-assoc function is essentially a Ring handler function that modifies the request. Because each session-assoc is unconnected from the next, there isn't a way to chain their output. However, this doesn't mean that a more sophisticated approach isn't possible.
On Feb 3, 11:31 am, Rich Hickey <richhic...@gmail.com> wrote:
> One idea is to pass commute-like and deref-like functions to the > handling code for sending change to, and accessing, the session state. > The state will be an immutable value as usual. This encapsulates > everything about the session data.
That's an interesting approach.
Would you consider it good or bad practise to inherit the session store from IDeref? Clojure's concurrency primitives have different functions to handle updates (swap!, alter, etc.), but universally use deref to return their contents. Would it be worth following the same convention for sessions?
On Feb 3, 7:18 pm, James Reeves <weavejes...@googlemail.com> wrote:
> On Feb 3, 8:44 pm, Vagif Verdi <vagif.ve...@gmail.com> wrote:
> This again is something that's limited to Compojure, and not > necessarily indicative of a problem with the functional approach.
I agree. As long as session implementation is robust and simple to use without corner cases it does not really matter for me how they are implemented internally.