URL lookup with koyo/url

When setting up a koyo-based web app in Racket, you’ll probably use dispatch-rules+roles instead of Racket’s built-in dispatch-rules. Both utilities give you a dispatching function: a function whose value is a URL pointing to a servlet in your app.

Use the koyo version you get a URL dispatching function. (koyo’s dispatch-rules+roles supports what it calls user roles, a powerful facility that has no analogue at all in dispatch-rules, but that’s the subject of another post.) dispatch-rules+roles solves an awkward problem that dispatch-rules creates. If you use dispatch-rules for a web app where you have interacting servlets (servlets that generate links referring to other servlets), the URL dispatcher coming from dispatch-rules imposes a kind of organizational logic that I find awkward. I want to write my servlets without having to include the application’s dispatcher. As far as possible, I want to make my servlets first, allowing them to link to one another, finally pulling them together in the dispatcher.

With dispatch-rules, you refer to servlets by identifier. Here’s an example:

(define-values (dispatcher url-generator)
   [("a") servlet-a]
   [("b") servlet-b]))

Let’s say you want servlet-a to generate a response whose body includes a link to b, handled by servlet-b. That means that, somewhere in the definition of servlet-a, you’ll need to do this:

(url-generator servlet-b)

(Yes, you pass a function to url-generator.) As long as your code for all this is all in one module, there’s no problem. The problem starts when you try to move the code for servlet-a to a separate file. But the definition of servlet-a uses url-generator coming from dispatch-rules. You’re stuck! Better put servlet-a back where it was. The price you pay is ever growing module sizes. Sure, some stuff can be socked away in separate files, but not everything.

koyo’s dispatch-rules+roles avoids this problem. It avoids it by giving you a URL generator that takes a plain old symbol. Let’s rewrite the above example in koyo terms:

(define dispatcher
   [("a") servlet-a]
   [("b") servlet-b]))

Well, that was fascinating, wasn’t it? All we did was rewrite dispatch-rules as dispatch-rules+roles. But notice the value in that: the API for both macros is basically the same. Actually, dispatch-rules+roles offers somewhat more, so they’re not exactly the same. A better way to put it is that you can use koyo’s dispatch-rules+roles as a drop-in replacement today for your dispatch-rules code because the syntax is the same and indeed, morally, they are equivalent.

Notice that the URL generator coming out of dispatch-rules is gone. How do I generate URLs, then?, you might ask. The answer is koyo’s reverse-uri.

In the above example, we wanted to refer to the URL b. Using dispatch rules, we got to b by using servlet-b. With koyo, you do it like this:

(reverse-uri 'servlet-b)

Yes, just a symbol. It’s that easy. Instead of referring to the servlet by an identifier (a name with a binding), you refer to it by its (symbolic) name.

If you’re used to the kind of compile-time guarantee that dispatch-rules offers, namely, it is impossible to refer to non-existent dispatchers, you might be thinking: Hey hold on, what’s to stop me from generating invalid URLs? The answer is: there’s nothing stopping you. With koyo’s , you might get runtime errors when you try to generate a URL for an unknown servlet. In the above example, for instance, this code

(reverse-uri 'this-will-fail!)

will fail (raise an exception) at runtime.

BUT. By following the koyo approach, you can safely move the code for servlet-a to a separate file. dispatch-rules+roles sacrifices a compile-time guarantee in favor of programmer flexibility.

In practice, I’m happy to live with the possibility that my application generates a bad URL at runtime. The risk of this can be reduced by system tests that exercise the URL-generating parts of the code. Although I admit that dispatch-rules does have its advantages, the cost for larger web apps is often rather high. koyo’s dispatch-rules+roles lowers that cost dramatically while preserving dispatch-rules’s nice routing syntax.