On dependencies, dependency injection, and sanity

What's interesting about debacles such as the recent left-padding madness is how it can get you to re-think seemingly obvious concepts, and challenge some basic assumptions. Here's in particular a little reflection that just occurred to me on the way between the bathroom and my office, and that may seem super-obvious to some of you. It wasn't to me, so here goes, I'm sharing…

Whether you go all theoretical, call it fancy and talk about "inversion of control", or you just pragmatically do constructor injection like God intended, at this point almost everyone agrees that dependency injection is a Good Idea. You inject a contract, and the black magic inside your IoC container finds the right implementation and gives you an instance, with the right scope, lifetime, etc. Coupling is low, applications are more robust, more modular, more flexible, and unicorns flutter around happily.

Now when you look at the wonderful world of package managers (a clear progress over what the hell we thought we were doing before), what are we doing exactly? Well, we need a component that performs a certain task (in other words, it fulfils a specific contract). We then go on to ask The One True Package Manager to hand us a very specific version of a very specific package. And we do so USING A STRONG NAME. Pardon me for screaming, but can you see the parallel here?

We are "injecting" a "dependency" into our application, but it doesn't for one second occur to us that the concept is almost exactly the same. Let's go over the comparison one more time.

Dependency Injection "dependency" "injection"
Ask for a contract Ask for a strong and versioned package name
Get a managed and scoped instance with members that you can call into Get a set of namespaces with stuff in them that you can call into

So what is good practice on one side (resolution by contract) is completely out of the picture on the other. Why can't we ask for packages by contract? I don't know exactly what that would be looking like, but I have the nagging feeling that there's something to invent here. Or does something like that exist? What do you think?


  • Packages should be immutable. A contract sounds like there is more than 1 Implementation is possible. This is a bad idea for the existing package manager concept. But indeed, maybe someone can give this a twist :)

  • The problem with this is that you have to take a dependency on a contract - which must be immutable. This contract definition must exist somewhere that it is accessible by all the packages that want to implement the contract. Therefore, you haven't really solved the package dependency problem, but just transferred it to the package which defines the contract.

    Now it might be possible that you could use some sort of duck typing to ask for a package which implements certain methods, but that would seem to come with many thorny problems of its own.

  • @AAD said "A contract sounds like there is more than 1 Implementation is possible. This is a bad idea for the existing package manager concept."

    Why is it a bad idea? I can see a couple of reasons why that would be a good idea, just like it is with IoC, but I'm having trouble seeing what the downsides would be.

    @Joe said "The problem with this is that you have to take a dependency on a contract - which must be immutable"

    How is that a problem? When you take a dependency on "left-pad" version 1.0.1, you are taking a dependency on both a contract and a specific implementation. The specific version is just as immutable. There is no reason why a contract couldn't be versioned by the way.

    "you haven't really solved the package dependency problem, but just transferred it to the package which defines the contract"

    Well, that is a little like saying "you haven't solved the problem of tight coupling between your code and an injected dependency, you've just tight-coupled yourself to an interface instead". Well, yes, that's the point. In the same way that an interface is actualizing the idea of a contract by being a pure virtual class, you could imagine having a pure contract package, and separated implementation packages that declare what contract(s) they implement. That actually sounds great to me, and it's exactly what I'm suggesting in the post. It does sound a little heavy-handed too, however, and what I'm wondering is if there would be lighter-weight ways of getting the same result, which leads us to your next comment...

    "it might be possible that you could use some sort of duck typing"

    Yes, look at how interfaces are implemented in Go for example: a class can implement a contract implicitly, without even having knowledge of it. Consumer code on the other hand is requiring the contract, which is checked at compile-time. It's a saner approach to duck-typing. You could imagine a system where packages include a contract package, and the package manager can select an implementation or give you the choice of an implementation from a set of implementation packages that check against that contract, implicitly or explicitly.

    But let's talk about the benefits for a second. Let's say you need encryption in your cross-platform app, and let's imagine for a second that your dev platform doesn't give you that convenience out of the box. Instead of taking a dependency against Windows.Cryptography and Linux.Cryptography, and OSX.Cryptography, etc., that each call into the OS libraries but all expose the same contract, and then #IFDEF that all over your code, you instead take a dependency against ICryptography. Somewhere else in your build script or make file, in the same way that an app using IoC will have separate code that harvests components and fills the IoC container with implementations, you can specify the rules that will determine which implementation of the contract to link against. The benefit is that you've cleanly separated the choice of an implementation out of your code, into the build phase where it belongs. That makes it a lot easier to manage and build the versions of your app for each OS.

  • This is not a "madness" this is a "fiasco". This is one very large instance of one very large technical debt. Right now very large fire. npm just "came to be". That is: it was not architected.

    Oh boy, does this makes me happy.

    Can we please stop throwing "programming idioms/mechanisms/concepts" into the fire and (re)start with Architecture first ;)

  • I like the idea. To me it is the contract that is the key. Loosely coupling the contract from the implementations can be done by having the contract be a set of unit tests (specs). Test sets would be versionened, and they would make it safe to replace the concrete implementations for the IoC in the cloud.

    I'm in, let's do it :-)

  • Using some kind of contract-based package management system sounds great! However, I wonder about how you'd wind up with a contract that more than one package agreed on. Wouldn't you just wind up with everyone running off and writing their own contract before they then implement it?

    I have a hard time believing that you'd get multiple implementations of any non-trivial contracts where different projects would agree 100% on all the type signatures. So you'd probably wind up with a 99.9% of the contracts only having one actual implementation and it'd be an extra layer of complexity that doesn't actually provide a benefit if that were the case.

  • I don't fundamentally disagree, and most libraries will definitely map 1:1 to their contract. I did give a perfectly reasonable example in my previous comment however. And here's the thing: it's the sort of feature that spawns new scenarios just by virtue of existing. It's not that current libraries would necessarily use the feature, it's that new libraries that would be impossible or hard today would be made possible and easy by the feature being there. It would open a door and create completely new things. I think.

Comments have been disabled for this content.