Jacob O'Bryant | 9 Jun 2026
As I wrote about previously, I've been working on splitting Biff up into a bunch of separate libraries and changing various things along the way. I've completed a rough draft of all twelve libraries and am now going through them one-by-one to polish and release them. The first library is now ready.
biff.core: system composition and other interfaces for Biff projects. This is the glue that holds all the other libraries together, and that's why I'm releasing it first.
For a long time Biff has had this "modules and components" structure where each application namespace in your project exposes a "module" map, then you have a bunch of boilerplate to combine stuff from those modules into a single "system" map, and then we thread the system map through your "component" functions on startup. Biff 2 retains that structure, and it has some additional stuff to deal with that boilerplate.
For an example of what I'm talking about, see this code which takes the :routes (and :api-routes ) keys from your modules and turns them into a :biff/handler value for the system map. I wanted a first-class way to be able to extract that kind of logic cleanly into a library so that the library's instructions can just be "add this module to your project" without an accompanying "and then paste all this stuff into your main namespace."
So this new biff.core library includes a concept of "init functions." These are functions that take a collection of modules and return a single map that can be merged into your system map. Ta da. Here's an example. Init functions are stored in the :biff.core/init key in your module maps, so we get that nice "all you need are modules (well, and components)" effect.
The main complication here is that the boilerplate of defining a (def handler ...) var in your application code actually has a nice side benefit: late binding. If you change any of your modules, the handler var will get updated, and if you set :biff/handler in your system map to the var instead of the value ( #’handler ), incoming Ring requests get the latest handler without you having to restart the web server. If we extract that boilerplate into library code, we don't get the var.
I ended up on this solution:
Init functions take a var of your modules vector, not the vector value itself.
Anything in the system map that you want to get updated without a restart needs to be a function. In some cases this means instead of setting a :com.example/my-thing key on the system map, you need to set a :com.example/get-my-thing function which returns my-thing .
... continue reading