The various layers of a Stack and why upgrading is hard.
It'd be nice if folks were having conversations. But instead, it's just the usual one-line snarky takes backed by no constructive argument like "PHP is <insert expletive>". And I've seen same takes directed at literally every other stack out there.
And my point is, all stacks are, for the most part, "fine". They've all had their share of CVEs, and with LLM-driven research, there will be an explosion of more, very soon. They're all designed to be patched with mature package managers that support version-pinning. It's just that these upgrades remain the responsibilities of the developers.
What happens though is developers start a project on a given stack at a given point in time. If you look at a "stack" you have these moving parts:
The Stack Layers, from high level to low level:
- Layer 1: The Application Framework (Laravel, Symfony, Spring, .Net, Django, Rails)
- Layer 2: A wild flurry of 3rd-party Libraries
- Layer 3: The Language / Runtime (C#, Java, PHP, Python, Ruby)
- Layer 4: The Operating System (Linux, Windows)
Layer 1: So the developer starts building a SaaS application in a given Application Framework (AF). Their application right-away starts getting coupled to this framework's, call it, "API". Now right here, if you embrace an "Onion" or "Hexagonal" Architecture, you can dramatically limit this coupling, making it orders of magnitude easier to upgrade the AF down the road. Most devs have never heard of those though, couple all their code tightly to the framework, and live and die by its very paradigms which have absolutely nothing to do with their business logic. Some frameworks are biased toward this tight coupling, others make it easier on you. I have opinions with specifics but I'll keep them to myself. The key factor is you absolutely are not required to lean into any bias. It's just that most developers don't spend time thinking through this.
Layer 2: As their needs evolve, they start adding 3rd party libraries to their application. Some of these libraries are tightly coupled to the Layer 1 framework, call them "extensions of the framework". Other libraries are self-contained and don't care about the framework. But they definitely care about the language version, Layer 3. If you want to upgrade a Layer 2 Library, you'll often need to upgrade your Layer 3 language version.
Layer 3: The language/runtime cares about Layer 4, the Operating System. Upgrading it will eventually require you to upgrade Layer 4, the OS.
Layer 4: The OS ... largely avoids getting upgraded.
Say you built an application on a Layer 3 language runtime of PHP 8.1, CVE comes out and people say upgrade. So you look for a minor version patch for PHP 8.1, and none exists, as clearly indicated by PHP's widely published version support schedule. So you try to emergency-upgrade to PHP 8.2 ... HAHA.
Whoops your Layer 3 language PHP 8.2 is not compatible with your Layer 4 OS version. So you upgrade the OS. Assuming you're successful, now three of your 15 Layer 2 3rd-party Libraries break when you run your Unit Tests. (because you did write unit tests ... RIGHT? And you do TDD, RIGHT?)
But you can't update your Layer 2 Libraries because they depend on other Layer 2 Libraries which depend on a specific version of your Layer 1 framework.
FML.
These challenges exist across all stacks.
Folks are out there deriding a Layer 3 language, not even qualifying a Layer 1 framework, all of which have always had very robust version management and upgrade paths since their inception, but that nobody ever leverages.
Blaming the stack is counterproductive.
Conversations need to be had on better Software Engineering practices. Which is exactly why I started my original post the way I did, calling out TDD, Onion/Hexagonal architectures, Domain-Driven Design and the Data Mapper pattern.