Interacting with a dependency
I want to try to leave as much code untouched and limit the amount of changes I have to make to the classes that interact with the framework
"classes that interact with the framework" will always have to be changed, because they interact with the framework.
The goal to abstracting your code is to minimize the amount of code/classes that directly touch anything belonging to the dependency.
Framework vs library
What separates a library from a framework? There's no hard and fast definition here, but developers will mostly agree on calling one thing a library and another a framework. The difference lies in the degree to which your code depends on it.
A library is a dependency, but one whose interface can be reasonably easily abstracted, allowing you to create a barrier between your code and the library, which minimizes the "collateral damage" (as you call it) when you change libraries.
A framework, however, cannot be easily abstracted. It generally requires you to write a bunch of code to integrate the framework into your codebase (or vice versa - depends how you look at it).
Abstracting a framework is going to be a massive undertaking, I doubt you're going to succeed in fully abstracting it, and I have yet to encounter a framework which could be gracefully swapped out (assuming a sufficiently large codebase which removes a full rewrite as a feasible option).
Distinguishing a framework from a library
I said there isn't a hard and fast definition of the distinction between a library and a framework, but the rule of thumb I apply in deciding whether something is a framework or a library is:
- Can it be abstracted?
- Will the abstraction allow me to continue to use the full range of features? (i.e. am I not limiting myself to only the features that I figured out how to abstract?)
- Can I abstract this without requiring knowledge of this dependency's internals?
- Is writing the abstraction a worthwhile venture on a cost-benefit-analysis?
If you answer 'yes' to every question, then it's a library, otherwise it's a framework.
Some examples here:
- I've stressed in past answers that Entity Framework is appropriately named and should not be treated as a library. Treating it like a library and trying to abstract it leads to several problems (listed in my answer) which require several solutions. While all of these problems can be solved, the combined effort to do so generally doesn't outweigh the benefit from implementing the abstraction.
- The .NET Framework (or Core) is a framework. The benefit of using it specifically hinges on using the toolkit that it provides. If you write your code in a way to not depend on these .NET specific tools, then there's no point to even using the framework to begin with.
- Though .NET tends to default to using NewtonSoft.Json for serialization, it's a library that can quite easily be swapped out for another serialization library.
- jQuery is a Javascript framework. Can its functionalities be abstracted by wrapping them in methods of your own and chaining those instead? Sure, but then you're just rewriting jQuery one method at a time. Abstracting it would defeat the purpose of using it.
- Logging libraries such as NLog and Log4Net are libraries, not frameworks. The interface is minimal and can easily be wrapped in a custom wrapper of your own.
- A calculator engine such as NCalc can be abstracted easily as you simply have define a wrapper for the operations you're going to be using. This means it's a library, not a framework.
Direct answers
What are some measures I can take to minimize the amount of code I have to change [..]?
All code that interacts with a dependency will have to be changed. Therefore you must minimize the amount of code that interacts with a given dependency.
If some of this code could be rewritten using a custom wrapper/interface of your own, then do it. All code that you rewrite to using only your own logic/wrappers/interfaces will therefore be "safe" when the dependency changes.
Think of it like radioactive waste and its impact on your workforce: people who interact with it will get sick, but people who interact with those sick people (= they are one layer removed from the waste itself) don't get sick themselves, since the sick people themselves don't become radioactive.
The aim of the game is to have as few of your employees (classes) work with the radioactive material (dependency) itself, because that's how we minimize the amount of radiation sickness (breaking changes).
What are some measures I can take [..] when I inevitably do switch frameworks [..]?
The choice of framework is highly volatile and [..] there's a good chance the framework I currently am using will be replaced
Given the tendency for frameworks (as opposed to libraries) to tightly integrate themselves in your codebase, you really should be as eager/expectant to swap out frameworks willy nilly.
That's not to say it shouldn't ever happen, but starting off under the assumption that you inevitably will change frameworks implies that you already knew beforehand that the framework isn't right for you and therefore shouldn't have been used from the get go.
What are some measures I can take [..] in order to avoid tying myself into the framework?
I want to [allow] for easy plug and play
By definition of a framework as opposed to a library (as supplied by me just now), the measures required for abstracting a framework are either incomplete, flawed, causing a cascade of other problems, or take up more effort than the problem they're trying to solve - in all cases rendering these measures moot (or even more harmful).
If your framework in question is indeed a framework and not a library (as per the above definition I supplied), then you're going to have to shift your expectations in how often you should jump from one framework to another, or how gracefully you could jump from one to the other.