Philosophy of Biff

Biff is a framework for building monolithic, full-stack web apps in Clojure. It exists because I spent a lot of time building Clojure web apps the regular way, by composing all the libraries together myself. Learning how to do that was a valuable experience and I recommend everyone do it at some point, but I don’t think it should be a hard prerequisite to being a productive Clojure web developer. I turned my composition of libraries into a framework so that those who have similar needs to me can reuse the work I’ve already done, if they want.

Biff is designed for my needs as a solo developer—previously as an entrepreneur, and still as a hobbyist. Prototyping speed is important in both those domains, not least for the sake of morale. Yet I also needed something that wouldn’t get in the way as my apps grew. These aren’t just throwaway weekend projects; some of them I’ve ended up developing and maintaining for years. In the very first release, Biff had a bunch of interesting, experimental features; I have since removed all the interesting parts and left only the reliable parts.

So the guiding principle for Biff has been “what do I want for my own projects?” Then “what would be helpful for other hobbyists and entrepreneurs, especially those who are relatively new to Clojure web dev?” I would also like Biff to be suitable for small teams, though I haven’t focused on this much yet.

The Clojure community has historically been wary of frameworks, and for good reason. Few things are as frustrating as being stuck inside someone else’s incorrect model of reality. And yet frameworks have real benefits. How do we write frameworks that make composition reusable without sacrificing simplicity in the process?

We want a collection of principled components, built to be discarded, separated by interfaces that are built to last.

– Zach Tellman, Elements of Clojure

First, I try to only use inversion of control when necessary. Biff’s component system is extremely small. There is no plugin autodiscovery. I try to keep IoC-related code in the project template instead of hiding it behind library functions. That way it’s easy to understand and modify.

Biff is built to be taken apart. Its main building blocks provide a small amount of customization, but beyond that, they’re meant to be discarded when they stop meeting your needs—or copied and pasted into your project, where you can modify them as you see fit. A little copy-paste is better than a lot of internal indirection. This keeps Biff adaptable and yet not overly complex.

Finally, I limit the scope. Biff’s target audience is solo developers, and I’m quite opinionated in my approach to serving that use case. Other common use cases should have their own frameworks; Biff doesn’t need to be a good fit for every situation. I include by default only things that I think will be needed in a sufficiently large fraction of Biff projects. For everything else, the best way to "extend" Biff is by writing how-to guides: extension by documentation. Preferably that documentation would also be helpful for those not using Biff.

There is a bit of a contradiction here: Biff is designed so it can be adapted to your needs, and yet if it doesn’t meet your needs you’re supposed to use something else. The distinction is that Biff’s adaptability is meant to accommodate your project as it grows. If you think that Biff might be a good fit for a new project as long as you change X, Y and Z, you might find it’s easier to simply compose the libraries yourself without Biff. You can always read Biff’s source code for inspiration. But if Biff meets your current needs as-is, you should be able to have confidence that it’ll adapt to meet your future needs as well.

Biff is itself a principled component within the larger Clojure ecosystem.

Published by Jacob O'Bryant on 8 Nov 2023