A Kohana ORM, an N+1 on a catalog page, and a blog post about it. That was November 2014. It is December 2025, the code around me runs PHP 8.5, and I went back and read that first post.
The surface changed completely. A 2014 controller was a fat method: it read the request, validated by hand, called the ORM, formatted the response, sometimes sent an email on the way out. Types lived in docblocks and in hope. Deploy was files over FTP and a prayer. Today the same feature is a typed handler with constructor injection, validation at the boundary, a use case object, a message on a queue for the email, a trace id through the whole thing. The engine checks what a comment used to promise. I do not miss the old way.
The problems I debug did not change. They changed clothes.
Boundaries: the fat controller of 2014 is the fat “service” of 2025 that knows about HTTP, the database and the business rule at once. Consistency: the race in a checkout is the same race in a queue consumer, with better logging. Latency: N+1 survived Kohana, Doctrine proxies and now native lazy objects, because it was never an ORM bug. Upgrades: the team that skipped five years of releases pays the same price as in 2014, in larger money.
Languages and frameworks solved the mechanical problems. Typing, packaging, deployment, async, mostly solved. What remains is the part that was never mechanical. Where does this logic belong. What happens when two things are true at once. What does this request cost. Can I prove the system is fine without ssh.
Frameworks were the vocabulary of the year. The sentences were ours.
Eleven years of this blog, and the Host header is still user input.