Over the last 5 months, I’ve spent time nearly every day doing web front-end engineering without using any libraries or frameworks; not even ones I had previously worked on. I’ve been using only the Web Platform. It’s something I haven’t done in a very long time. The process caused me to reflect on both the current trends in our industry as well as problems with some of my own past assumptions.
What I’ve learned through this experience would be a large and branching essay, not one I’m remotely prepared to write just yet. What I would like to do is spend some time reflecting on the high-level differences between libraries and frameworks, while sharing a few of my own thoughts and guidance on the subject. This is an old topic, but one I think is worth revisiting from time to time.
Perhaps you’ve heard of “The Hollywood Principle”? It can be stated quite simply as:
Don’t call us, we’ll call you.
This principle is used to describe how frameworks operate. A framework calls your code. It requires callbacks, hooks, lifecycles, base classes, etc. You must implement those, or the framework cannot function. This is a stark contrast to how libraries work. You call the library from your code, typically passing simple inputs and getting back results.
A similar but slightly different way to think about the difference is that you plug your code into a framework, while you plug a library into your code. The focus is on what code is in the driver’s seat. Is it your code or someone else’s?
ASIDE: Understanding these distinctions yields some interesting realizations. For example, this means that every component system is a framework. Component systems, whether ECS models, scene graphs, native systems like Xaml or SwiftUI, or web-based component models like Angular, React, and Vue, all require you to implement various callbacks in order to plug in your code. You cannot use them without implementing their callbacks, hooks, or lifecycles.
Why does this matter?
The library/framework distinction is important because a framework usually dictates your entire application architecture while a library does not and cannot. By nature, a framework tends to intentionally limit you. It usually does this for the purpose of providing some benefits in exchange for the constraints it places on your architecture, of course. A library leaves things completely open, placing no constraints on the overall architecture.
Please don’t hear me wrong. I’m not making a value judgement on libraries or frameworks here. This is not “frameworks bad, libraries good”. There are hardly any such simple maxims in software engineering. Rather, I want to point out the difference between these two things and help us all see a bit clearer what’s right in front of our own faces every day. Understanding this should help us make better decisions for our own applications, since the library/framework distinction has broad power to affect the architecture of what we’re building and its ability to evolve with the business.
Let’s do a bit of compare/contrast with libraries and frameworks. We’ll start with areas where the two have more in common and then see how they diverge as we progress.
IMPORTANT: I realize I’ve been making generalizations and I’m about to make a lot more. While there tends to be a clear distinction between libraries and frameworks, it’s still a bit of a spectrum. Always look at each technology in detail yourself to understand its characteristics and think about how you are/would use it. For example, some Web Component libraries add their own hooks or callbacks, which definitely make them more like a framework. Some don’t add any, simply helping with the platform integration, like a library. Some technologies add so many strong opinions, that they are clearly a framework, controlling your code. On the other hand, there are view engines that don’t even add a component model, but simply function as a rendering library. To complicate matters, there are technologies like React that technically can be used as a library (i.e only rendering with no components/hooks/callbacks) but typically are almost never used this way. This is why React can call itself a library, though for all intents and purposes, it is a framework. It’s not how the technology markets itself but how it’s used in context. And context is possibly the most important aspect of architecture.
Both libraries and frameworks attempt to improve ergonomics around a task or set of tasks.
Take the common scenario of building a web site…
Imagine that if every time you wanted to build a web site, you had to start with a raw socket and build up all the HTTP infrastructure. It’s not that you couldn’t do that, but it would be tremendously painful and time consuming. Once you had done it one time, you wouldn’t want to do that again. So, you’d probably put that into a little library and reuse it. Lo and behold, that is exactly what has happened on nearly every platform.
With an HTTP library you have better ergonomics when you want to build a web site. But it only goes so far. You still need to handle routing, rendering, forms, security, etc. You could create or import libraries for all those things. Each library could even have great ergonomics on its own.
Instead of using an HTTP library, you could reach for a web framework. A framework tends to tackle a broader set of ergonomics. While our HTTP library is only concerned with the smaller domain of HTTP servers, our framework is concerned with the entire experience of building a web site and attempts to stitch the various capabilities together in a seamless and coherent way.
Ultimately, both libraries and frameworks are concerned with ergonomics, but there is a large difference in vision and scope.
Libraries tend to have few opinions beyond the shape of their APIs. Your HTTP server probably has a basic configuration shape along with a few shapes for request and response objects. That’s about it. Similarly, a router might have route configuration inputs, and a URL matching API.
What’s the biggest benefit to low-opinion libraries? You can put them together using your own opinions. If you want to build up the route configuration from your folder structure, the routing library does not know or care. If you want to transform HTTP request objects in a particular way that matches your domain, before passing them to your route handler, go right ahead. Your router has no opinion on that.
On the other hand, frameworks tend to have a lot more opinions. They proactively make many decisions for you. This helps folks get started quickly with a proven set of choices and configurations (in theory). In the very least, it helps to reduce “analysis paralysis”. But it also constrains your architecture in specific ways. Your opinions cease to be and instead you subsume the opinions that have been made for you. This could work to your benefit, or it could be a problem.
TIP: Because of the highly opinionated nature of frameworks and their side effects, you would do best to establish your opinions independently of any framework. Then, look for a framework that matches your existing opinions. Cargo-culting the opinions of a framework apart from an independently developed solution tends to be destructive in the long run.
WARNING: Frameworks can be very powerful for those new to the industry, enabling them to accomplish a lot in very little time. At the same time, they can also be dangerous because they implant opinions in highly impressionable and less experienced individuals. For this reason, I always recommend that those newer to the industry hold themselves back from frameworks until after they have spent time learning their target platform. Build some things yourself first, have some of your own experiences, and form some of your own opinions. Then you have some basis on which to evaluate and adopt a framework.
DANGER: Beware the social media hucksters who want to dissuade you from thinking for yourself. “There’s no need to make choices. I’ve made all the choices for you. Just use my stack.” They have no information about what you are building, yet they are so confident they have the perfect solution for you. This is a colossal red flag. Like most grifters, these people are often very nice. So, don’t let that fool you into giving away one of the most important responsibilities and privileges you have.
Patterns and Conventions
Ergonomics and opinions are deeply connected to patterns and conventions. This is one of the primary ways many opinions play out. Both libraries and frameworks support them but in very different ways.
Libraries tend to be more flexible, allowing for partial or complete abandonment of their patterns. In fact, the patterns/conventions are usually little more than recommendations presented in documentation. Frameworks tend to strictly enforce patterns though, sometimes disallowing alternative patterns or even malfunctioning when developers take different approaches. In terms of conventions, frameworks also tend to be stricter, sometimes not working outside of the conventions.
You could say that frameworks tend to lean towards the strong end of patterns and conventions, while libraries tend to lean the other way.
Libraries are almost always highly flexible and architecture agnostic. In practice, this appears to be because most libraries start with platform primitives as their base, building their APIs up from there, and only layering their patterns and conventions as a recommendation at the end (albeit sometimes a strong recommendation).
Many frameworks, on the other hand, take an entirely different path. Because of their broad vision, they more often start with the end developer experience and then work their way down to the platform. This can make the layering less clear or non-existent, resulting in a situation where it is hard or impossible to separate the primitives from the conventions and patterns. From many framework’s perspectives, that’s not even relevant though. The philosophy is entirely different. You aren’t supposed to mess with those layers, remember?
As someone who has built convention-over-configuration libraries and frameworks for close to two decades, I can tell you there can be great advantages to convention-driven approaches. However, there can be downsides as well. So, it’s important to carefully evaluate the specifics of the technologies you are considering.
Frameworks with rigid conventions often end up enforcing a very specific architecture as a result. If this architecture is mis-aligned with your application, team, or business, then you can end up with a disaster. If cargo-cult adoption of a highly convention-driven framework happens without consideration of the architectural needs of the application, then the project is heading for a world of pain. Sadly, this happens very often.
For this reason, I usually try to choose:
- Libraries with light weight or no conventions and flexible patterns, so I can build the conventions and architecture that are best for my app/team. OR
- A framework with a highly configurable convention system that also lets me plug in my own patterns, so I can configure the system to fit my architecture exactly. OR
- A framework that supports POJO development so the framework stays completely separate from my application code and I can write my code as “vanilla” as possible.
The important take away here is that you need to be in control of your own application, not a 3rd party.
Always try to understand the problem before jumping to a solution. Always determine the solution architecture before choosing the specific technologies. Expect change and choose technologies that leave evolution open as an option. If the business shifts, a highly opinionated framework that isn’t configurable may no longer fit with how the architecture needs to shift. If the framework prevents incremental evolution, you are stuck with a very costly re-write. This could tank the business. Try to avoid architectures that set you up for a full re-write down the road. Favor architectures that allow you to incrementally shift your codebase over time in response to the inevitable change in the business and technology landscape.
Libraries tend to give you this by default. With a framework, it depends. So, figure out your architecture before making your tech stack decisions and if you decide to go with a framework, make sure it’s one that lets you stay in the driver’s seat.
I’ve talked a lot about libraries and frameworks, but not much about platforms. In my experience, the term “platform” is a bit harder to pin down. There aren’t any handy aphorisms. Personally, I tend to think of a platform as an “all-encompassing framework” that enables building a wide variety of systems and applications. Examples include the Web Platform, Windows, Mac OS, Linux, PlayStation, etc. but also curiously things like .NET, Java, and Unity.
The platform and language you choose are the most difficult things to change later. Frameworks are second, followed by libraries at a more distant third. Changeability and evolvability are critically important characteristics in a software system. So, be very careful and make sure you are confident when choosing platforms.
In our industry, we’re constantly bombarded with “the next big thing”. As technologist, we often thrive on this. But it’s important that we slow ourselves down and think carefully about each choice. What effect would it have on our systems? our architecture? our team? Identifying where something lives on the library/framework/platform spectrum can be a useful tool in the exploration of these questions.
If you enjoyed this look into libraries, frameworks, and platforms, you might want to check out my Web Component Engineering course. I’d also love it if you would subscribe to this blog, subscribe to my YouTube channel, or follow me on twitter. Your support greatly helps me continue writing and bringing this kind of content to the broader community. Thank you!