Skip to content

Writing Handler Middleware

Dmytro Bunin edited this page Apr 20, 2020 · 28 revisions

In v0.8.0 of re-frame, Middleware was replaced by Interceptors.
So this document is no longer current, and has been retained only as a record.
Current docs can be found here

--

Sometimes I feel like I’m just pretending to have imposter syndrome.    credit

Middleware Makes Imposters

A Middleware is a function. Given an event handler, it will return an event handler.

handler -> handler

But here's the thing: it won't return a real handler - it will return something that pretends to be a handler.

This imposter handler will often modify the behavior of the original handler, or side-effect in some useful way.

Template

Every Middleware function fits this template:

(defn my-middleware
  [handler]                 ;;  <--- middleware takes a handler argument
  ... in here we return a handler)

And almost 100% of Middleware matches this more fleshed-out template:

(defn my-middleware
  [handler]                 ;;  <--- Middleware takes a handler argument
  (fn imposter-handler      ;;  <--- returns what looks like a handler to me
     [db v]                 ;;  <--- yep takes the right parameters
     ... something in here)))  ;; <--- what happens in here ??

No-op Middleware

This is the simplest possible Middleware. But it isn't much of an imposter. It actually returns the original handler untouched. Duh! It was thrown out of middleware school.

(defn no-op 
  [handler]
  handler)   ;; give back the same handler

Excellent, we've just reinvented identity.

Here's a slightly more interesting version of the no-op Middleware:

(defn almost-noop
  [handler]
  (fn imposter-handler
    [db v]
    (handler db v)))    ;; just on-calls the original handler

As you'll soon see, this process of on-calling a real handler from within an imposter handler is very useful.

Horrible Mistake Middleware

Due to someone else's mistake (probably someone in marketing), the following Middleware has a bug. It always forwards the wrong event.

(defn buggy-middleware
   [handler]
   (fn imposter-handler
     [db v]
     (handler db [:send-spam true])))    ;; yikes. on-calls with the wrong 'v'

Do you see how the middleware-returned handler "wraps" the given handler. This particular wrapping means the handler always gets the same, wrong event. Very evil. But it can be used for good ...

Trim-V Middleware

Events (v) always have the form: [:some-id some other parameters]. It would be good if we could trim off that annoying first element :some-id. How about this middleware ...

(defn trim-v
   [handler]
   (fn imposter-handler
     [db v]
     (handler db (rest v))))    ;; get rid of the pesky first element of v

Simple, but slightly risky. v comes in as a vector, but is passed on as a list. Probably wise to put a vec around the rest.

Any handler "wrapped" in trim-v will have this slightly neater look:

(defn my-handler
   [db [param1 param2]]  ;; there's no ugly '_' before param1 
   ...)

Outsideware

On another page, I claimed that Middleware should be called Outsideware because it normally does something either before or after on-calling to the real handler. Either side. Like the bread either side of ham in a sandwich.

Here's how that looks in code:

(defn my-middleware
  [handler]                 
  (fn imposter-handler     
     [db v]                 
     (let [ ......  do something BEFORE the handler is called
            val   (handler db v)   ;;   <---- original handler called here
            ......  do something AFTER the handler is called]
     ... return val ?)))

When Middleware does something in the BEFORE position, it is often altering db or v, ahead of passing them into handler. trim-v was this kind of Middleware - it altered v before on-calling.

When Middleware does something in the AFTER position it is often modifying the value returned. The built-in middleware called enrich is an example of this.

But not always. A great many Middleware don't change db, v or the return at all, and instead they just need to be in the pipeline to produce a side effect, like debug.

Middleware Factories

A Middleware Factory is a function which returns Middleware.

Middleware factories always take a parameter which is used to parameterise the behavior of a Middleware returned.

Template

Every Middleware Factory fits this template:

(defn a-factory 
  [params]
  (fn middleware              ;;  <-- returns a middleware
    [handler]                 ;;  <--- middleware takes a handler argument
    (fn imposter-handler      ;;  <--- returns what looks like a handler to me
       [db v]                 ;;  <--- yep takes the right parameters
       ... something in here)))  ;; <--- what happens in here ??

Notice that our imposter-handler now closes over (and can use) params given when the middleware factory is called.

For an example look at enrich or path in: https://github.com/Day8/re-frame/blob/master/src/re_frame/middleware.cljs