If you’re writing any non-trivial software, you’re likely to use other packages which will become dependencies for your software. Some of those dependencies will have dependencies of their own. This gets complicated enough in some languages and platforms that you can only manage those dependencies with a package manager of some kind.
This is where lesser writers would insert the obligatory “left-pad” joke, engage in language chauvinism, and maybe make meta-commentary on how “we” should do something to make software ecosystems simpler. That would be very 2016. Instead, I’m going to give a practical example of how and why you should keep your dependency tree as flat as possible.
Around the same time everyone was still laughing about left-pad, I had a side project that was… let’s call it a “content management system.” (If you know, you know.) The relevant details are that it was a web application using Django as the main framework, back when Django was on version 1.8. One of the requirements was to use Markdown to format textual content. I didn’t really know much about Django back then, but I saw there was this useful thing called Django Markdown. “Hey, this looks like exactly what I need,” I thought.
Time marched on, and in 2017 we were just about complete enough for the first production release. Except, the next LTS version of Django was 1.10, and Django Markdown didn’t support it, because whoever was maintaining Django Markdown had stopped. Thankfully, Django Markdown was open source, so I started using this fork.
The first production release of my “content management system” was in 2018. I maintained it for about 4 more years, adding features and upgrading Django to whatever the latest LTS release was. Then in 2023, I kinda decided, for various reasons discussed obliquely elsewhere, that I was going to be done with that application and do something else. So I figured I’d do one last set of updates and wipe my hands of the whole thing.
I did wipe my hands of it, and handed it off to someone else. But I never did those updates, because the guy who made the “django-markdown-app” fork decided to not support Django versions greater than 3. So I could never get my app off of Django 2.2 without breaking Markdown support or re-writing it, something that I wasn’t inclined to do on a project I was going to walk away from. I eventually told the new guy about the problem, and I think he’s fixed it since (we still talk occasionally, but I didn’t actually ask him about this).
Flash forward to this year. I decide to start this blog on a whim. Flash forward to this week, I decide I want markdown for blog formatting. I take a look at “django-markdown-app”, and the maintainer is still there, but just closes on everyone who asks if he’s going to update to a supported version of Django. I guess he really, really wants his fork to live on something that’s been dead for 2 years. Whatever.
Some google searching later, and I find this useful tutorial on how to write a template tag for Django yourself. I end up doing something very similar to this for Starbase Zebra. Yet, I feel dumb doing it, because frankly, it should have occurred to me already without reading this article, for quite a few reasons:
-
The first version of the dependency died before I even got to deploy it to production. That was a sign that I shouldn’t have picked it up in the first place. I’m a lot more cagey about picking up dependencies now; if it hasn’t had a commit in one year I make it a point to find out why.
-
I never actually studied Django-Markdown’s code to see what it really did. In retrospect, with years of production use to validate against, I didn’t use or need most of its features. Just the template tag. Several of its features seem more obviously useless to me now, like the MarkdownField model. Quite a few of them also never worked, like the wysiwyg editor it was supposed to integrate into the Django admin.
-
I actually knew enough about Python Markdown have used it directly. As part of my old app, in 2020, I wrote at least 5 different custom extensions to it, mostly to deal with legacy data and other silly stuff. And to do
strikethroughsbecause someone had asked. I actually directly lifted the strikethrough code to use in this blog, without really any edits. I guess 2020 me should have sent a message to 2017 me to not use this shit. -
I also learned enough about Django to use it directly for the bits that needed to be used directly. I wrote a bunch of template tags in my old app. It should have occured to me that that was the only thing I needed.
By not taking on “django-markdown-app” or its most recent fork, or forking it myself, I cut three dependencies down to two. More importantly, I cut out a dependency that was standing on top of two other dependencies. Having a dependency of two dependencies creates a situation in which none of the three dependencies can be updated without also updating the other two.
This gets even worse the more complex your dependency tree gets. You can end up with a case where you have two direct dependencies, with their own trees of transitive dependencies, who end up both being dependent on different versions of the same library. So you have to throw half your app away if you want to upgrade one thing. This is so bad in Java that Maven has a command to help you identify it: “mvn depedency:tree” will actually print a whole graph for you to figure it out. At my day job, I’ve spent more than a few hours staring at the output of that command to figure out why my authentication system decided to shit the bed. Oh, it’s because one of them depends on Bouncy Castle 1.46, and the other one depends on Bouncy Castle 1.60, and the API broke between them (which you can’t tell just by looking at the version number, Bouncy Castle doesn’t do SemVer. Because that would make too much sense for something called “Bouncy Castle” anyway.).
So yeah, don’t let yourself go there. Especially if whatever you are bringing in a dependency for is actually core to whatever your “business” is. Markdown was very important to my content in the old app, so it was worth making sure I integrated it directly and understood the internals of it as much as possible. Keep your dependency tree flat. Ideally, your dependency tree should be a list, e.g. 1 dimensional. Even better if you can keep them all in your head while also having them in whatever your package manager reads.