In response to the growing horde of tiny packages, many experienced engineers have published opinionated frameworks, stitching packages together in a way that works well for them and many others. Boilerplate templates have become so numerous that we need sites to help us choose one. Frameworks attempt to bring unity to a group of engineers who work together to make an architecture better. They can be severely limiting, however, since they are opinionated and might make choices that aren't the best for your app. Like mini-cathedrals in the bazaar of tiny packages, they can be tightly controlled by strict gatekeepers who don't consider your use case as important as you do.
Back in my previous life as a Python developer, the Django framework was an extremely popular choice for building back ends in Python. It was so widely used that many developers got into the habit of integrating everything with the framework even when it didn't need to be. We thought of ourselves as Django developers first and Python developers second, and if a package didn't have
django- as the prefix it was regarded with suspicion. We missed out on a vibrant community of Python developers and libraries that weren't Django-specific. (Thankfully, this is no longer the case today.)
The Bazaar is Wonderful
One of the core philosophies of Python, quoted from The Zen of Python, is that "There should be one-- and preferably only one --obvious way to do it." This likely explains why the Django community was so vulnerable to thinking that our framework was obviously the one way to do it, but I prefer an environment with many strong choices so that you can pick the approach that works best for your users.
Nodejitsu posted a summarization of their Node philosophy several years ago in which they explained their perspective on tiny, decoupled packages. Here's a great quote from the article:
Coupling is poison to large codebases. It allows developers to hide improperly thought out abstractions and bad APIs by strictly defining and limiting the consumer to a fixed surface area. The idea of "loosely coupling" your components is that you can easily switch one out for another.
That's why I appreciate the tiny-module culture of the Node ecosystem so much. It has the potential to eliminate much of the pain of swapping out pieces of your architecture and building up your own personal framework that does exactly what you need.
Choose Your Own Adventure
The downside to all this choice is that it can be extremely daunting to review the multitude of packages and make a selection. The good news is that I can help! This entry is the first in a series of posts in which I'm planning to outline the many different aspects of building out your own application structure. I'll start out today with a look at the process of selecting the right packages for your project.
For many people (myself included at times), selecting a package involves asking around in chat rooms and on Twitter and going with whatever the most people say is "best". This method can lead to some success at times, but it tends to go very wrong when you're 90% done with the integration and suddenly you realize that a feature you didn't even know about 2 days ago needs to handled in a totally different way for your app. Now you have the additional work of either cleaning out the unsuccessful attempt from your codebase or forking the project to suit your needs.
Instead of making decisions based on community buzz, a better strategy is to take some time up front to judge the package based on some key criteria, and make an informed decision about whether it will work well for your project. What are those criteria? I'm glad you asked!
Evaluating A New Package
This is by no means an exhaustive list, but these are the positive and negative attributes I look for most frequently when evaluating a new module.
Purpose and Scope
The first thing to review is the purpose of the package and the scope of what it does.
- 👍 Does it clearly describe its functionality?
- 👍 Does it meet a real need in your application?
- 👍 Would it be easy to swap out with something else later?
- 👎 Does it do a whole lot more than you need?
- 👎 Is it tightly coupled with other packages?
Look for packages that clearly communicate what they do and the problem they solve. Make sure you're solving an actual problem rather than prematurely "optimizing". As I mentioned above, I prefer small, single purpose packages wherever practical but I use monoliths when there is a good reason to. Think about how you would rip it out and switch to something else later if needed.
Once you've decided that a package meets your needs and not too much more, take a look at the documentation.
- 👍 Is it easy to understand?
- 👍 Does it explain concepts as well as describe usage?
- 👍 Is it thorough, or just an afterthought?
- 👍 Is it kept up to date as the package changes?
- 👍 Are there examples?
The documentation doesn't have to be extremely verbose, but it's important for it to be thorough and up-to-date. It should cover each part of the functionality and options in a way that's easy to understand and use. It should explain the ideas behind the library and how they are implemented. If applicable, examples of common usage should be included.
If you're using the library in your application, you might have to be the one who maintains it when something breaks. Make sure there are comprehensive unit tests so that you don't step into a minefield when you need to make a change.
- 👍 Are there unit tests?
- 👍 Are they thorough?
- 👍 Are they up to date?
- 👍 Do they cover your use case?
- 👎 Are there skipped or placeholder tests?
If the package you're looking at doesn't have tests, or if the tests aren't detailed enough, then you should be prepared to write your own if you decide to use the library. Contribute the tests as a pull requests and stay on your fork until it's merged. It's better to be on an old version than to use something untested.
Before you make a final decision, it's a good idea to check out the package and try to run the tests to see if they are kept up to date. If you see several failing tests, either you're running them wrong or they weren't updated when something changed. This is a red flag.
Take some time to ensure that the package is still being maintained. If you use a project with an absent maintainer, you are effectively the new maintainer (at least inside your project).
- 👍 When was the last commit?
- 👍 When was the last version cut?
- 👍 How frequent are commits and versions?
- 👍 Does the maintainer participate in discussion?
- 👎 How many issues and pull requests are outstanding?
- 👎 How old are they?
You want a package that is still being actively maintained, and frequency of commits and releases are a reasonable way to assess that. Another measure of activity is the amount of issues that are opened, whether they are addressed by the maintainer, and whether pull requests are being merged. If you're not very confident in fixing issues with the library and it's not very active, you should probably select another.
If nobody else is using a library, it's going to be much harder to find help if something goes wrong. Take a look at the community of developers using the library and evaluate how active they are.
- 👍 How many people are using and contributing?
- 👍 Who can you go to if you need help?
- 👍 How many stars on its version control homepage? On npm?
- 👍 How many downloads over time on npm?
- 👍 Do Stack Overflow questions about it get answered?
- 👎 Are there many active divergent forks?
Think about where you would go if you run into trouble. Is there a chat community? Are people answering Stack Overflow questions? The more people are using a library, the more chance you have of finding support when you need it. Also, check to see if there are active forks of a library. It might not necessarily be a sign of trouble, but take some time to understand why the forks are there and what they are accomplishing that the original project isn't.
Finally, make sure to consider whether you can actually use the package.
- 👍 Does it work with the version of Node that you use?
- 👍 Is it on npm?
- 👍 Does it work well with the bundler you use? (webpack, browserify, etc.)
- 👍 Does it use sensible version numbering?
- 👍 Does it use a license that is compatible with your project?
- 👎 Does it have large dependencies you don't need (like jQuery)?
- 👎 Will it significantly impact your performance budget? (bandwidth, render time, etc.)
Many packages only support specific versions of Node. Some packages come with great browser support, but others only work on the server. Sometimes the package you want isn't available on the package manager you're using (though hopefully that's less common now that front end developers are increasingly embracing npm). Make sure you consider the license the project uses and whether it's compatible with your project. Watch out for chunky dependencies that you don't need, and carefully consider the size and how it will impact your front end payload.
The Bazaar Awaits
Thanks for reading! If you're interested in discussing further, you can find me on the Reactiflux chat server, on Twitter as @bkonkle, and on other fine tech communities.
(Thanks to Audrey Roy and Daniel Greenfield for inspiration from their 2011 talk at DjangoCon, Django Package Thunderdome!)