| I've been in the industry for over 30 years and I absolutely get where you're coming from. It is an ever changing field and it can be exhausting. I hear this repeatedly from many coworkers (and workers from the same field). All I can give is some advice from my lengthy experience. Most of the changes happen on the "edges". Those edges are primarily three fold: First, the user interface: everything that deals with input parsing and formatting. This also includes protocol marshalling (Http, JSON, XML, ...) Second the data persistence. This involves choice of database or any other persistence. Including serialisation of your data. Any I/O really. Third, anything to do with deployment. This includes environment isolation, configuration management, process automation and so on. These the edges have nothing to do with your core application logic. You can reduce the impact of a switch to a new stack by inflating your core logic and shielding it from the rest of your stack. This is tricky and I still catch myself all the time doing it wrong. Because doing it wrong is tempting and easy. For example, consider a connection to the db: you will need a connection string. You can fetch that from an environment variable somewhere deep in your core code. But by reaching out to the system like that, you make that core piece of code dependent on the deployment strategy. Instead, you can move that part as close as possible to the entry point and then pass it down as function/class arguments. You can group similar operations together, close to the entry point. That way, if the dependent e strategy or the stack changes, your core code does not need to change. Another example is the case where your core code is dependent on a library from your stack. For example, when working with python and flask, it's easy to import "request" or "g" from the framework. But every piece of code relying on that will be tightly coupled to the framework and subject to change whenever the framework changes (either same framework with breaking changes, or framework switch). The solution is the same. Move those elements as close as possible to the entry points. In this example of flask, keep this as close as possible to your routes. But also keep your routes clean of core logic. Interact with the HTTP layer only and pass the values as arguments to your core. Finally, a similar situation, but much more sneaky and hard to spot is reliance on data types from your stack. Spotting cases where you rely too much on library modules can be done by investigating imports. But reliance on data types is harder to spot. Sticking with the flask example: the http headers are encapsulated by an object of the flask stack (werkzeug in this case). You may be careful and extract the headers in the route to follow my earlier example. But if you then pass the headers unmodified down into your core logic you will again be coupled to the stack. Instead you should extract only the values you care about and hand only those down to break the coupling. Finding the right balance where to cut the coupling is challenging. The more you cut, the better you will be shielded from changes. But the less you can benefit from pre existing implementations from the stack. Where to cut is ultimately a design decision. When done well, it will be far less frustrating to jump on the bandwagon of the latest tech. Not completely painless but far less stressful. Not least because you will have confidence that you didn't accidentally break core logic. |