Problem statement: migrating to a new library
For one reason or another, library A has been deprecated and you are tasked to migrate to library B.
The problem is that library A is spread all around your production and test codebases with direct calls to its classes without any interface in between. This poses the following challenges:
- Since library A is used directly in code, it is called an implicit dependency, which obfuscates how it is being used making it harder to migrate.
- If library A is used 100 times, and you happen to misuse its equivalent in library B, then you would need to go back and change all of them.
- What if two years from now you now need to migrate to library C?
- You may also want to swap between A and B using a feature toggle which becomes a hassle if library A is used 100 times. You can read more about feature toggles here: Don't break production when you add new features! - Feature Toggles in C# @ dev.to
Surely there is a better way to attack this problem.
Solution: implement the adapter pattern before migrating
The adapter pattern
Adapter is a structural design pattern that allows objects with different interfaces to collaborate. An adapter is a special object that converts the interface of one object so that another object can understand it. [1]
Adapter pattern image from refactoring.guru
Using the adapter pattern can help in our task by:
- Since we will be refactoring to dependency injection we are making the dependency on the library explicit, making it easier to understand where it is being used.
- We can swap between both libraries easily in our composition root.
- If we happen to add a bug while implementing the new library, we can just fix the adapter implementation instead of the hundred implicit calls in the codebase.
- If you need to migrate again to a new library, we can just implement the adapter for it. We only need to refactor to an adapter once!
Migrating to a new library
We will follow 4 straight forward steps:
- Implement the adapter pattern for the existing library.
- Change all dependencies of the existing library to the created adapter interface using dependency injection.
- Implement the same adapter interface for the new library.
- Inject the new library instead of the old library.
Example: from Newtonsoft.Json to System.Text.Json
Disclaimer: this is a purely informative/basic example. You are probably dealing with more complex situations.
You are currently using NewtonSoft.Json
in your codebase and you want to migrate to System.Text.Json
. You also want to avoid the already discussed problems, so you decide to implement an adapter first.
Following the steps above we do the following:
Create an adapter interface called
IJsonAdapter
in which we declare common methods such asSerialize
andDeserialize
. We then createNewtonsoftJsonAdapter
where we implement theIJsonAdapter
interface.
In our codebase we then inject the
IJsonAdapter
interface and use that instead ofNewtonSoft.Json
.
Finally, we inject
SystemTextJsonAdapter
instead ofNewtonsoftJsonAdapter
. Optional: we can use a feature toggle to swap between easily.
References
[1] Adapter. Refactoring.Guru. (n.d.). https://refactoring.guru/design-patterns/adapter
Top comments (0)