Every Day Design Pattern: Adapter
This is the second post in a series of design patterns i use (almost) daily. You will find the other posts at the bottom of this article.
On wikipedia, the adapter pattern is described like so:
the adapter pattern is a software design pattern (…) that allows the interface of an existing class to be used as another interface. It is often used to make existing classes work with others without modifying their source code.
The adapter is used to make a class ‘fit’ the interface you wish to use. Which can be helpful when you have multiple implementations of something, or if you want to migrate from one system to the other.
Multiple implementations
A great example of using adapters to have multiple implementations is Flysystem. Flysystem will let you save to different kinds of backends, without having to change your code. There is one interface you talk to, and behind that can be any adapter which saves it to a specific place, like dropbox, aws S3, or your local filesystem.
For that it has the FileSystemAdapter
interface. This is the interface every implementation has to adhere to.
Then there is a LocalFileSystemAdapter
which implements this interface. Or the FtpAdapter
, which also does that.
Now, when you want to save to FTP on your production server, and to the local file system on dev, the only thing you’ll need to change is some configuration, to make sure you get the
right class. Now when you decide to switch from FTP to google cloud, you use the GoogleCloudStorageAdapter
.
Here using adapters for the same interface allows you more flexibility, and makes upgrading easier.
Migrating between systems
For this example i want to take you back to october 2017. During that time i was working a bit on OpenCFP. OpenCFP used a deprecated library for its authentication: sentry. The idea was to replace sentry with sentinel, a new auth package by the same people that made sentry.
This was done in two steps. The first step was done by Dustin Wheeler. In this pull request, all usages of the sentry authentication where ‘hidden’ behind two interfaces. The Authentication
and AccountManagement
interfaces. This meant that the only places in the code base that knew about sentry were the implementations of those interfaces.
So now there existed the SentryAuthentication and SentryAccountManagement Only these two places knew of anything related to the authentication/account management. By first moving to this new implementation we made the migration much easier. All we now needed to do was create new implementations, based on the new sentinel package, and we were done.
The sentinel upgrade later came in november 2017 in this pull request. In the end it was still 39 files big, but keep in mind that 18 of those were tests, and another 4 were database migrations. This is of course a lot more than just two implementations of the interface. In the end a few more things were needed, to make sure everything kept working.
By using the two adapters, we could switch between sentry and sentinel, without having to change any of the code using it. All of the logic related to the third party code was in the adapter. This also means that if sentinel had to be updated, and some of the logic would change, this only needed to be done in one place.
Other posts in this series
People related to this post
- Chris Hartjes - OpenCFP creator/maintainer
- Dustin Wheeler - Created the ‘adapter’ PR in OpenCFP
- Frank de Jonge - Flysystem creator/maintainer