DEV Community

Nico Vogel
Nico Vogel

Posted on

Wrapping Libraries for Enhanced Flexibility: A Practical Approach

In the realm of software development, third-party libraries are invaluable tools for accelerating projects.
However, these libraries might not perfectly align with evolving requirements.
Enter the concept of wrapping libraries — an approach that adds a layer of control and adaptability.

In this blog, we'll delve into library wrapping, its benefits, challenges, and provide an example.
Let's explore how wrapping libraries empowers you to meet dynamic demands in the coding world.

Code can be found here: NicoVogel/logger-wrapper

What Does it Mean to Wrap Libraries You Use?

At its core, library wrapping involves crafting a bridge between your application code and external libraries.
This bridge, often referred to as an interface, encapsulates the library's functionality, and your code exclusively interacts with this interface.
Here's a breakdown of the process:

  1. Interface Creation: Design an interface that exposes only the features you require from the library.

  2. Test Your Interface: Develop a suite of tests for your interface to ensure that it behaves as expected.

  3. Selective Usage: In your application code, use the interface exclusively to interact with the library.

  4. Library Usage: The actual library is integrated into the implementation of your interface. \

Unveiling the Benefits

  1. Abstraction of Complexity: Wrapping libraries allows you to abstract away unnecessary complexity.
    Your code interacts with a streamlined interface, making it easier to work with the library's features without being overwhelmed by its intricacies.

  2. Flexible Library Swapping: Should you need to switch libraries due to changes in requirements or the library's status, your application is less affected because you isolated all the library interaction to a single point inside the interface implementation.
    This is especially useful when dealing with unmaintained libraries or when you want to avoid vendor lock-in.

  3. Test-Driven Stability: By testing your interface extensively, you ensure that your application's behavior remains consistent even if you decide to upgrade the library to a new major version.
    For this to work, you can't mock the library.

  4. Feature Enhancement: You can extend your interface with additional features that the original library might lack, tailoring the library's capabilities to your exact needs.

What Are the The Roadblocks to Be Aware Of

As always, there are no silver bullets in software engineering.

  1. Increased Code Complexity: Creating and maintaining an interface requires additional code, potentially leading to increased complexity.

  2. Abstraction Challenges: Balancing abstraction and practicality can be challenging.
    Over-abstracting may lead to convoluted code, while under-abstracting might not provide the desired benefits like better generalization, less code duplication and isolation.

Don't underestimate the second downside.
If you have never been a library/API maintainer, this can be incredibly hard.


Real-World Case: Creating a Logger Wrapper

In my current project we have specific requirements for logging and we could not find a library that met them outright.
The requirements are:

  • Logging needs to happen in frontend and backend.
  • A log level defines what should be logged
  • A logger hierarchy allows to increase/decrease logging in only parts of the application.
  • Frontend logs are send to the backend.
  • Frontend logs need to be accessible in the UI
  • The backend stores frontend logs in a file.
  • The backend logs are shown in the terminal and stored in a file.

As usual, we did not need to meet all the requirements from the start, but it just grew over time.
The general development flow was in essence:

  • We started with ts-log (after only a short search)
  • As more requirements came in, we wrapped ts-log in an interface to add more features
  • The more we added, the uglier the wrapper became and we searched again for a library. This time going with pino. We massively simplified the wrapper and there was no change at all for our application code.
  • Again, pino does not support everything we need out of the box, so we added some more capabilities in.

This should give you a taste as to how a wrapper can form naturally over time.
The code can be found here: NicoVogel/logger-wrapper

The code does not reflect the full logger we use in production, but should give you a nice idea about how to wrap a library.


Don't Wrap Everything

It's important to be selective when deciding whether to wrap a library or not.
Consider the following decision tree:

  1. Likelihood of Library Swap: If switching libraries is foreseeable, creating a wrapper is a wise move.
  2. Missing Features: If the library lacks essential features, consider creating a wrapper to address these gaps (The alternative is to not wrap it fully, but just provide a supporting class).
  3. Library Support: If the library was not updated for quite some time or is not used by many, consider creating a wrapper.
  4. Evading Vendor Lock-in: If you don't want a vendor lock-in, then you have to create a wrapper. But ask really hard why this is required (e.g. the database that we might change in the future, but never will).

Remember, in the end the decision to wrap a library depends on your project requirements and team expertise.

Conclusion

Wrapping libraries through the creation of interfaces offers a practical approach to enhance flexibility and adaptability in your software projects.
By abstracting away unnecessary complexities, tailoring functionality to your needs, and isolating potential library changes, you ensure a more stable and maintainable codebase.
However, it's important to strike a balance and avoid over-abstracting.
The decision to wrap a library should be based on careful consideration of factors such as potential library switches, missing features, and library support.

Top comments (0)