Egor Panok

Full Stack JavaScript Developer

Factory Pattern in Node.JS - Case Study

July 16, 2018 - 5 min read

Factory

The Factory pattern separates object creation from its implementation.

This way it gives us more freedom and control of how we do it, meaning that we can leverage the whole bundle of instruments to create new objects – object literals, constructors, Object.Create(), closures, prototypes, static fields – but all this will be hidden from the user of our factory function.

When to Use

The Factory pattern is useful in the following situations:

  1. We need to create new different objects based on need
  2. We want to enforce encapsulation

Case Study – Connection Object In ORM Supporting Many DB Engines

Problem

So, let’s suppose that we’re creating an ORM (Object Relational Mapping) package for Node.JS that can handle different types of DB engines: MySQL, Postgres, MongoDB, CouchDB, etc. Let’s name it MagicORM.

One of the tasks will be to create a Connection object that can open and close the connection to the database. And that Connection object should be database agnostic, meaning that will put a connection string while creating the object and then it should be able to open and close the connection correctly.

Solution

The reality is that handling connections for different types of DB engines differ, that’s why we’re creating different objects for that purpose: MySQLConnection, PostgresConnection, MongoDBConnection, CouchDBConnection.

For the sake of simplicity, let’s say that all of them are created by constructor functions (which could be not the case). All of them share the same duck-typing interface – they all have two methods: open() and close(). One important aspect of the case is that our package will not expose those specific connection objects. Only main Connection object will be exposed / exported.

The user of our package will deal with Connection object, having no clue of the actual encapsulated implementation.

He will use it like this:

const connectionString = “mongodb://localhost:27017/my-app”;
const connection = MagicORM.createConnection(connectionString);
connection.open();

So, here’s when we need to use Factory pattern.

Let’s look at a possible solution of how that can be accomplished.

Please note, just to simplify it I wrote the code in two files:

  1. connection.js – it’s our module, containing MySQLConnection, PostgressConnection, MongoConnection, CouchDBConnection and createConnection factory function (which is exported from our connection.js module). But I deliberately encapsulated our connections using Module pattern, or to be more specific - IIFE modification of it – we don’t want to mess up with the scopes of different objects – do we?
  2. app.js – here is where I use our Connection module.

Ok, no more words. Let’s code it!

//file: connection.js

const MySQLConnection = (function() {
    function MySQLConnection(connectionString) {
        this.connectionString = connectionString
    }

    MySQLConnection.prototype.open = function() {
        console.log(
            `Opening ${this.constructor.name} to ${this.connectionString}`
        )
        // Implementation goes here
    }

    MySQLConnection.prototype.close = function() {
        console.log(
            `Closing ${this.constructor.name} to ${this.connectionString}`
        )
        // Implementation goes here
    }

    return MySQLConnection
})()

const PostgresConnection = (function() {
    function PostgresConnection(connectionString) {
        this.connectionString = connectionString
    }

    PostgresConnection.prototype.open = function() {
        console.log(
            `Opening ${this.constructor.name} to ${this.connectionString}`
        )
        // Implementation goes here
    }

    PostgresConnection.prototype.close = function() {
        console.log(
            `Closing ${this.constructor.name} to ${this.connectionString}`
        )
        // Implementation goes here
    }

    return PostgresConnection
})()

const MongoConnection = (function() {
    function MongoConnection(connectionString) {
        this.connectionString = connectionString
    }

    MongoConnection.prototype.open = function() {
        console.log(
            `Opening ${this.constructor.name} to ${this.connectionString}`
        )
        // Implementation goes here
    }

    MongoConnection.prototype.close = function() {
        console.log(
            `Closing ${this.constructor.name} to ${this.connectionString}`
        )
        // Implementation goes here
    }

    return MongoConnection
})()

const CouchDBConnection = (function() {
    function CouchDBConnection(connectionString) {
        this.connectionString = connectionString
    }

    CouchDBConnection.prototype.open = function() {
        console.log(
            `Opening ${this.constructor.name} to ${this.connectionString}`
        )
        // Implementation goes here
    }

    CouchDBConnection.prototype.close = function() {
        console.log(
            `Closing ${this.constructor.name} to ${this.connectionString}`
        )
        // Implementation goes here
    }

    return CouchDBConnection
})()

// Factory function
function createConnection(connectionString) {
    if (connectionString.slice(0, 5) === "mysql") {
        return new MySQLConnection(connectionString)
    } else if (connectionString.slice(0, 8) === "postgres") {
        return new PostgresConnection(connectionString)
    } else if (connectionString.slice(0, 5) === "mongo") {
        return new MongoConnection(connectionString)
    } else if (connectionString.slice(0, 5) === "couch") {
        return new CouchDBConnection(connectionString)
    } else {
        throw new Error("DB is not supported")
    }
}

module.exports = createConnection
//file: app.js
const Connection = require("./connection")

const mysqlConnectionString = "mysql://898.909.909.92/myapp"
const postgresConnectionString = "postgres://898.909.909.92/myapp"
const mongoConnectionString = "mongo://898.909.909.92/myapp"
const couchDBConnectionString = "couch://898.909.909.92/myapp"

const mysqlConnection = Connection(mysqlConnectionString)
const postgresConnection = Connection(postgresConnectionString)
const mongoConnection = Connection(mongoConnectionString)
const couchConnection = Connection(couchDBConnectionString)

mysqlConnection.open()
postgresConnection.open()
mongoConnection.open()
couchConnection.open()

mysqlConnection.close()
postgresConnection.close()
mongoConnection.close()
couchConnection.close()

So, now if we run node app.js with node we’ll see the following:

$ node app.js
Opening MySQLConnection to mysql://898.909.909.92/myapp
Opening PostgresConnection to postgres://898.909.909.92/myapp
Opening MongoConnection to mongo://898.909.909.92/myapp
Opening CouchDBConnection to couch://898.909.909.92/myapp
Closing MySQLConnection to mysql://898.909.909.92/myapp
Closing PostgresConnection to postgres://898.909.909.92/myapp
Closing MongoConnection to mongo://898.909.909.92/myapp
Closing CouchDBConnection to couch://898.909.909.92/myapp

Result

Our Connection factory now can create appropriate connection objects which we can use then to open and close connections to different types of DB engines. And by the way, as a user of Connection factory we don’t even think of the different types of connections – they are encapsulated in the Connection module. And that’s exactly what we wanted to accomplish!

Happy Coding!


Written by Egor Panok, full stack JavaScript Developer who loves building useful things. Follow him on Twitter

© 2020, Egor Panok, Full Stack JavaScript Developer