Modeling Concepts Effectively in Javascript

Javascript is really really fluid and gives you many ways to express yourself:

  • Objects
  • Functions
  • Higher-order functions
  • Closures
  • Classes
  • Instances
  • Modules

So with all of this freedom, where do you even begin? When should you use a class instead of a collection of functions? When is a higher order function more appropriate than a class? Let’s explore :)

Program for people

The number one guiding principle I follow is program for people, especially the most important person: me! I’m sure if you’ve worked on a project for longer than that you’ve had an experience like:

Open file.* Omg, what idiot wrote this. *git blame* …

It was me.

This humbling experience leads to all sorts of good programming practices, like intermediate variables, terse comments, and, the subject of this article, grouping primitives into higher-level abstractions.

When you program in well-tested higher-level abstractions, you can deal with a problem on the domain level, rather than at the algorithmic level. I’m dealing with a User, not a collection of pure functions.

I like to use the simplest possible expression to express the problem at the highest level of abstraction.

Objects

Objects are generally good for storing data. The problem is that they’re loosely typed. Without strict typing, you can’t express the problem at a higher level.

Strict typing is the foundation of abstraction.

And abstraction lets you compensate for the limited size of your skull by allowing you to reason about a problem at different levels.

SO, you need to use either a function or a class to enforce an object’s type and structure.

Functions

You can use a pure function to enforce type. This is basically what redux does, with its reducers. Redux is extremely interesting to me because functions are essentially used to store data. The problem I have with it is you can’t assume the structure of an object you get from a function, which leads to a duplication of type-checking. Type-checking, again, is the foundation of abstraction, useful for reducing cognitive load and getting more shit done.

So, I can describe a user as a function:

userValidator = ({ username }) => ({ /* user stuff */ })

Then when I want to map that to a persistence layer (like a database) with a DataMapper pattern, I’d create another function

createUser = user => sql(‘INSERT’)

And then you’d use it like:

createUser(userValidator({ username: ‘tucker’ }))

But there’s no way to ensure the object passed to createUser has a valid structure, so you’d have to duplicate your type-checking efforts in createUser().

We’ll get to fixing this in a bit, but first let’s look at another problem: the `sql` dependency is hidden within the module, not injected, so it’s not easily testable. Dependency injection is the bedrock of testability. You’d dependency-inject it in functional programming with a higher-order function.

Higher-order functions

Higher-order functions are useful for customizing a function for a particular use. In particular, for injecting dependencies.

We can rewrite createUser using a higher-order function like:

createUser = sql => user => sql('INSERT')

That way you could re-use the sql instance, and define it’s connection parameters in one place without using globals:


const sqlConnection = sql('postgres://')
const validatedUser = userValidator({ username: 'tucker' })
createUser(sqlConnection)(validatedUser)

NOW, let’s say we want to create a deleteUser function.

user.js
export const userValidator() // Same as above
export const createUser = sql => user => sql('INSERT user')
export const deleteUser = sql => user => sql('DELETE user')

Now, I’ve bundled up everything here into the higher concept of a user, into a single module. Note that the module is created based on the higher concept of user and not on the algorithmic concept of data-mapper functions.

I could have just as easily created a userValidator.js module and a userDataMappers.js module. That’s bad! It leads to an explosion of modules, that I explore more deeply in Simplifying Redux Architecture.

Note that we’re also duplicating the sql requirement. This is where abstracting into a closure might be a good idea.

Closures

Closures give state to a function or set of functions.

You could rewrite the data-mapping functions above like:

user.js
const userDataMapper = sql => ({
createUser: user => sql('INSERT user'),
deleteUser: user => sql('DELETE user')
})

This is probably a good use-case for closures.

The problem I have with closures, is they’re not explicit. They’re a little too low-level and pattern-y for my tastes.

Classes

A better way, in my opinion, to express a collection of related higher-order functions is a class.

WOAH, hold on there hoss. I thought we evolved from this class-based drudgery.

Au contraire, my functional programming acquaintance. We regressed into functional programming, leaving behind all the beautiful things strict typing and object composition gives us. Object-oriented programming lets you express concepts at a higher level, so you don’t have to think about the implementation.

The gorilla / banana problem is an effect of inheritance, not object-oriented programming itself.

SO, you could re-express the closure above like

User.js
export class UserModel {
constructor(object) {
// Enforce that object follows specific structure
}
}

export class UserDataMapper {
constructor(sql) { this._sql = sql) }

createUser(user) {
// Enforce that user is a UserModel (getting all the benefits of its type-checking
this._sql('INSERT user')
}

deleteUser() {}
}

Ahhhh, classes. Now that’s nice. Lots of type-checking and higher levels of abstraction. One thing that’s jumping out at me is the use of patterns in the names of the classes. You could probably combine these in to a single User class to get even higher-level, but I personally like the names of patterns in the names of classes.

You get the benefit here of dealing with the higher-level concept of a user in a very explicit manner. The class is the DNA of the UserModel, and the userModels themselves are the organisms created from that DNA.


Object-oriented programming is, from my point of view, wayy more maintainable than functional programming. I think the current love of functional programming stems from the class-explosion-hell you get when you use inheritance. There’s nothing, ahem, inherently wrong with object-oriented programming and classes. In fact, I think they’re very good, explicit tools. The problem is with inheritance. Don’t throw out the baby with the bathwater :)

Thanks!


Follow me on Twitter @TuckerConnelly