Library design and version conflicts

This is more of a rant about how we (not limited to, but including PHP community) have been designing our packages than a real article. So, i bet as anyone right now, when you are writing a project you are depending on bunch of packages/libraries people have created and you are using composer to manage your dependencies. This is all fine an you should keep doing that as it’s the best way to manage your dependencies and will save you the time you would spent reinventing the wheel.

Now the not so good parts. If you are a big framework your composer.json will look something like this:

Symfony’s composer.json

You can pull this many dependencies off since most of them are under your direct control and they mostly don’t depend on anything outside your own packages. Now let’s look how might an example composer.json look for a medium to large size project.

Magento2’s composer.json

So what’s changed? You might see that in this case we are depending on bunch of Zend stuff, some PHP extensions and a lot of stuff that directly under your (as a Magento maintainer) control. But we are interested in what remains — the depends you don’t control and that are from independent developers around the world.

It might not be immediately obvious what the problem here is. So let’s have an example. You want to create an application that works with reddit API and some lesser known API — maybe a local newspaper api. So what you’ll do? Jump on packagist and look for some libraries that would wrap those APIs, and you’ll find some — nice. so you’ll require them both in your project, but OH NO! Both of those packages depend on different versions of Guzzle so you get something like this pretty error message:

Example of unresolvable package version conflict upon install/update

Naturally, you’ll try to force the resolution of one of the required versions to match the other one. Easy right?

Example of forced version resolution

But sadly, it won’t work because those were two major versions and there have been a breaking change in between them on which the libraries depend, so now one of them doesn’t work properly.

Sooner or later you will arrive at this point and you’ll ask yourself, is there something i can do to fix this? Could have the authors of these libraries do a better job designing them? Well, maybe… What we really want to do is what people have been already doing when designing a system, but for some reason it’s mostly overlooked in libraries. We want to decouple the dependencies from what the code does. Ideally make the dependencies implementation easily swappable via an adapter.

Let’s look at the news API library. It will live in a package called local-news/local-news. Instead of Guzzle, we would want to depend on some ClientInterface that would ship with the library. So, now we don’t depend directly on Guzzle, but now we don’t have any real implementation of our ClientInterface. Which means that our libraries don’t do jack. So now we have to solve the problem of providing an implementation of the ClientInterface for each of our libraries.

Image for post
Image for post
The implementation of ClientInterface lives outside the local-news/local-news package

So what can we do?

Basic implementation in the package

If feasible, we could have a basic, no dependencies required, implementation of ClientInterface in the local-news/local-news package. If we do this, we don’t have to provide any adapters as the users can create these themselves if needed. This could be an option here as we are doing only GET requests to the API, so just CURL could suffice. However, this is not doable most of the time.

Image for post
Image for post
Basic implementation provided by the local-news/local-news package

Adapter inside the package

You could technically have the adapters inside the main package, and the required dependencies for them in “suggested” part of composer.json, but this just introduces another point of failure since the package doesn’t communicate it’s dependencies properly.

Image for post
Image for post
Please don’t do this

Separate package with adapters

We can have only the interfaces for the dependencies in our package and then have a separate package with the adapters that would depend on this one. So in our case, we would have only the ClientInterface without the implementation in one package, let’s say local-news/local-news-no-deps. This package will have no outside dependencies (maybe PHP version, or extensions). Then we’ll have second package with the familiar name local-news/local-news, that’ll depend on local-news/local-news-no-deps and guzzlehttp/guzzle and will implement the ClientInterface.

Image for post
Image for post
Underlying package (local-news/local-news-no-deps) doesn’t depend on guzzlehttp/guzzle

Is it worth it?

In short, yes. Design like this is already used in packages that need to provide multiple implementations of 1 interface. For example Flysystem or Omnipay. The added work you put in designing the package provides much needed flexibility for the users. You will have to release 2 packages, but for the users nothing will change, except when they’ll run into unresolvable version conflicts, they’ll be able to use the underlying package that has no dependencies. Yes, this will require work on their part to provide implementation of the interfaces your library uses, but is still preferable to not being able to use the package at all.

Written by

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store