Many people have probably heard the term “build or buy”, but if not, the idea is that when faced with a new requirement for your business, you can either try to accomplish it internally, or outsource the solution. This question pops up across industries, and is very common in tech.
In the tech industry, traditionally people might think about this question being asked at a higher level, for instance, a CTO who is considering whether to buy a software solution, or hire a team and build it in-house.
Over time though, I have come to find this question can also be applied to developers, even at the junior level, and that it never really goes away as you progress in your career. At these levels, the question comes up often when you’re starting on a new feature, and you can either write it yourself, or use code someone else has written, often available as an open source library.
People might not think too hard about this question when they have an open source option to “buy”, but I encourage you to keep reading to understand some factors you might want to consider the next time you’re deciding whether to build or buy, even if it’s “free”.
The first factor, time, is an obvious consideration, as building a feature can be time-intensive. First you need to design your approach, then code it, test it, maybe get it reviewed, and finally, *refactor*. Often times this is an iterative process. Published libraries such as NPM packages or Ruby gems though, are usually known for being pretty easy to get up and running quickly, following the (always beautifully documented) instructions in the ReadMe.
I would point out though that sometimes you may decide the library no longer meets your app’s needs. At that point, it can be be tricky and time consuming to untangle the library from your code. For example, if you’ve run migrations and you have to decouple your data from the library, or if you’re using the library’s methods in several places in your app, and you now have to track them all down. Using a library may not always save you time in the long run.
The next factor is complexity, which is related to time. Of course, a complex feature would take longer to build. However, I urge you to bucket it one step further and consider:
Is this feature complex, but, is it also common?
Security is a good example of this; authentication and session management can be complex, but these are very common features used by most web apps. This makes it likely that someone has already built a library, such as Devise for Rails apps, that is abstracted for most use cases and is pretty well received in the community. In that case, it might be better to let someone else do the heavy lifting for you.
On the other hand, if the feature is complex but unique, a library might not even be available to use. In that case of course, you’ll have to default to building the feature. For simpler features, even if it’s common with plenty of libraries available, it’s important here to do your due diligence on the possible solutions. You should be aware of cases where the package or gem may either overreach its boundaries and restrict your app, or when it may implement some unexpected behavior.
An example of this might be some of the soft delete gems out there used for archiving features. Some of these gems overwrite ActiveRecord’s “destroy” method, which is unexpected behavior. Someone new to the codebase, or unfamiliar with the gem, may not be aware of that behavior. If there’s a risk of damaging confusion, it might be worth writing the archiving feature into your app yourself.
When you’re building, you have complete control of the feature from start to finish, making each decision along the way to meet your requirement. This makes customization easy if your feature is particularly unique or complex.
As mentioned earlier though, open source libraries often do the work of abstraction to meet many use cases, and accept issues and build from user suggestions. These abstractions may in fact solve your use case perfectly. Additionally, some libraries are more configurable than others, allowing for greater flexibility and focusing more on one thing only.
However, relying on the abstraction a library provides could start to overreach in your app, either restricting your flexibility or inhibiting other libraries. I’ll pick on Devise again as an example: Devise touches your models, views, and controllers; that’s a lot of reach.
You may then run into situations where you’re trying to accomplish a behavior that Devise doesn’t provide for. At that point you find yourself overwriting Devise methods, or in our case, ripping it out altogether and rolling our own internal library to meet only the requirements we needed (shameless plug, check out the TokenMaster gem!).
Last, but certainly not least, you should think about the value proposition. If you think this feature could grow and evolve to become core to your product or business, it may be very valuable to build it and have complete ownership over the code.
An example might be if you build a feature to collect, manage, and analyze data in a certain way, and you ultimately provide an API for customers to work with that data. Suddenly that feature you built to do that becomes very valuable.
However, if that’s not the case, or you are looking to “buy” after the considerations above, there are several ways to find evidence of a library’s value. This is the due diligence I’ve mentioned a few times now. For example, you can evaluate:
- the social proof of a library, such as stars or downloads on GitHub or Ruby Gems / NPM;
- how well a library is maintained, tested, and documented; and
- technical specs of the how the library works.
These factors can help you decide which library would be most valuable to “buy” for your application.