Reviewed by a tech expert

Adapter pattern in Node.js

#Sales
#Sales
#Sales
#Sales
Read this articles in:
EN
PL

In my previous posts I explained the idea of Service Objects, and how to test them. I also prepared the repository with the working code on GitHub - check it out if you feel like it.The code contains also another fascinating design pattern - adapter for Facebook API. Let's take a closer look at this bad boy.The idea behind this adapter is to abstract the implementation of the performed operation (communication with Facebook API) and isolate the rest of the code from details.Here is what the adapter looks like:// async.js

var FB = require('fb');

var config = require('config/config');



module.exports = function FacebookAdapter() {

FB.options({ version: 'v2.8' });

FB.setAccessToken(config.FB_TOKEN);



this.fetch = function (pathname, options) {

return new Promise(

function (resolve, reject) {

FB.api(

pathname,

'get',

options,

function (response) {

if (!response) {

reject('Error occurred');

}



if (response.error) {

reject(response.error.message);

}



resolve(response);

}

);

}

);

};

};

The adapter exposes one method, and for the rest of the code, it doesn't matter HOW it's done. The service objects (or whatever is supposed to communicate with Facebook API) don't care about what specific library is used, what API is, what's the configuration etc. Those are under the hood, inside the adapter.All that matters for the external world are exposed by the adapter's API. In our case, it's only one method - fetch - with two parameters.The rationale behind having the layer of adapters for 3rd parties is wide:

It's testable

Adapter is a class. You can fairly easily test it by creating an instance of it, if you'd like.

You define the API

The adapter uses fb library which exposes a multitask method - FB.api. We don't want to use it in its full power - we need only to fetch some data.The adapter defines the API that is convenient for our code.

DRY

Note that fetch takes 2 arguments while FB.api takes 4. The remaining two are defined only once in the adapter.

EASY TO CHANGE

The logic can be easily changed because it resides in one class. Perhaps it's a big deal when changing the underlying library or upgrading its version.

CLEAN CODE

The details are put aside, which makes the parts of the code that use it easier to read and understand.Would you rather work with such a code:var pathname = pageId;

var options = {

fields: 'name,about,link,location,posts.limit(5).fields(message,type)',

};

return facebookAdapter.fetch(pathname, options);

or that one?FB.api(

pageId,

'get',

{ fields: 'name,about,link,location,posts.limit(5).fields(message,type)',},

function (response) {

if (!response) {

reject('Error occurred');

}



if (response.error) {

reject(response.error.message);

}



resolve(response);

}

);

Adapter sets the boundary between the logic and the implementation details.

MANY IMPLEMENTATIONS

You can have multiple implementations of the adapters.One useful trick with the adapter is switching the implementation based on the environment.Usually, we don't want to use real network connection in your test. One way to deal with this is to write something like FakeFacebookAdapter that only simulates the real behavior. Because we have no intention to use it in production, we can add a "test-interface", helpful for our test.Here is an example implementation:// fake.js

module.exports = function FacebookAdapter() {

var that = this;

var exampleResponse = {

// whatever

};



this.requestSent = [];



this.clear = function () {

that.requestSent = [];

};



this.fetch = function (pathname, options) {

that.requestSent.push(pathname);

return new Promise(

function (resolve, reject) {

resolve(exampleResponse);

}

);

};

};

fetch method "pretends" the communication with real API. Its implementation is simple and would be ever simpler - however, we need to abide the contract between the "real" adapter and the application, and therefore use Promise.No network is involved - welcome, fast and reliable tests!This implementation allows us not to use mocks for web requests at all.To distinguish the implementation of the adapter, one has to write the code that matches the environment to implementation. This is my proposition - a singleton-like interface:// facebookAdapter.js

var Async = require('adapters/facebook/async');

var Fake = require('adapters/facebook/fake');



if (process.env.ENV == 'test') {

global.__facebookAdapter = global.__facebookAdapter || new Fake();

} else {

global.__facebookAdapter = global.__facebookAdapter || new Async();

}



module.exports = global.__facebookAdapter;

(if you wonder what the naming came from - async, fake - I usually consider also adding a synchronous implementation, sometimes useful in a development process. The names of the implementations indicate the way they work)This way the following line has a different meaning in different environments:var facebookAdapter = require('adapters/facebook/facebookAdapter');

Check the repo if you need more explanation.

SUMMARY

Use adapters! This is a design pattern with a variety of pros... and perhaps no cons.I think one has to have a really good reason not to use adapter for communicating with boundaries. And I mean a wide range of them - not only Facebook but also, for example, AWS, database, file system... maybe the API? BackendAdapter? Why not?In the next post, I'm going to explain the builder pattern and you'll see how nicely the abstraction of the adapter goes with this one.

People also ask

No items found.
Want more posts from the author?
Read more

Want to read more?

Web

A step-by-step guide to performance testing

Enhance your app's efficiency with our step-by-step guide to performance testing. Learn to optimize speed and reliability.
Web

Post-deployment application performance monitoring

If you're a member of a development team (particularly a tester), this place is perfect for you. At a certain stage of the project, you publish the application.
No results found.
There are no results with this criteria. Try changing your search.
en