Why Adapters and Facades Are Awesome

Adapters and Facades Are AwesomeIt's been a long time since I've actually been on here teaching you something; 9 months since my last actual tutorial and really useful article. Sorry about that! I'm trying to get back on track now though. You should see much more frequent posting and hopefully some very useful posts. Anyway, this tutorial is about a great idea I implemented at work that involved the Adapter and Facade patterns. After using these patterns in this way, I have a much deeper respect for them and I want you to share that repect, so let's take a look at some awesome ways/reasons to use them!

The Pitfalls of PouchDB

It all started when I got moved onto a project where we were creating an offline web application (it had already been 90% developed by the time I got switched to the project) and we were trying to fix some performance issues related to storing/retrieving/handling local data that was stored in IndexedDB using a library called PouchDB. Well in the process of debugging the issues, I came to the realization that I pretty much hate PouchDB. Don't get me wrong, it's not a horrible library (heck it's purpose is to mimic CouchDB for the front end), but it just has a few aspects that I have a hard time dealing with:

  • Callback Hell: Everything was asynchronous, and PouchDB handles this by using callbacks (more recent versions implement promises, but we weren't aware of this, and it'd require a lot of regression testing to be sure there weren't any breaking changes to PouchDB). It didn't take long for us to run into nested callbacks several levels deep because we have hierarchical data that uses IDs to refer to parent/children objects, so we're running semi-recursive calls all over the place.
  • The API is Ugly: We're not talking about the HTTP protocol, so when I see two different methods for saving data with the names of put and post, I get frustrated (they did this to mimic CouchDB). Then the method to delete something is called remove instead of delete. The API doesn't feel very consistent and it's not convenient to have two different methods for saving. Each operation also sent an err object and response object to the callback, so you always had to put if (!err)... inside every callback. The response object was also somewhat inconsistent in what it would contain. I also consider the use of callbacks as a part of the API design, which is another bit that bugs me. It's certainly a few steps up from using the native IndexedDB API, though I guess.
  • Slow: PouchDB adds some of their own functionality into the mix which can cause slowdowns. This is compounds the fact that IndexedDB itself isn't all that speedy. Alternative libraries and offline storage options could likely be faster.

Of course, we're on a deadline, so we can't just jump in and replace PouchDB with something else, because that would require us to research other solutions to test if they are easier to use and faster. Then we'd have to go throughout the application and completely change any code that used PouchDB, which was plenty.

Making Things Better With the Adapter/Facade Pattern

Our best option to fix at least some of the issues was to implement an abstraction layer that would act as a facade and adapter. It was a facade because it simplified the interface and it was an adapter because the abstraction layer would allow us to switch out the library, while still using the same API to interact with the new library. With this facade in place, we could immediately utilize the new API where we were making changes, and then later we came in and updated the rest of the application to use it. This approach improved the situation a lot:

  • Promises: Every method we created used promises instead of requiring callbacks. This removed our callback hell and helped us organize our code more logically. It also helped make things consistent with our AJAX calls which already used promises, so now everything that was asynchronous was using promises.
  • Simpler API: One save method to rule them all! The promises split errors out into separate functions instead of always needing to check for errors in each callback. Made responses more consistent and normalized. Also added convenience features: we were often trying to fetch a group of records using a list of IDs, so we instead of needing to call get for each record, we implemented the ability to pass an array of IDs to get and get an array of records back.
  • Easier to Change: The speed issues that come with PouchDB aren't fully solved yet. We were able to optimize our own code to get substantial performance increases, but we still run into performance issues. However, if we get the opportunity to do some research and find that there are faster alternatives that we'd like to implement, we should only need to go into our adapter without touching any of the other code.

Of course I can't just tell you all of these things without showing you some code examples. Here's an example of what we did with our get method to allow us to request 1 or more "documents" (rather than just one) and use promises instead of plain callbacks. I know many will argue with our choice to use jQuery for promises, but it serves our purposes and doesn't require an additional library to be loaded.

Database.prototype.get = function (ids) {
    var docs = [];
    var self = this;

    // Just get a single doc if it's not an array of IDs
    if (!_.isArray(ids)) {
        return this._getSingle(ids);
    }

    // Otherwise we need to grab all of the docs
    return _.reduce(ids, function(memo, id, index) {
        // Start a new `_getSingle` when the previous one is done
        return memo.then(function() {
            return self._getSingle(id);
        }).then(function(doc) {
            // Assign the retrieved doc to it's rightful place
            docs[index] = doc;
        });

    // Use an already-resolved promise to get the 'memo' started
    }, $.Deferred().resolve().promise()).then(function() {
        // Make sure the user gets the docs when we're done
        return docs;
    });
};

Database.prototype._getSingle = function(id) {
    var dfd = $.Deferred();

    this.db.get(id, function(err, doc) {
        if (err) {
            // Reject when we have an error
            dfd.reject(err);
        } else {
            // We got ourselves a doc! Resolve!
            dfd.resolve(doc);
        }
    });

    // Make sure the user get's a promise
    return dfd.promise();
};

Underscore's reduce function really comes in handy for performing multiple asynchronous operations sequentially. You might think it'd be better to try to have multiple _getSingle calls running in parallel, but PouchDB queues operations up anyway, so we gain nothing by doing that. Using _.reduce ends up making the code a bit difficult to wrap your head around if you're not used to the pattern, but you get used to it. It's also very nice because if one fails, the rest of them won't even bother trying to fetch.

In any case, we've made our get method more powerful and flexible while adding (some of) the benefits of promises (would be all of the benefits if we used "real" promises). We did something similar with our save method, which allowed use to save 1 or more docs -- all of which could be either new or previously saved -- without being required to know which method to call on PouchDB, and we once again added promises:

Database.prototype.save = function (doc) {
    var dfd = $.Deferred();
    var arg = doc;
    var method;

    // Determine method and arguments to use
    if (_.isArray(doc)) {
        method = "bulkDocs";
        arg = {docs: doc};
    }
    else if (doc._id) {
        method = "put";
    }
    else {
        method = "post";
    }

    // Save the doc(s) with the proper method/args
    this.db[method](arg, function (err, response) {
        if (err) {
            // Uh oh... error. REJECTED!
            dfd.reject(err);
        }
        else {
            // Yay it worked! RESOLVED!
            dfd.resolve(response);
        }
    });

    return dfd.promise();
};

In this case, PouchDB actually had its own method for dealing with multiple documents at once, so we used that, and if we only received a single doc we determined whether we needed to use put or post. Once we've determined which method to use and formatted the arguments accordingly, we go ahead and run the operation.

Other Great Opportunities for Adapters and/or Facades

One example of using adapters and facades is great, but that doesn't mean it's useful in a lot of situations, right? Well, creating an adapter for pretty much any relatively small library might be a good idea, especially if there's a decent chance that you may want/need to migrate to a new library to replace it. I actually have another interesting example that I'm looking into doing that is slightly different.

I've been using Socket.IO for a while and I love it, but there have been numerous reports of bugs and issues with it. Everyone seems to be migrating over to SockJS instead. I'm happy to move over to SockJS, except for one glaring issue: it's missing numerous features that I've come to love in Socket.IO. I can't just stick with Socket.IO (unless they fix their issues), but changing my apps to use SockJS would require a lot of refactoring and changes. The solution? Add an adapter layer that gives me Socket.IO's API on top of SockJS. This could prove to be a difficult and extensive undertaking -- possibly even more so than just changing my apps directly -- but if I can pull it off, it would be extremely useful in future projects as well.

This is an interesting example because I'm not implementing an adapter for the sake of changing the API of the library I'm already using, but instead taking the API of one library that I'm currently using and applying it to the library I'm migrating to. If you like your library's API but need to swap the library out for one reason or another, this might be a great way to make the change simpler. This also works well if you don't necessarily like the library's API, but haven't had the time to create an adapter already for the library you're currently using and utilize it throughout your code.

Conclusion

Well that's all there is to that. Adapters and Facades are mentioned in design patterns books/articles/etc. for a reason. In fact, they are the reason that many libraries exist! But we don't need to just let the library authors write them; there are numerous situations where adding an additional layer between your application and your libraries can be useful, so don't feel shy. Some frameworks, such as Aura, even create adapters around the DOM utilities in case you want to use something other than jQuery, or you decide later to switch out for something else. This is a great practice that requires a good chunk of work up front, but certainly helps keep the work down in the future if you need to make changes. Just make sure to put some thought toward your API so that it doesn't become the piece that needs to change later on. God Bless and Happy Coding!

About the Author

Author: Joe Zim

Joe Zim

Joe Zimmerman has been doing web development ever since he found an HTML book on his dad's shelf when he was 12. Since then, JavaScript has grown in popularity and he has become passionate about it. He also loves to teach others though his blog and other popular blogs. When he's not writing code, he's spending time with his wife and children and leading them in God's Word.


  • Justin Obney

    Great post sharing design pattern uses to the JS world!
    Also shouldn’t 2nd *docs* on line 9 of example save adapter be *doc*?

    • http://www.joezimjs.com Joe Zimmerman

      You’re right, thanks! I did a bit of a rewrite before writing the article, but I never got a chance to test it.

  • Sven Lito

    You might want to review your article again, it’s pretty uninformed at times. If you would have done some actual research before writing it you would have found that PouchDB actually has promise support baked in and that it’s not all that bad as you’re describing it to be.

    • http://www.joezimjs.com Joe Zimmerman

      Hey thanks for pointing this out. We’re using an older version of PouchDB which doesn’t support promises. We could update, but we’d still require the adapter for certain things (one `save` method, a `get` that supports fetching multiple IDs at once, etc.), so it is no longer necessary and doesn’t make the article any less relevant.

      I might have to note in the article that it does support promises now, though. :)

      • Sven Lito

        Yeah, leaving a note on promise support would indeed be good :)