Rails is a Lake, Node.js is a Waterfall

Today I was working on patching our modules to accept configuration from a user importing the library (instead of just hard coded like it was before).

If you read my previous blog post, you’ll know that I’ve abandoned a complicated addition of features and refactor in favor of simply getting what we have implemented right now working in an open source fashion. Even with just a small amount of configuration to add, we still have some problems to iron out. It’s been an interesting experience for sure, and one that’s helped me gain some understanding on how Node.js applications are structured.

Take into account these three pillars of JavaScript:

  • Global state is evil because there are no namespaces.
  • Functional programming is possible because functions are first class objects.
  • Functional programming is feasible because dynamic typing allows you to easily pass around anonymous functions.

These features (and restrictions), among other qualities, have influenced Node.js application architecture.

I’m used to other web frameworks that I’ve used in the past, where configuration usually means modifying global state. Want to turn off logging? There’s usually a sort of “Logging” global object whose state you would modify. And where do you write the code to modify it? Anywhere! It’s global state! An example of a framework that acts this way is Ruby on Rails. You might configure this like this:

Application.debugger.active = true;
Application.debugger.log = function() {
  console.log('Wow, stuff is happening.');
}l

Application./* other stuff*/ = /* other config */;

Application.start(); // now run it

But Node.js doesn’t like global state. So what is done instead? Constructor functions exist for the components that comprise your application (think composition over inheritance) and the objects returned by those constructors become parameters for other constructor functions. At the end, you have an “app” object that is fully configured with everything you need and you can run it. Express.js is an example of a Node.js framework like this. You might configure this like this:

const Debugger = function(config) {
  // Return a "debugger" app component
  return {
    log() {
      console.log('Wow, stuff is happening.');
    },
    active: config.active
  };
};

// The "app" app component depends on a debugger component
const App = function(config, debugger) {
  // Return app component
  return {
    run() {
      // Lots of crazy stuff, this is the app itself
    },
    // Here's that debugger
    debugger: Debugger(config.debuggerConfig)
  };
};

// Make our application
const app = App({
  debuggerConfig: {
    active: true
  },
  other: 1,
  stuff: 2
});

app.run(); // start it up

If you used the metaphor of moving water as your configuration data, setting up a Rails application is like splashing around in a lake. The water goes to various places but it’s really all being mixed in the lake. Setting up a Node.js application is like a waterfall. The lowest level components are the points at the top of the waterfall. The objects decent down the waterfall of functions until at the very bottom they have all the state they need.

This architecture pattern has its benefits. If the components of your application are created with pure functions, it means you can be sure the creation of each component won’t influence the creation of another.

But it took until February for me to see this clearly. And now we’ve created applications for our project that involve constructor functions living who knows where in our code directories, taking who knows what as parameters to construct them. And those parameters come from constructors, which are… where? which act… how?

Yikes.

So the challenge with patching up our modules now is that there exist pieces of our application, these components, which often need to go all the way down that waterfall of constructor functions. And they used to be hard coded, so we used to just create pieces of them manually in the middle of the waterfall where we wanted, not caring about them as they passed through. But now we realize that some of them (like the tool the Logger module uses to log data to the server) need user configuration. That means they need to start at the top of the waterfall and we have to configure each part of the waterfall as it goes down to pass it through, to make sure that it reaches the bottom.

Good thing we still have time to set this all up. I definitely didn’t fully understand these aspects of software architecture in class. It’s stressful uncovering them and having to work through these problems. But it is rewarding. And I’m still confident that despite the stress, we’ll be successful with our project.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s