Skip to content

Instantly share code, notes, and snippets.

@gurdiga
Created February 10, 2014 07:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gurdiga/8912011 to your computer and use it in GitHub Desktop.
Save gurdiga/8912011 to your computer and use it in GitHub Desktop.

JS interface composition with promises

Inspired by Mr. Robert C. Martin’s episodes on SOLID principles that I’ve watched lately, and by the idea of “programming to interfaces” I’ve tried to come up with a schema that would allow me to have the concerns separated, but still composable.

These are a few modules from an Angular project.

So, I have an AuthenticationService module that does user account house-keeping:

// app/authentication-service/authentication-service.js
(function() {
  'use strict';

  /*jshint unused:false*/
  XO.AuthenticationService = {
    createUser: function(login, password) {},
    authenticateUser: function(login, password) {}
  };

}());

Yep, that’s an interface. :) I know formally there is no such thing in JS, and I have created it just as an experiment to see where it will get me. Aside from being useful in unit tests where I check method signature correspondence of implementations, it’s also good reference for me when in 2 years I move my project to another authentication service.

Next, an implementation:

// app/authentication-service/firebase-authentication-service.js 
(function() {
  'use strict';

  XO.FirebaseAuthenticationService = function($firebaseSimpleLogin) {
    var FirebaseAuthenticationService = {

      createUser: function(email, password) {
        var automaticLoginAfterCreate = false;

        return $firebaseSimpleLogin.$createUser(email, password, automaticLoginAfterCreate);
      },


      authenticateUser: function(email, password) {
        return $firebaseSimpleLogin.$login('password', {
          email: email,
          password: password
        });
      }
    };

    return FirebaseAuthenticationService;
  };

}());

That’s easy; nothing fancy so far. One nice thing that I want to mention about this is that it’s asynchronous. Yes, you can hardly tell it from this code, but this is actually talking to the remote Firebase service: the trick is that it’s promise-based. See those returns in methods? I’m actually returning the promise from the AngularFire’s SimpleLogin module.

Now, as soon as I tried this “for real” I found out that I want some loging, and here is where the cool part comes in. Following Mark Trostler’s “programming to interfaces” idea I mentioned earlier I came up with this:

// app/authentication-service/logged-authentication-service.js 
(function() {
  'use strict';

  XO.LoggedAuthenticationService = function(authenticationService) {
    var LoggedAuthenticationService = {
      createUser: addDebugMessage(authenticationService.createUser, 'AuthenticationService.createUser()'),
      authenticateUser: addDebugMessage(authenticationService.authenticateUser, 'AuthenticationService.authenticateUser()')
    };

    return LoggedAuthenticationService;


    function addDebugMessage(fn, message) {
      return function(email, password) {
        var promise;

        console.debug(message, email);
        promise = fn(email, password);
        promise.then(function() {
          console.debug(message + ' finished', email);
        });

        return promise;
      };
    }
  };

}());

It also implements AuthenticationService, but acts like a decorator: you pass in another implementation and it adds logging to it. It’s not exactly Mark’s implementation but at the core, it’s the same principle.

Here I found another bit of promise awesomeness: they are composable. What I’ve done here is get the promise from the wrapped module, hang a .then() call to it to do what I want to do after the original’l method is done, and then just pass it further. This lets me add logging and use this module instead of the wrapped one, and no one can tell the difference because they both implement the AuthenticationService service.

Aside from having concerns nicely separated, it’s also a breeze to test-drive, and here is how I would use it in my application bootstrap module:

// app/main.js 
(function() {
  'use strict';

  XO.main = function() {
    XO.AuthenticationService = XO.LoggedAuthenticationService(
      XO.FirebaseAuthenticationService(XO.Firebase.SimpleLogin)
    );
  };
}());

First, I initialise the XO.FirebaseAuthenticationService module passing it the XO.Firebase.SimpleLogin as a dependency, and then I add logging to it by wrapping it with XO.LoggedAuthenticationService. Similarly I will probably add benchmarking at some point.

And now I will wrap XO.AuthenticationService into an Angular service and use it in my app:

module('XO').service('AuthenticationService', function() {
  return XO.AuthenticationService;
});

Neat! B-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment