The framework mirage
OK, this is going to be a tough one. Strap down, even if it pisses you off at first - I wouldn’t be writing it if I didn’t think it was helpful.
Stop me if you’ve heard this one before
A programmer has something he wants to build.
He finds a framework.
It looks easy enough to get into. There’s a few tutorials available, which show step by step how to build a basic application of just the right type.
He’s happy. He feels really productive. CRUD just “drops out” of the framework’s design. It’s like it was created with his problem in mind.
Then he’s done with the tutorials. He starts plugging in his own, specific requirements. Every so often he hits a snag, but that’s OK, he can just twist the framework’s arm a bit, right?
It all goes well for a while. Then he hits a big one. The framework doesn’t do quite work the way he wants it to.
He posts a question. Maybe he doesn’t get an answer. Maybe he does, and he’s told it’s not within the benevolent dictators’ design parameters.
Crap. Oh well. He can start working around the decisions that don’t match his application. This will add some development overhead. At some point it’ll be enough that he has to consider his options.
That’s OK. He has the source. He can fork it, and do his custom stuff - he knows it well enough by now. He’ll lose the upstream fixes, unless he puts in some real effort into integrating them, but at least he doesn’t have to ditch the codebase.
This will tide his team over until the re-write, which they’ll build upon a different framework. One that looks like it better suits their needs.
That’s just the way things are, right?
If you take someone’s code, they have made some decisions for you. Sometimes the decisions work in your favor, sometimes they don’t.
There’s an alluring side to this. The main reason why a framework might feel solid at first is because someone made a lot of decisions for you - one might say such a framework is opinionated. Those decisions seem to make sense, and they get you from zero to CRUD in just a few steps.
If you happen to be building the exact type of application that the framework creators have in mind, if their way of thinking immediately clicks with you, that’ll take you a long way.
But when you try to move away just bit from the set of problems they decided to address, you realize that those choices accrete. On a solidly integrated framework, you don’t get to pick and choose. Pulling away even a single one requires considerable effort.
Most of the time, either you take it or you leave it.
The not-framework approach
Frameworks expect to have an answer to most of your fundamental questions. Can you really rely on them to do that?
Consider an alternate path.
At first glance, Clojure’s Luminus is not as solidly integrated as other alternatives. For every potential component, you get to pick and choose - and never mind PostgreSQL vs. MongoDB vs. Datomic.
- It defaults to Migratus for database migrations… but you can just pull it out and use any alternative.
- Ring provides a nice abstraction for HTTP, but which web server do you want? Immutant or HTTP Kit?
- Do you want ClojureScript support? If so, do you want Reagent or Om? And what’s the difference between those two anyway?
Even once you’ve made a decision, chances are you won’t find any step-by-step zero-to-CRUD tutorials for your specific combination.
So… how does this help again?
No, Luminus is not making any decisions for you. The very few areas where it provides a single choice, like Selmer for templating, are orthogonal to your application’s design.
Unlike the frameworks in fashion on other languages - or even some that are starting to trend for Clojure - it’s not opinionated. One could say it’s open minded.
Here’s what happens when you create a new Luminus application.
- You’ll have to tell it which options you want included. A list of the options is available on the site.
- Luminus will generate a new template from those options, every option potentially pulling in a few libraries.
- It will also include some reasonable defaults and glue code, to save you time.
While it’s happy to show you its conventions with the templates that it generates, you won’t find it making any choices for you in advance. But more importantly, at no point does it try to wrestle control of those decisions from you.
It lets you make up your own mind.
Each library Luminus includes does make some decisions internally, for obvious reasons. But these are mechanical in nature, and given that Luminus doesn’t want its basic components to be strongly fused together, you are free to swap out any particular part.
Think of it as the bevy of functions you have on Clojure. Every one of them provides a piece of independent, re-usable functionality. Yes, the internal decisions of how the functions were implemented are made for you in advance, but it’s up to you to decide how to put them together to build your application, or if even you’d rather use an external library.
Yes, this means there will be a slightly steeper learning curve. You’ll need to know what these choices mean, before you can make an informed decision.
That means that on day-1, you’ll feel less productive than how you would on an opinionated framework, because you don’t get to coast on decisions someone else has made for you.
If you are looking for day-1 productivity, it may not be for you.
I’m not. I’m looking at life time value.
After using this loosely-coupled approach on projects for a couple of years, I’ve realized two things.
I like choosing
I get to replace any single part of the system, if I find it doesn’t suit my needs.
I may not need to. Hopefully I made the right decision up-front. But if for whatever reason I didn’t, I know that no other choice is tightly integrated with it, so I can gradually pull it out and replace it - I don’t need to switch frameworks, or fork it, or live with it.
I get to change my mind.
I no longer dread updates
Up until a few months ago, YeSQL was the standard way to access SQL databases in Luminus. When YeSQL support slowed down, Dmitri Sotnikov decided to replace it with HugSQL, which follows the same approach but takes it further and is actively developed.
Want to guess how many existing Luminus projects it affected?
How can I state that with certainty?
Because it couldn’t possibly have affected a single one. Your choices are only for which libraries is your initial template generated from. After that, you are free to decide which of these do you want to update at any point, depending on if it suits you or not.
Any changes to future Luminus choices will have zero impact on you.
This means initially you’ll be taking baby steps. You may feel like an idiot as you go about figuring things out.
We all hate that. I get it.
It’s part of what makes frameworks so appealing when facing a new platform: they promise you’ll hit the ground running. Nobody likes to crawl when they’re used to just zoom by basic tasks.
But when changing contexts, we have to take baby steps first and then go on from there, because every decision we make is a trade-off.
When choosing a loosely-coupled approach, you’re saying that day-1 productivity is less important than long-term maintenance and total application lifetime productivity.
Personally, I’d rather spend time up-front understanding the choices I’m making. If I don’t have fundamental decisions made for me in advance, I have to look at what the options are, and can decide to change some pieces.
This understanding increases transparency, which in turn enhances my comprehension of the very foundations that I’ll be building upon for months or years.
Given I spend more time on application support and extension than on the first few days, it’s a trade-off I’m happy to make.