Using the “Selectors” design pattern in Node.js

As the back-end development world evolves — particularly the Node.js ecosystem — functional decomposition has become an essential strategy of system scalability and a primary driver of the industry wide shift towards micro-service architectures.

Gone are the days of monolithic Java-like object oriented design systems which make top level domain services difficult to deconstruct. While these strategies work for small, midsize, and even large-scale codebases, they simply don’t mesh well with the micro-service architecture. For companies which want to embrace small autonomous two-pizza teams, the ability to fork off highly cohesive, loosely coupled components into separate repositories, managed autonomously by DevOps style teams becomes essential.

For this, the “anemic domain model” pattern from the Domain Driven Design school of thought is to be preferred. The language between services is not objects, but simply just data. Performing operations on plain old data objects is easy to write, easy to read, easy to test, and most importantly easy to decompose and scale out. The return on investment for the boilerplate required to support object oriented designs just doesn’t make sense in the current landscape. Maintaining large class definitions, factories, aggregate validation, object relational (document) mapping artifacts, schemas, etc becomes just busy-work meant to maintain the integrity of an existing model, rather than building necessary support structures to deliver real business value.

So, where do we go from here? How do you take your existing codebase with a traditional three tier object oriented architecture, (bless you if you’re all greenfield!) and safely position it for hyper-growth across all axes? I wish I could give you a silver bullet, but all of the answers simply wouldn’t fit in one article. Follow me on medium to stay tuned with my latest perspectives! I often write about system design and architecture and I guaruntee you’ll find some useful topics if you follow.

In this article I aim to address one pattern I’ve found useful to begin the journey of melting the monolith. I call it the Selector Pattern, and it is an extremely easy and intuitive way to invert your domain logic piecemeal over time, so you can dip your toes into the world of functional programming without driving off of a cliff.

A selector is a synchronous function used to compute derived value(s) from an object. Favor stripping out property access logic into reusable. functions to facilitate reuse and testability in your service layer.

When programming with the object oriented programming paradigm you’ll often see code like the following:

class User {
  firstName = 'foo';
  lastName = 'bar';
  fullName = () => `${this.firstName} ${this.lastName}`;
}

const user = new User();

console.log(user.fullName()); // foo bar

Using selectors as a pattern within your codebase would advocate redesigning to look like the following:

class User {
  firstName = 'foo';
  lastName = 'bar';
}

// selectors.js
export const fullName = (user) => `${user.firstName} ${user.lastName}`;

// main.js
const user = new User();
console.log(fullName(user)); // foo bar

Simple right? Why would we do this?

Small functions are portable. It’s extremely easy to re-use, share, or copy functional logic across domain boundaries or services (separate git repositories) without blurring the lines of the bounded context and accessing domain object methods in foreign contexts.

In addition to architectural design, there are also additional benefits.

  • The This Keyword — The JavaScript this keyword is one of least understood parts of the language and continues to be a common source of bugs. selectors are a functional programming approach, and it’s usage dramatically reduces the need for using the this keyword in your code. Execution context is explicitly passed into the function as an argument (dependency injection via method injection) so there’s no reliance on function bindings
  • Explicit vs Implicit State — When defining selector functions (especially with typescript) creating clean, clear, readable interfaces is incredibly easy. ES6 Destructuring and default assignments make understanding the function inputs trivial, and make unit testing straight forward and easy since you can test with plain old javascript objects (POJOs). With an Object Oriented approach there’s a tremendous amount of boilerplate code required in your testing files to create objects with valid state just to test sometimes trivial functionality.
  • Decluttering the Service layer — Within a hexagonal architecture the service layer orchestrates I/O executes business logic in response to some trigger. There’s often a lot going on and to reduce the cyclomatic complexity of the code, business logic is often abstracted into separate functions either as smaller service functions (functional) or object methods (object oriented). Selectors provide a consistent place to store computations to make these large methods more readable.
  • Cache-ability — Your mileage may vary on this one — memoization on selectors in the browser is a thing because browser based applications are are relatively low-throughput, short lived applications where memory isn’t expected to grow for too long. On the server, this is not the case. Depending on your application, a selector on a long lived server side entity can be memoized or cached in memory to improve performance. Simply put, when the selector is called the computed value is stored in memory and when the selector is called again with the same input, the cached value is returned instead of recomputing. Again, in server side development this is usually a terrible idea but if you know what you’re doing it’s a useful tool to have in your toolbelt, especially for infrastructure level code.

What does the file-system look like?

src/
  CatController.js;
  CatRepository.js;
  CatService.js; // <- move 'selector' code from here
  selectors.js; // <- to here

Implementing selectors is the first step of many in decomposing your monolith, inverting your domain logic, and constructing your evolutionary architecture. Let me know if you have questions or want to know the next steps and I promise to address them in a future article.

Thank you for reading! To be the first notified when I publish a new article, sign up for my mailing list!

Ben Lugavere is a Lead Engineer and Architect at Boxed where he develops primarily using JavaScript and TypeScript. Ben writes about all things JavaScript, system design and architecture and can be found on twitter at @benlugavere.

Follow me on medium for more articles or on GitHub to see my open source contributions!