Slimming Down Fat Models

Written by Pete Corey on Oct 5, 2015.

I’ll admit it; I have a weight problem. Lately, my models have been getting fat.

Really fat.

To be honest, I didn’t see this coming. I thought I’ve been doing everything right. I’ve been keeping my views and controllers nice and thin, and I’ve been working hard to encapsulate complicated business logic into appropriately named model methods.

This might be a little awkward, but let me show you… Suppose we’ve got a Job Board application. In it, there’s a Jobs collection. We’re using dburles:collection-helpers to build a simple Jobs model. Imagine that we want to allow users to close jobs they’ve created when the position is filled. Let’s create a model method that can be called from our controller layer (our Meteor method):

Jobs.helpers({
  ...
  closeJob: () => {
    Jobs.update(this._id, {
      $set: {
        closed: true
      }
    });
  }
  ...
});

Ah, but wait. When a job closes we also need to email the job owner and congratulate them on successfully filling the position. Let’s update our model.

closeJob: () => {
  Jobs.update(this._id, {
    $set: {
      closed: true
    }
  });

  Email.send({
    to: this.getOwner().getEmail(),
    body: "Congrats on filling the position!"
  });
}

After getting some user feedback, we’ve realized that we need to congratulate the person who filled the position. Also, we should notify all of the other applicants who applied, but didn’t get the job. Also, we need to do some housekeeping and update the Organization that this job belongs to.

Let’s get back to work on our model.

closeJob: () => {
  Jobs.update(this._id, {
    $set: {
      closed: true
    }
  });

  Email.send({
    to: this.getOwner().getEmail(),
    body: "Congrats on filling the position!"
  });

  Email.send({
    to: this.getFiller().getEmail(),
    body: "Congrats on landing the job!"
  });

  this.getApplicants().forEach(applicant => {
    Email.send({
      to: applicant.getEmail(),
      body: "Sorry, but the job has been closed."
    });
  });

  this.getOrganization().closeJob();
}

Whew, that looks mostly complete!

Though technically, job owners will close a job for a variety of reasons. Maybe they’ve found someone to fill the position, or maybe they’ve just decided to take the job off of the job board for other reasons. If they use the closeJob model method, the system will send the job owner a congratulations email, and all applicants emails about the job being filled. That’s no good.

Maybe the solution is to pass in flags to determine whether we should kick off various emails:

closeJob: (congratulateOwner, congratulateFiller, notifyApplicants) => {
  Jobs.update(this._id, {
    $set: {
      closed: true
    }
  });

  if (congratulateOwner) {
    Email.send({
      to: this.getOwner().getEmail(),
      body: "Congrats on filling the position!"
    });
  }

  if (congratulateFiller) {
    Email.send({
      to: this.getFiller().getEmail(),
      body: "Congrats on landing the job!"
    });
  }

  if (notifyApplicants) {
    this.getApplicants().forEach(applicant => {
      Email.send({
        to: applicant.getEmail(),
        body: "Sorry, but the job has been closed."
      });
    });
  }

  this.getOrganization().closeJob();
}

The complexity of our model method is getting out of hand! The controller calling this method is forced to know much more about the internals of the method than it should. The bottom line is that our model is getting too fat.


So how do we shed all of this weight that’s built up on our model? We can find inspiration in more complicated system architectures and design philosophies such as Command Busses, Event Sourcing and even Domain Driven Design. All of these design patterns and philosophies make fundamental use of server-side events to segregate and organize functionality within large applications. By leveraging events in our application, we can make huge gains in terms of code cleanliness, understandability, and testability.

Let’s take a step back and think about our domain in reverse. We have a few things we want to do on every job closure. Whenever a job is closed because a suitable applicant was found, we want to send an email congratulating the job owner:

when("JobWasFilled", job => {
  Email.send({
    to: job.getOwner().getEmail(),
    body: "Congrats on filling the position!"
  });
});

We’ll also want to congratulate the user who filled the job:

when("JobWasFilled", job => {
  Email.send({
    to: job.getFiller().getEmail(),
    body: "Congrats on landing the job!"
  });
});

Any time a job posting is closed for any reason, we’ll want to notify all of our users who applied for that job:

when("JobWasClosed", job => {
  job.getApplicants().forEach(applicant => {
    Email.send({
      to: applicant.getEmail(),
      body: "Sorry, but the job has been closed."
    });
  });
});

