Managing dependency on external modules in a seamless way
Have you ever worked with a project that depends on external dependency packages throughout your application code base? External packages can change at any time, which can cause issues and make refactoring difficult. To avoid this problem, we can use the Adapter Design Pattern to wrap our external dependency packages with a wrapper component that maps their interfaces back to our internal ones.
In this article, we will talk about the Adapter Pattern, and how it allows us to encapsulate external dependencies and their interfaces, shielding our application code from changes in the outside world. 
What is Adapter Design Pattern
Adapter Design Pattern is a structural design pattern that allows you to encapsulate an existing class and adapt it to a new interface. The adapter design pattern is widely used in the computer programming and software development industries. An adapter holds the component with the additional functionality not available in the original component.
To illustrate, let’s say we have a class called Car, which stores information about cars that can run on the road. We would like to add a new feature that allows us to run the car on a railway track. For a visual representation, it should work something like this.
Source: refactoring.guru 
For this feature to work correctly, we need our Car class to support this functionality. Meanwhile, no methods in our Car class can handle this feature. So how do we implement this feature? The answer is by creating an adapter class that will take all of these duties for us. Let’s see a short code snippet to understand what we are talking about.
Adapter Pattern for External Dependency Changes
Let’s see a high-level overview of the approach we are talking about. In brief, assume you have a NodeJS project and it relies heavily on an external package named `log4js` to log various events. For instance, the handleAssetPublishService is an example method where log4j is being used directly.
Similarly to the above implementation, let’s say your application has several different methods different from this, which use log4js directly and all of them are distributed into different AWS lambdas for logging.
Refactoring this implementation and wrapping the log4js package inside an Adapter of our own.
We have created a LogAdapter class that serves as a wrapper for the log4js packages. As you can see the LogAdapter class implements the ILogger interface. In the constructor, we first initiate the behavior of the log4js logger object and later implementing the method bodies. With this, the modified method will look something like the one below.
Benefits of using this Approach
You might wonder why we should go to such lengths to implement the same functionality inside an adapter class. Does it not just create extra boilerplate code inside our application?
Well, think of the approach in this way, assume as we have talked about, your application may have several lambda functions which rely upon the log4js. If there is a need to change the log4js to a different logger package or update its version, the whole implementation needs to be refactored accordingly.
Without an adapter class, we will have to manually refactor our whole application where it is being used. On the other hand, with the adapter class implementation, we now just need to make changes to our adapter class and our codebase remains unaffected by the overall changes.
One of the real-life examples is the log4j vulnerability incident . For instance, suddenly one day we hear that the log4js package has a significant security vulnerability that lets an attacker see all the log messages inside an active system. Overnight, they released a new stable version of the log4js package for the security fix. They had changed all the previous method signatures and taking new params now, making it incompatible with the current application. Without an adapter, we have to immediately change our entire application, removing the log4js implementation from every lambda, which could be a nightmare for your developer team.
But, if we have an adapter class implementation we can just refactor it inside the adapter class and our whole application will be safe from this security risk. Suppose, this scenario happens and your development team decided to change the log4js interface with another package named `pino` and `pino-pretty`, just to ensure security. All we need to do is now, change our LogAdapter class’s implementation to serve the logging functionality throughout our application.
Well, we are almost to the end of our article. Implementing this strategy inside our application, allows us to write clean and maintainable code that we can reuse without fear of changes in external dependencies making our code more robust and scalable.
It will help us to decouple our code from its dependencies to isolate it from dangerous changes that can happen outside of our circle since we can change the internal implementation at any time without having to worry about side effects throughout the application.
In the mentioned example, we have explained a use case, where we used the Adapter pattern to solve it. But depending on the problem statement, the design pattern might change to Proxy or Decorator or a similar structural design pattern that is closely similar to that of the Adapter pattern.
Thank you for your time.
Reference 2: https://refactoring.guru/design-patterns/adapter