The trouble with client libraries

A Client Library is a nice way to simplify using your API, increase adoption of your service, and reduce code duplication across your company. If you’re not careful though, these time saving tools can slowly but surely become time sinks. Below are some common pitfalls that negate the benefits of a client library.

Client Library issues

Hiding your API warts

If your API has any unusual behaviour or ‘gotchas’, it can be tempting to hide those behind a clean and simple interface in your client library. Of course, this doesn’t simplify your API. It just hides that complexity until it’s too late. For example, if you add performance optimisations it’s unlikely they will be a good fit for every consumer. Ultimately, they get in the way of your consumers making their own optimisations, tailored for their needs

Doubling your interface surface area

We spend significant time and effort designing well defined and loosely coupled APIs for our services. Do we do the same for our client libraries? For developers day-to-day, they won’t be interacting with your carefully crafted API. It’s the method calls of your client library they will be dealing with.

You don’t host your code

When your API contains a bug, making a change and deploying it is entirely within your team’s control. What about your client API? Not so much.

A Client Dependency is a common dependency

There’s nothing quite as exciting as upgrading a http request library across 15 applications, or the ‘dependency jenga’ of version pinning and exclusions.

Distributed Monolith

This is what the above issues lead to. Upgrades of common libraries must be executed simultaneously, everyone must use the same language and framework, any new technology must pass stringent approvals, and in the absolute worst case, service changes regularly require changes in applications that consume it.

 

In saying this, client libraries are not fundamentally wrong. We just have to understand the costs and benefits of these libraries, and make sensible choices with them.

Recommendations

Functionality libraries over client libraries

Favour libraries that provide specific functionality over libraries that are tailored for a particular service. For example if your service provides a custom TCP protocol, don’t bake the logic of that protocol into a client library, write a generic library to use that particular protocol. This will improve the re-usability of that protocol and make it clearer to consumers what’s happening under the hood.

KISS beats DRY

Keeping things simple is a necessity for client libraries. Some degree of code duplication is a small price to pay to avoid the coupling contagion these libraries can bring.

API boilerplate encourages understanding

A great way to understand an api is to write the boilerplate code involved in making API requests and marshalling responses. The code written will be duplicated, yes, but that process forces developers to understand the API they’re calling from the beginning, and not just when things go wrong.

Only common libraries go in client libraries

A corollary of the issue described above: as any dependency in a library will get into consuming applications, this coupling must be acknowledged up-front. Only a common restricted list of dependencies should be allowed in client libraries. If you’re unwilling to bind every consumer to the same http framework or logging library, don’t.

Libraries should be a higher standard

A client library should have a tougher review process than standard code. It is not enough for the team that creates the service to review the client library, as that code will not just be part of their service. It will spread across the system, so should be held to the same standard as at least the API design.

Don’t be “Helpful”

If a consumer encounters issues interacting with your API, be it simply interacting with it, or some performance issues, resist the urge to help them out by solving their problem in your client library. You might be including their business logic in your code base. This fix is unlikely to be a good fit for every other consumer, but once it’s in the client library they’re going to get it. Fixes for these issue should be a) in your service or b) in the consumer.

Your library should be a mirror image of your API

If the structure of your client library interface isn’t very similar to your API, consumers will have two pictures of your service: the official version that’s documented for your service’s API, and the real world version provided by your client library.

Related links:

Don’t build a distributed monolith

Sharing libraries in microservices: the bad and the good