7 Patterns to Refactor JavaScript Applications: Policy Objects

Note: This is part six (prev/next) of a seven part series that starts here.

Business logic always includes some boolean rules for data models, such as if a User is activated, or if a Student is passing. These rules interpret model attributes and return true or false, describing whether the object does or does not pass the policy.

For example, if a process sends an email to a given set of users, a Policy Object can be used to filter that set down to only those users that are email-able. A User would pass the Policy Object if they: have an email address, that email address has been verified, and they have not unsubscribed from correspondence.

Brian Helmkamp describes the potential overlap in concept between a Policy Object and a Query Object or a Service Object. He writes, “Policy Objects are similar to Service Objects, but I use the term ‘Service Object’ for write operations and ‘Policy Object’ for reads. They are also similar to Query Objects, but Query Objects focus on executing SQL to return a result set, whereas Policy Objects operate on domain models already loaded into memory.”

Example

Let’s imagine a collection of students that comprise the entire freshman student body. We want to filter out all students that are eligible to register for sports, which we define with these rules:

  1. Not suspended
  2. Not expelled
  3. Are passing all classes

Eligibility for sports enrollment isn’t a core concept of the Student model and the logic is complex, so we can extract it into a Policy Object like this:

var _ = require('underscore');
var PassingStudentPolicy = require('./passingStudentPolicy');

var SportsEligibilityPolicy = function(student) {
  this.student = student;
};

SportsEligibilityPolicy.prototype = _.extend(SportsEligibilityPolicy.prototype, {
  
  run: function() {
    return this.isNotExpelled() && this.isNotSuspended() && this.isPassing();
  },

  isNotExpelled: function() {
    return this.student.isExpelled !== true;
  },

  isNotSuspended: function() {
    return this.student.isSuspended !== true;
  },

  isPassing: function() {
    return new PassingStudentPolicy(this.student).run();
  }

});

module.exports = SportsEligibilityPolicy;

You can also see the composition opportunities for Policy Objects in the #isPassing method, which uses another Policy Object to return whether or not the student is passing. Now policies that are part of our business logic are extracted from the model itself and put into unit-testable, easy to understand, and composable components.

Testing

Unit testing a Policy Object couldn’t be simpler: bbuild an object that should either pass or fail and ensure that the Policy Object returns the right boolean value.

var _ = require('underscore')
var expect = require('chai').expect;
var Policy = require('./Policy');

describe('Policy', function(){
  it('returns true if the object passes the policy tests', function(){
    var student = {
      firstName: 'John',
      lastName: 'Smith',
      isExpelled: false,
      isSuspended: false
    };

    var eligibility = new Policy(student).run();
    expect(eligibility).to.be.true;
  });

  it('returns false if the student is expelled', function(){
    var expelledStudent = {
      firstName: 'John',
      lastName: 'Smith',
      isExpelled: true,
      isSuspended: false
    };

    var eligibility = new Policy(expelledStudent).run();
    expect(eligibility).to.be.false;
  });

  it('returns false if the student is suspended', function(){
    var suspendedStudent = {
      firstName: 'John',
      lastName: 'Smith',
      isExpelled: false,
      isSuspended: true
    };

    var eligibility = new Policy(suspendedStudent).run();
    expect(eligibility).to.be.false;
  });

});

Conclusion

Even the simplest business rules can benefit—both from clarity and repetition—with extraction into a Policy Object. This pattern is incredibly simple to introduce and test, and can give big wins to the maintainability of your application.

In the next and final post of this series, we’ll take a look at Decorators, which are great for composing complex and varying processes.

The post was originally written for the Crush & Lovely Blog in 2012 and has been lovingly brought up-to-date for Engine Yard by Michael Phillips, the original author. Special thanks to Justin Reidy and Anisha Vasandani for help with the original.

About Michael Phillips

Michael was born in Portland, Oregon but spent a decade living and working in New York City. With experience working on product teams as well as with consulting companies, he has worked on web and mobile applications ranging from greenfield startups to large corporations. He is most interested in client-side JavaScript application development, but also loves learning about engineering leadership, programming culture and best practices, and client relationship management. Michael lives in Denver, Colorado with his wife Alison and currently works for Quick Left.