Making route/session/flash "globally" available

38 views
Skip to first unread message

Brian Carper

unread,
Jul 8, 2009, 3:40:34 PM7/8/09
to Compojure
I have routes that dispatch to functions which call many other
functions (often 5 or more levels deep) to dynamically generate HTML
for the layout of my site. Some of those functions need to see the
session, so that certain bits of layout are drawn or left out
depending whether the user is logged in.

So pretty much every function needs to have the session as a
parameter, and most of them just pass it along to other functions
without doing anything with it. I have similar needs for the params
and headers and flash. Passing all of this around to every function is
painful.

How do you deal with this? I come from a Rails background where
params and session are always easily accessible. I like this for
convenience even though it isn't as "functional". I'm currently doing
something like this:

(def *REQUEST* nil)
(def *SESSION* nil)
(def *PARAMS* nil)
(def *HEADERS* nil)
(def *FLASH* nil)

(defn with-bindings [handler]
(fn [request]
(binding [*REQUEST* request
*SESSION* (:session request)
*PARAMS* (:params request)
*HEADERS* (:headers request)
*FLASH* (:flash request)]
(handler request))))

(defn binding-routes [& handlers]
(-> (apply routes* handlers)
with-bindings
with-params
with-cookies
with-session))

(def blog-routes
(binding-routes
(GET "/" (index-page)))) ; now index-page and all the functions it
calls can access thread-local copies of the session/params/etc. vars
from above

Good idea or bad idea? Is there a better way?

Thanks
--Brian

James Reeves

unread,
Jul 8, 2009, 5:25:47 PM7/8/09
to Compojure
On Jul 8, 8:40 pm, Brian Carper <briancar...@gmail.com> wrote:
> So pretty much every function needs to have the session as a
> parameter, and most of them just pass it along to other functions
> without doing anything with it.  I have similar needs for the params
> and headers and flash. Passing all of this around to every function is
> painful.
>
> How do you deal with this?  I come from a Rails background where
> params and session are always easily accessible.  I like this for
> convenience even though it isn't as "functional".

This answer might be a little lengthy, so please bear with me.

In computer software, bugs are caused when a program enters an
unforeseen state. So to prevent bugs is equivalent to reducing
unpredictability.

Functional programming is a way of reducing unpredictability by
eliminating implicit state. In a pure functional language like
Haskell, functions are free of side-effects and this makes them more
predictable. For instance, a function in Haskell will always return
the same result, given the same arguments.

In a more general sense, what functional programming languages like
Haskell do is limit the flow of information: only data explicitly
passed through a functions arguments affect its results. We can
additionally constrain this data by adding strict type checking, and
Haskell provides developers with a very sophisticated type system.

Since Haskell is a very good language for reducing bugs, why do I use
Clojure?

Basically because robustness is not the only measure of a programming
language. Clojure sacrifices static typing and allows some side
effects in return for greater flexibility and conciseness. In my view,
Clojure strikes a better balance between flexibility and robustness.

Global bindings require a similar trade-off. You're trading robustness
for conciseness, but the trick is to ensure that you're getting the
right balance.

Let's now consider your application. Your views depend strongly on
whether the user is logged in or not. You probably also need
information like their username and some for of user ID. Assuming
that's all you need:

(declare current-user)

(defn with-user-binding [handler]
(fn [request]
(binding [current-user (-> request :session :user)]
(handler request))))

(defroutes user-routes
...)

(decorate user-routes with-user-binding)

In the above case, I'm using exactly one binding, and I'm limiting it
to user-routes, which is a collection of all the routes that I know
will need the current-user binding. In the above example, I'm passing
no more information than is required to my functions.

In Compojure, you should think of routes as a gateway; something that
not only defines how browsers can access your application, but also
something that determines what information is allowed to pass through
to your internal logic.

In my opinion, a philosophy of information restriction like this
results in programs that have less bugs, are more secure, and are
easier to refactor.

- James

Brian Carper

unread,
Jul 8, 2009, 9:12:02 PM7/8/09
to Compojure
On Jul 8, 2:25 pm, James Reeves <weavejes...@googlemail.com> wrote:
> In Compojure, you should think of routes as a gateway; something that
> not only defines how browsers can access your application, but also
> something that determines what information is allowed to pass through
> to your internal logic.
>
> In my opinion, a philosophy of information restriction like this
> results in programs that have less bugs, are more secure, and are
> easier to refactor.

Thanks for your reply. I've always viewed routes as a simple dispatch
table from request URIs to handlers. Handlers know which bits of the
session/params/flash they need. Making that decision at the route
level puts a lot of distance between the decision and the place the
data is actually used. I think this makes it more difficult to
understand and refactor, in addition to tons of bookkeeping in the
routes, which is why I went with what I did.

The session is an immutable object, so I see no danger in letting
everyone have a peek. Functions are only reading the session, not
altering it; there are no side-effects introduced. There is the
danger of lots of functions looking into and depending on the guts of
the session, but I only access the session through accessor functions
I wrote, so I'd only need to change the accessor in that case.

I realize safety vs. convenience is an eternal debate though. I agree
in general that restricting information is good, but I just don't see
how it's useful in this case. If you could give a specific example of
how your version is better than mine, that'd be helpful.

James Reeves

unread,
Jul 10, 2009, 2:45:12 PM7/10/09
to Compojure
On Jul 9, 2:12 am, Brian Carper <briancar...@gmail.com> wrote:
> I realize safety vs. convenience is an eternal debate though.  I agree
> in general that restricting information is good, but I just don't see
> how it's useful in this case.  If you could give a specific example of
> how your version is better than mine, that'd be helpful.

It's unfortunately quite tricky to demonstrate safety via an example.
After a few days of thought, I was unable to come up with a concise
example that could effectively demonstrate the advantages of my
version. The best I can do is to point out that my version involves
less information being exposed to functions, which in turn means there
are less factors that could cause bugs.

Additionally, a useful side-effect of this approach is that the user
is forced to think carefully about how data is passed between
functions. If a lot of data is being shared between functions, that
might indicate your functions are too tightly coupled. By artificially
restricting the flow of data, we force a design that is very loosely
coupled. This is useful for testing, as less data needs to be mocked,
and for refactoring, as it's easier to replace a portion of
architecture if it has only a few connections to the rest of your
application.

- James

Brian Carper

unread,
Jul 15, 2009, 1:41:39 PM7/15/09
to Compojure
On Jul 10, 11:45 am, James Reeves <weavejes...@googlemail.com> wrote:
>
> It's unfortunately quite tricky to demonstrate safety via an example.
> After a few days of thought, I was unable to come up with a concise
> example that could effectively demonstrate the advantages of my
> version. The best I can do is to point out that my version involves
> less information being exposed to functions, which in turn means there
> are less factors that could cause bugs.

I've been stewing over this for a few days and I've almost convinced
myself that you're right. Thanks for something to think about in any
case!

One definite weakness of my approach is testing or even playing around
at the REPL, because it's easy to forget to bind those vars when you
call the functions, but it's impossible to forget to pass them if
they're required, explicit function parameters.
Reply all
Reply to author
Forward
0 new messages