Question

I came across with following statements while reading the Clean Code book of Robert C. Martin.

Chapter : 7 : Error Handling
Page No : 109
..In fact, wrapping third-party APIs is a best practice. When you wrap a third-party API, you minimize your dependencies upon it: You can choose to move to a different library in the future without much penalty. Wrapping also makes it easier to mock out third-party calls when you are testing your own code.

One final advantage of wrapping is that you aren’t tied to a particular vendor’s API design choices. You can define an API that you feel comfortable with.

I am quite confused about the bold part.

I am totally clueless how can we move to different library so easily if we wrap the third party library and how testing becomes easy with it ?

Was it helpful?

Solution

Imagine you have a complicated library that you depend on. Say the library exposes four different calls to its functionality, and your code base uses each of them three times.

If you use the raw vendor API in your code, then if the vendor library changes or you have to replace it, you'll have to change twelve API calls in your code base. If you had an abstraction layer between your code and their code, you'd have to change four places in the abstraction layer and none at all in your code base. You can see that this saves effort - the more effort the more calls the API offers. And most real-world APIs offer more than four entry points.

OTHER TIPS

In addition to Killian Foth's very good answer, consider some issues with testing code that uses APIs:

  • Testing is difficult because the API does something "real". Imagine you want to test your code that uses an API making a credit card payment. If each time you run your code it results in a real transaction, that is going to be a big limit on your testing.
  • Testing is difficult because you can't control the information the API returns.
    • Your code is supposed to do something special on Christmas, but the API only returns the current date. Wait until Christmas to test that feature?
    • You want to test handling a million email addresses, but these come from an external platform that only has a few test users. Add a million test users manually to that platform?
    • Your code uses a phone GPS API to get the current location and then set the language accordingly. Do you have to actually go to different countries to see if this works?

Many more examples could be created, but you get the idea. All of these issues are solved by wrapping the API. Then you can replace the actual API with a test one that returns anything you want for testing purposes.

You are right to be suspicious. I haven't read this book, but I personally strongly disagree with this advice.

This approach adds both additional code and additional abtraction.

Code can add bugs and overhead.

Abstractions have their limit, and the poor maintainer now has to understand both the library and the abstraction on top to do its job.

Abtraction is hard: either you mimic the underlying library API almost 1:1, and at this point this "abstraction" is not one anymore. Or you create your own API that will certainly mismatch with the design of the library you are using, or with the future library you will want to use in the future. In the codebase I am working on right now, there is such an abstraction over the XML parser we are using. We have a known performance issue, where the abstraction needs to keep a list of all nodes and iterate over it over and over, to satisfy the abstraction's API. It's been more than 10 years that we depend on this specific 3rd party lib, and we never had the need to change it. Removing this awful abstration is on my todo list...

Also, third party libraries being replaced are an exception and not the rule. This advice is asking you to do some work upfront, with all the disadvantages I outlined above, for a case that is unlikely to happen.

About the "mocking calls to 3rd party lib" part, I am unable to think of a case where I needed to mock a library. I may be a bit shortsighted, here.

I feel that often this practice can be used badly.

For example, say you are using the MongoDB client to access your Mongo database. Wrapping the client itself while still exposing all its functionality is pretty hard. You'll have to implement a query object and transform it to the propitiatory one etc. Its a lot of work and if you do change the client later you may find that the new one follows a completely different pattern and your interfaces and wrapper classes are no longer appropriate.

However, you do want to be able to inject mocked objects etc for testing and generally avoid making other stuff dependent on the thrid party library.

This can still be achieved if you wrap at a higher level, say following the repository pattern. By wrapping at this higher level you avoid having to deal with all the details of the 3rd party library and can expose a limited and constant interface which is relevant to your code, rather than the external code you are wrapping.

Disclaimer: I wrap the middleware of my own company...

There are multiple claims here, so I will address them separately.

Wrapping also makes it easier to mock out third-party calls when you are testing your own code.

There are several benefits here, specifically when mocking just counting the number of calls helps. Some will argue that the number of calls is not functional and therefore should not be tested; however I have seen people introducing changes which multiplied the number of calls by a factor of 10x or 100x, and the performance implications are not trivial. This is especially true when...

... some 3rd party libraries come with a lot of dependencies (network connection, complicated configuration file) and only by mocking can you easily inject errors, among other specific situations you wish to test for.

Of course, at this point, wrapping is more down to get rid of an unwanted dependency (connection, configuration file) than anything else.

..In fact, wrapping third-party APIs is a best practice. When you wrap a third-party API, you minimize your dependencies upon it: You can choose to move to a different library in the future without much penalty.

This should go hand in hand with the DRY principle and with abstraction.

When calling a 3rd party library to parse a XML document, then navigate it to extract some pieces of data, the caller:

  • is not interested in which 3rd party library you use
  • is probably not interested in the structure of the XML document

as a result, you will wrap this into some code of your own, which has the double benefits of elevating the abstraction level (it's not a XML document, it's a request to buy 4 vanilla ice creams, with caramel on top).

If you repeatedly decode XML documents, quite rapidly you will naturally fuse parts of the decoding code which comes up over and over. This is just DRY.

As a result, I find than wrapping 3rd party components come naturally: whenever I use the same call twice or thrice, I tend to extract the code in a common function, which somehow becomes a wrapper.

(unasked question about boundaries and business model)

A 3rd party API is a foreigner, it does not speak your language.

There is a boundary between your own code and the 3rd party code: you use different objects. For example, you may model your server as a string (IPv4) and integer (port) but the underlying API expects just a string (IPv4:port), or vice versa. As a result, a transformation needs to be applied when talking to this foreign API.

Ideally, you want to maximize the amount of code dealing with your own data:

  • you understand it
  • you control it (invariants are checked and meaningful for your app.)
  • ...

this requires pushing the boundaries of foreign code as far as possible.

Also, because each translation carries its own mismatch impedance risk, you want to reduce the number of places in the code where such translation occur.

In both cases, this argues for a thin layer between your application code and 3rd party APIs.

There are differing opinions voiced, but I doubt people fundamentally disagree.

The point is that wrapping APIs can be best practice.

  1. If the interface of the API is trivial, then there's no point in wrapping. Same if the interface is stable, doesn't get in the way of testing, and isn't much more complicated than what you need.
  2. If the interface is complicated and you only use a subset, use a wrapper. For example, an advanced logging framework where you always want to log things in the same format and all you ever use in the app is LogError, LogWarn, LogInfo, and LogDebug. Using a wrapper allows using a mock for testing log messages, and also allows to easily change the implementation
  3. If the interface is complicated because it has to be, and you use all of it, you won't be able to improve the interface and the wrapper will be as complicated as the original interface. That means a wrapper won't offer a benefit when moving to a different library.
Licensed under: CC-BY-SA with attribution
scroll top