What is a library?
A library is a collection of implementations of behavior, written in terms of a language, that has a well-defined interface by which the behavior is invokedWikipedia
So a library is an artifact containing the implementation of some functionality but hiding it behind an API. “Host” systems can use the library to achieve the functionality by simply invoking the API instead of having to understand the implementation. Libraries are created to share code between multiple systems. e.g. In the Java world, Rulette is a rule-engine library published to a central repository. Anyone who needs to use a rule engine in their system can pull in the library from the repository and use it.
A library is different from a framework in that while your code calls library functions, a framework will typically call your code. e.g. I can write code that uses the MySQL-connector library like HikariCP to connect to a database. Spring Boot, on the other hand, is a framework that provides the structure within which I must write my code. Spring Boot invokes my code, while my code may invoke the library for connecting to the database.
There are a few basic characteristics of a well-designed library. It should be easy to understand and use. Its behavior should be easily modifiable where that was the intent of the library author. Behaviours not intended to be modifiable should be completely hidden from users.
To these ends, here are a few guidelines that have helped me in writing libraries.
Make it small
- One of the biggest problems in using a library is the number of dependent libraries it requires. LIbraries with too many dependencies are often large in size (causing the size of the host system to bloat) and may cause clashes with other libraries being used in the host system itself (complicating the host system).
- Resolving conflicting dependencies which cause errors at runtime is one of the worst debugging experiences IMO.
- The fewer dependencies a library has, the easier it will be to use.
- A library should do a specific thing in a specific way.
- While the exact definition of “specific” is up to the author, and one can make things as “configurable” as one wants, it is usually better to build a tight, opinionated library than a large, multi-faceted monster that tries to do too many things in too many different ways.
- A minimal but sufficient feature set, APIs, and configurations, all go a long way in making a library easy to select (among alternatives) and use.
Write the user’s code first
- I have written about designing from one level above before. For a library, the “one level above” is the user code which is going to use it.
- So first write a few samples of how host systems might use the library and experiment with a few different scenarios. We can run this by our actual users or even sit with them and ask them to write how they might want to use the library.
- This will give good insights into which APIs/configurations are necessary and which are not.
Identify internal components and identify the extensible ones
- With the external environment set, we need to design the internals.
- If we want library users to be able to modify certain the behaviour of certain parts, we first need to identify these “parts” so that we can design for extension.
- So having an opinion means we decide what we will allow being customized, and in the design process, we identify exactly where these customizations will go.
- These components/interfaces need to be designed with special care as they will be exposed to users and therefore hard to change later on.
Allow injection of implementations of extensible components
- Since we want a library to be usable by any type of host system, it is usually a bad idea to assume a runtime environment when writing libraries. e.g Writing an HTTP client library using annotations like autowired (spring example) will make it unusable in non-spring systems.
- But it is ok if you are deliberately writing a library to be used only in a specific environment. It’s a fair opinion to reduce complexity by assuming runtime.
- IMO, it is better to provide a builder (or other similar) pattern so that these configurations and custom implementations can be injected when the library is being set up for use.
- Provide as few ways (ideally only one) of using the library. Again, opinionated design will reduce the complexity of the library API.
An aside on platform thinking
- The guiding principles of designing libraries are very similar to the high-level principles for designing platforms.
- Libraries, like platforms, are not things by themselves. They exist to allow others to build things using them. The same principle of composability and extension can be seen at high and low levels in platforms and libraries respectively.
- The fact that a library is shipped as a “closed” artifact makes sure that even the owner of the library is forced to use it like any other user. This is the Golden Law of Platforms – eat your own dog food. If the library wants to allow any modifications to its behaviour via configuration/extension, it must provide well-defined hooks for this. This is External Programmability – the second law of building platforms.
If you liked this, subscribe to my weekly newsletter It Depends to read about software engineering and technical leadership