And we also want to always update the job’s parent organization:

when("JobWasClosed", job => {
  job.getOrganization().closeJob();
});

All of these pieces of code can live in their own files on the server. We can organize them in such a way that makes immediate sense to anyone looking at the structure of our project:

server/
  listeners/
    jobWasClosed/
      closeJobOnOrganization.js
      notifyOtherApplicants.js
    jobWasFilled/
      congratulateOwner.js
      congratulateFiller.js

Now, our closeJob model method is incredibly simple, straight forward, and best of all, only has to worry about the model:

closeJob: () => {
  Jobs.update(this._id, {
    $set: {
      closed: true
    }
  });

  EventEmitter.emit("JobWasClosed", this);
}

We’ll also need to emit the JobWasFilled event from our method layer:

Meteor.methods({
  fillJob: jobId => {
    ...
    job.closeJob();
    EventEmitter.emit("JobWasFilled", job);
  }
});

Now, when a job is filled, the JobWasClosed event will be fired by the model, and the JobWasFilled event will be fired by our method layer. Our listeners will pick up on these events and do their duty. If closeJob is called through some other means, our JobWasClosed listeners will be triggered, but the JobWasFilled listeners will not. Perfect!

I feel so thin!


Server-side events are an architectural gem that can be easily incorporated into most applications to provide clean, easy decoupling of components. Events let you cleanly separate functionality, organize it into easy to understand chunks, better reason about the workings of your application, and more effectively write tests.

If you decide to start using events in your applications, I highly recommend spending a few dollars and watching this excellent Laracasts series on Commands and Domain Events. It’s targeted towards PHP developers using the Laravel framework, but the ideas are completely applicable to any software project. Jeffrey Way is an excellent teacher and masterfully presents the ideas and benefits behind server-side events.

Package Scan Web Tool

Written by Pete Corey on Sep 28, 2015.

This past week I’ve decided to put a little more love into my east5th:package-scan project. In an attempt to lower the barrier of entry for using the tool, I’ve given it a super-simple web interface. Check it out at scan.east5th.co!

The tool lets you select or drop in a Meteor versions file, which will then be compared against the list of packages with known security issues. If any matches are found, it’ll display those vulnerable package alerts on the page.

I made a conscious decision to not send versions files to the server to do the scanning. Instead, I pull the alerts.json file into the browser, along with a browserfied version of semver, and run the scan directly in on the client. This way, the users’ versions files never leave their browser.

Be sure to try it out, and more importantly, contribute if you know of any vulnerable package versions that we’re not reporting!

Exporting ES6 Classes From Meteor Packages

Written by Pete Corey on Sep 23, 2015.

To celebrate the release of Meteor 1.2 and built-in support for ES6 syntax, I’ve been playing with implementing some of my favorite Object Oriented design patterns in JavaScript. While doing this, I quickly ran into an interesting quirk of the Meteor package system. I began by writing creating a class in a Meteor package:

class CommandHandler {
  ...
}

Next, I added an export for CommandHandler in the package’s package.js file:

api.export('CommandHandler');

But interestingly, CommandHandler was undefined in my Meteor application. What’s going on here?


A quick session in the Babel REPL shows that a plain class decleration will compile down to a locally scoped function:

class CommandHandler {}
"use strict";

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var CommandHandler = function CommandHandler() {
  _classCallCheck(this, CommandHandler);
};

This type of function declaration will not be picked up by the api.export method. Only package-level variables, or variables declared without a var, let, or const, are exportable. A quick fix to our class definition would be to tie our class definition to the global scope:

CommandHandler = class CommandHandler {}

This declaration compiles down to the following JavaScript:

CommandHandler = function CommandHandler() {
  _classCallCheck(this, CommandHandler);
};

We can get more concise about it and use an unnamed class expression:

CommandHandler = class { }

Which compiles down to:

CommandHandler = (function () {
  function _class() {
    _classCallCheck(this, _class);
  }

  return _class;
})();

When using unnamed class expressions, it’s important to remember that certain features like this.constructor.name cannot be used within your class.

Notice that the CommandHandler function is now being declared on the global scope. Now api.export will happily pick up our CommandHandler declaration and we can use it within our Meteor application.

Happy ES6ing!