Node - Js Design Patterns Sample Chapter
Node - Js Design Patterns Sample Chapter
Node - Js Design Patterns Sample Chapter
ee
Sa
pl
Acknowledgments
This book is the result of an amazing amount of work, knowledge, and perseverance from
many people. A big thanks goes to the entire team at Packt who made this book a reality,
from the editors to the project coordinator; in particular, I would like to thank Rebecca
You and Sriram Neelakantan for their guidance and patience during the toughest parts of
the writing process. Kudos to Alan Shaw, Joel Purra, and Afshin Mehrabani who
dedicated their time and expertise to reviewing the technical content of the book; every
comment and advice was really invaluable in bringing this work up to production quality.
This book would not exist without the efforts of so many people who made Node.js a
realityfrom the big players, who continuously inspired us, to the contributor of the
smallest module.
In these months, I also learned that a book is only possible with the support and
encouragement of all the people around you. My gratitude goes to all my friends who
heard the phrase "Today I can't, I have to work on the book" too many times; thanks to
Christophe Guillou, Zbigniew Mrowinski, Ryan Gallagher, Natalia Lopez, Ruizhi Wang,
and Davide Lionello for still talking to me. Thanks to the D4H crew, for their inspiration
and understanding, and for giving me the chance to work on a first-class product.
Thanks to all the friends back in Italy, to the legendary company of Taverna and
Centrale, to the lads of Lido Marini for always giving me a great time, laughing and
having fun. I'm sorry for not being present in the past few months.
Thanks to my Mom and Dad, and to my brother and sister, for their unconditional love
and support.
At last, you have to know that there is another person who wrote this book along with
me, that's Miriam, my girlfriend, who walked throughout this long journey with me and
supported me night and day, regardless of how difficult it was. There's nothing more one
could wish for. I send all my love and gratitude to her. Many adventures await us.
The "Node way". How to use the right point of view when approaching a Node.js
design problem. You will learn, for example, how different traditional design
patterns look in Node.js, or how to design modules that do only one thing.
Throughout the book, you will be presented with several real-life libraries and
technologies, such as LevelDb, Redis, RabbitMQ, ZMQ, Express, and many others. They
will be used to demonstrate a pattern or technique, and besides making the example more
useful, these will also give you great exposure to the Node.js ecosystem and its set
of solutions.
Whether you use or plan to use Node.js for your work, your side project, or for an open
source project, recognizing and using well-known patterns and techniques will allow you
to use a common language when sharing your code and design, and on top of that, it will
help you get a better understanding about the future of Node.js and how to make your
own contributions a part of it.
Chapter 6, Recipes, takes a problem-solution approach to show you how some common
coding and design challenges can be solved with ready-to-use solutions.
Chapter 7, Scalability and Architectural Patterns, teaches you the basic techniques and
patterns for scaling a Node.js application.
Chapter 8, Messaging and Integration Patterns, presents the most important messaging
patterns, teaching you how to build and integrate complex distributed systems using
ZMQ and AMQP.
Node.js Design
Fundamentals
Some principles and design patterns literally define the Node.js platform and its
ecosystem; the most peculiar ones are probably its asynchronous nature and its
programming style that makes heavy use of callbacks. However, there are other
fundamental components that characterize the platform; for example, its module
system, which allows multiple versions of the same dependency to coexist in an
application, and the observer pattern, implemented by the EventEmitter class,
which perfectly complements callbacks when dealing with asynchronous code.
It's therefore important that we first dive into these fundamental principles and
patterns, not only for writing correct code, but also to be able to take effective
design decisions when it comes to solving bigger and more complex problems.
Another aspect that characterizes Node.js is its philosophy. Approaching Node.js
is in fact way more than simply learning a new technology; it's also embracing a
culture and a community. We will see how this greatly influences the way we
design our applications and components, and the way they interact with those
created by the community.
In this chapter, we will learn the following topics:
The module system and its patterns: the fundamental mechanisms for
organizing code in Node.js
The observer pattern and its Node.js incarnation: the EventEmitter class
Small core
The Node.js core itself has its foundations built on a few principles; one of
these is, having the smallest set of functionality, leaving the rest to the so-called
userland (or userspace), the ecosystem of modules living outside the core. This
principle has an enormous impact on the Node.js culture, as it gives freedom to the
community to experiment and iterate fast on a broader set of solutions within the
scope of the userland modules, instead of being imposed with one slowly evolving
solution that is built into the more tightly controlled and stable core. Keeping the
core set of functionality to the bare minimum then, not only becomes convenient
in terms of maintainability, but also in terms of the positive cultural impact that it
brings on the evolution of the entire ecosystem.
Small modules
Node.js uses the concept of module as a fundamental mean to structure the code of a
program. It is the brick for creating applications and reusable libraries called packages
(a package is also frequently referred to as just module; since, usually it has one
single module as an entry point). In Node.js, one of the most evangelized principles
is to design small modules, not only in terms of code size, but most importantly in
terms of scope.
[8]
Chapter 1
This principle has its roots in the Unix philosophy, particularly in two of its
precepts, which are as follows:
"Small is beautiful."
"Make each program do one thing well."
Node.js brought these concepts to a whole new level. Along with the help of npm,
the official package manager, Node.js helps solving the dependency hell problem
by making sure that each installed package will have its own separate set of
dependencies, thus enabling a program to depend on a lot of packages without
incurring in conflicts. The Node way, in fact, involves extreme levels of reusability,
whereby applications are composed of a high number of small, well-focused
dependencies. While this can be considered unpractical or even totally unfeasible
in other platforms, in Node.js this practice is encouraged. As a consequence, it is
not rare to find npm packages containing less than 100 lines of code or exposing
only one single function.
Besides the clear advantage in terms of reusability, a small module is also considered
to be the following:
Having smaller and more focused modules empowers everyone to share or reuse
even the smallest piece of code; it's the Don't Repeat Yourself (DRY) principle
applied at a whole new level.
Another characteristic of many Node.js modules is the fact that they are created to
be used rather than extended. Locking down the internals of a module by forbidding
any possibility of an extension might sound inflexible, but it actually has the
advantage of reducing the use cases, simplifying its implementation, facilitating its
maintenance, and increasing its usability.
Chapter 1
I/O is slow
I/O is definitely the slowest among the fundamental operations of a computer.
Accessing the RAM is in the order of nanoseconds (10e-9 seconds), while accessing
data on the disk or the network is in the order of milliseconds (10e-3 seconds). For
the bandwidth, it is the same story; RAM has a transfer rate consistently in the order
of GB/s, while disk and network varies from MB/s to, optimistically, GB/s. I/O
is usually not expensive in terms of CPU, but it adds a delay between the moment
the request is sent and the moment the operation completes. On top of that, we also
have to consider the human factor; often, the input of an application comes from a
real person, for example, the click of a button or a message sent in a real-time chat
application, so the speed and frequency of I/O don't depend only on technical
aspects, and they can be many orders of magnitude slower than the disk or network.
Blocking I/O
In traditional blocking I/O programming, the function call corresponding to an
I/O request will block the execution of the thread until the operation completes.
This can go from a few milliseconds, in case of a disk access, to minutes or even
more, in case the data is generated from user actions, such as pressing a key. The
following pseudocode shows a typical blocking read performed against a socket:
//blocks the thread until the data is available
data = socket.read();
//data is available
print(data);
It is trivial to notice that a web server that is implemented using blocking I/O will
not be able to handle multiple connections in the same thread; each I/O operation
on a socket will block the processing of any other connection. For this reason, the
traditional approach to handle concurrency in web servers is to kick off a thread
or a process (or to reuse one taken from a pool) for each concurrent connection
that needs to be handled. This way, when a thread blocks for an I/O operation
it will not impact the availability of the other requests, because they are handled
in separate threads.
[ 11 ]
handle data
from A
Connection A
Connection B
Server
handle data
from A
Thread
Thread
Connection C
handle data from C
Thread
The preceding image lays emphasis on the amount of time each thread is idle,
waiting for new data to be received from the associated connection. Now, if we
also consider that any type of I/O can possibly block a request, for example, while
interacting with databases or with the filesystem, we soon realize how many times a
thread has to block in order to wait for the result of an I/O operation. Unfortunately,
a thread is not cheap in terms of system resources, it consumes memory and causes
context switches, so having a long running thread for each connection and not using
it for most of the time, is not the best compromise in terms of efficiency.
Non-blocking I/O
In addition to blocking I/O, most modern operating systems support another
mechanism to access resources, called non-blocking I/O. In this operating mode,
the system call always returns immediately without waiting for the data to be read
or written. If no results are available at the moment of the call, the function will
simply return a predefined constant, indicating that there is no data available to
return at that moment.
For example, in Unix operating systems, the fcntl() function is used to
manipulate an existing file descriptor to change its operating mode to non-blocking
(with the O_NONBLOCK flag). Once the resource is in non-blocking mode, any read
operation will fail with a return code, EAGAIN, in case the resource doesn't have any
data ready to be read.
[ 12 ]
Chapter 1
The most basic pattern for accessing this kind of non-blocking I/O is to actively
poll the resource within a loop until some actual data is returned; this is called
busy-waiting. The following pseudocode shows you how it's possible to read
from multiple resources using non-blocking I/O and a polling loop:
resources = [socketA, socketB, pipeA];
while(!resources.isEmpty()) {
for(i = 0; i < resources.length; i++)
resource = resources[i];
//try to read
var data = resource.read();
if(data === NO_DATA_AVAILABLE)
//there is no data to read at the
continue;
if(data === RESOURCE_CLOSED)
//the resource was closed, remove
resources.remove(i);
else
//some data was received, process
consumeData(data);
}
}
moment
it
You can see that, with this simple technique, it is already possible to handle
different resources in the same thread, but it's still not efficient. In fact, in the
preceding example, the loop will consume precious CPU only for iterating over
resources that are unavailable most of the time. Polling algorithms usually result
in a huge amount of wasted CPU time.
Event demultiplexing
Busy-waiting is definitely not an ideal technique for processing non-blocking
resources, but luckily, most modern operating systems provide a native mechanism
to handle concurrent, non-blocking resources in an efficient way; this mechanism
is called synchronous event demultiplexer or event notification interface. This
component collects and queues I/O events that come from a set of watched
resources, and block until new events are available to process. The following is the
pseudocode of an algorithm that uses a generic synchronous event demultiplexer to
read from two different resources:
socketA, pipeB;
watchedList.add(socketA, FOR_READ);
//[1]
watchedList.add(pipeB, FOR_READ);
while(events = demultiplexer.watch(watchedList)) {
//event loop
[ 13 ]
//[2]
Connection B
Server
handle data
from A
Connection C
[ 14 ]
handle data
from C
handle data
from B
Thread
Chapter 1
Request
I/O
5b
Execute
Handler
1
5a
Event Queue
Event Demultiplexer
Resource Operation
3
Resource Operation
Event
Handler
Event
Handler
Event
Handler
Handler
Handler
Event Loop
6
[ 15 ]
[ 16 ]
Chapter 1
A set of bindings responsible for wrapping and exposing libuv and other
low-level functionality to JavaScript.
V8, the JavaScript engine originally developed by Google for the Chrome
browser. This is one of the reasons why Node.js is so fast and efficient.
V8 is acclaimed for its revolutionary design, its speed, and for its efficient
memory management.
[ 17 ]
Finally, this is the recipe of Node.js, and the following image represents its
final architecture:
Node.js
Core Javascript API (node-core)
Bindings
libuv
V8
[ 18 ]
Chapter 1
There is nothing special here; the result is passed back to the caller using the
return instruction; this is also called direct style, and it represents the most
common way of returning a result in synchronous programming. The equivalent
continuation-passing style of the preceding function would be as follows:
function add(a, b, callback) {
callback(a + b);
}
The add() function is a synchronous CPS function, which means that it will
return a value only when the callback completes its execution. The following
code demonstrates this statement:
console.log('before');
add(1, 2, function(result) {
console.log('Result: ' + result);
});
console.log('after');
Since add() is synchronous, the previous code will trivially print the following:
before
Result: 3
after
[ 19 ]
[ 20 ]
Chapter 1
console.log(before)
2
Transfer of control
addAysnc(...)
3
Event Loop
setTimeout(...)
6
console.log(after)
When the async
operation
completes
7
callback(a + b)
10
8
9
console.log(Result:
+result)
[ 21 ]
Clearly, the callback is just used to iterate over the elements of the array, and not to
pass the result of the operation. In fact, the result is returned synchronously using a
direct style. The intent of a callback is usually clearly stated in the documentation of
the API.
Synchronous or asynchronous?
We have seen how the order of the instructions changes radically depending
on the nature of a function - synchronous or asynchronous. This has strong
repercussions on the flow of the entire application, both in correctness and efficiency.
The following is an analysis of these two paradigms and their pitfalls. In general,
what must be avoided, is creating inconsistency and confusion around the nature
of an API, as doing so can lead to a set of problems which might be very hard to
detect and reproduce. To drive our analysis, we will take as example the case of an
inconsistently asynchronous function.
An unpredictable function
One of the most dangerous situations is to have an API that behaves synchronously
under certain conditions and asynchronously under others. Let's take the following
code as an example:
var fs = require('fs');
var cache = {};
function inconsistentRead(filename, callback) {
if(cache[filename]) {
//invoked synchronously
callback(cache[filename]);
} else {
//asynchronous function
[ 22 ]
Chapter 1
fs.readFile(filename, 'utf8', function(err, data) {
cache[filename] = data;
callback(data);
});
}
}
The preceding function uses the cache variable to store the results of different
file read operations. Please bear in mind that this is just an example, it does not
have error management, and the caching logic itself is suboptimal. Besides this,
the preceding function is dangerous because it behaves asynchronously until the
cache is not setwhich is until the fs.readFile() function returns its resultsbut
it will also be synchronous for all the subsequent requests for a file already in the
cachetriggering an immediate invocation of the callback.
Unleashing Zalgo
Now, let's see how the use of an unpredictable function, such as the one that we
defined previously, can easily break an application. Consider the following code:
function createFileReader(filename) {
var listeners = [];
inconsistentRead(filename, function(value) {
listeners.forEach(function(listener) {
listener(value);
});
});
return {
onDataReady: function(listener) {
listeners.push(listener);
}
};
}
When the preceding function is invoked, it creates a new object that acts as a notifier,
allowing to set multiple listeners for a file read operation. All the listeners will be
invoked at once when the read operation completes and the data is available. The
preceding function uses our inconsistentRead() function to implement this
functionality. Let's now try to use the createFileReader() function:
var reader1 = createFileReader('data.txt');
reader1.onDataReady(function(data) {
console.log('First call data: ' + data);
[ 23 ]
As you can see, the callback of the second operation is never invoked. Let's see why:
[ 24 ]
Chapter 1
We can see that the entire function was also converted to a direct style. There is no
reason for the function to have a continuation-passing style if it is synchronous.
In fact, we can state that it is always a good practice to implement a synchronous
API using a direct style; this will eliminate any confusion around its nature and
will also be more efficient from a performance perspective.
[ 25 ]
Please bear in mind that changing an API from CPS to a direct style, or from
asynchronous to synchronous, or vice versa might also require a change to the
style of all the code using it. For example, in our case, we will have to totally
change the interface of our createFileReader() API and adapt it to work
always synchronously.
Also, using a synchronous API instead of an asynchronous one has some caveats:
A synchronous API will block the event loop and put the concurrent requests
on hold. It practically breaks the Node.js concurrency, slowing down the
whole application. We will see later in the book what this really means for
our applications.
Deferred execution
Another alternative for fixing our inconsistentRead() function is to make it purely
asynchronous. The trick here is to schedule the synchronous callback invocation
to be executed "in the future" instead of being run immediately in the same event
loop cycle. In Node.js, this is possible using process.nextTick(), which defers
the execution of a function until the next pass of the event loop. Its functioning is
very simple; it takes a callback as an argument and pushes it on the top of the event
queue, in front of any pending I/O event, and returns immediately. The callback will
then be invoked as soon as the event loop runs again.
[ 26 ]
Chapter 1
[ 27 ]
As you can see from the signature of the preceding function, the callback is always
put in last position, even in the presence of optional arguments. The motivation
for this convention is that the function call is more readable in case the callback is
defined in place.
It is a good practice to always check for the presence of an error, as not doing so will
make it harder for us to debug our code and discover the possible points of failures.
Another important convention to take into account is that the error must always be
of type Error. This means that simple strings or numbers should never be passed as
error objects.
[ 28 ]
Chapter 1
Propagating errors
Propagating errors in synchronous, direct style functions is done with the
well-known throw command, which causes the error to jump up in the call
stack until it's caught.
In asynchronous CPS however, proper error propagation is done by simply passing
the error to the next callback in the CPS chain. The typical pattern looks as follows:
var fs = require('fs');
function readJSON(filename, callback) {
fs.readFile(filename, 'utf8', function(err, data) {
var parsed;
if(err)
//propagate the error and exit the current function
return callback(err);
try {
//parse the file contents
parsed = JSON.parse(data);
} catch(err) {
//catch parsing errors
return callback(err);
}
//no errors, propagate just the data
callback(null, parsed);
});
};
The detail to notice in the previous code is how the callback is invoked when we
want to pass a valid result and when we want to propagate an error.
Uncaught exceptions
You might have seen from the readJSON() function defined previously that in
order to avoid any exception to be thrown into the fs.readFile() callback, we
put a try-catch block around JSON.parse(). Throwing inside an asynchronous
callback, in fact, will cause the exception to jump up to the event loop and never be
propagated to the next callback.
[ 29 ]
In Node.js, this is an unrecoverable state and the application will simply shut down
printing the error to the stderr interface. To demonstrate this, let's try to remove the
try-catch block from the readJSON() function defined previously:
var fs = require('fs');
function readJSONThrows(filename, callback) {
fs.readFile(filename, 'utf8', function(err, data) {
if(err)
return callback(err);
//no errors, propagate just the data
callback(null, JSON.parse(data));
});
};
This would result in the application being abruptly terminated and the following
exception being printed on the console:
SyntaxError: Unexpected token d
at Object.parse (native)
at [...]/06_uncaught_exceptions/uncaught.js:7:25
at fs.js:266:14
at Object.oncomplete (fs.js:107:15)
Now, if we look at the preceding stack trace, we will see that it starts somewhere
from the fs.js module, practically from the point at which the native API has
completed reading and returned its result back to the fs.readFile() function, via
the event loop. This clearly shows us that the exception traveled from our callback
into the stack that we saw, and then straight into the event loop, where it's finally
caught and thrown in the console.
[ 30 ]
Chapter 1
The preceding catch statement will never receive the JSON parsing exception,
as it will travel back to the stack in which the exception was thrown, and we just
saw that the stack ends up in the event loop and not with the function that triggers
the asynchronous operation.
We already said that the application is aborted the moment an exception reaches
the event loop; however, we still have a last chance to perform some cleanup
or logging before the application terminates. In fact, when this happens, Node.js
emits a special event called uncaughtException just before exiting the process.
The following code shows a sample use case:
process.on('uncaughtException', function(err){
console.error('This will catch at last the ' +
'JSON parsing exception: ' + err.message);
//without this, the application would continue
process.exit(1);
});
[ 31 ]
[ 32 ]
Chapter 1
The source code of a module is essentially wrapped into a function, as it was for
the revealing module pattern. The difference here is that we pass a list of variables
to the module, in particular: module, exports, and require. Make a note of how
the exports argument of the wrapping function is initialized with the contents of
module.exports, as we will talk about this later.
Please bear in mind that this is only an example and you will rarely
need to evaluate some source code in a real application. Features such
as eval() or the functions of the vm module (http://nodejs.org/
api/vm.html) can be easily used in the wrong way or with the wrong
input, thus opening a system to code injection attacks. They should
always be used with extreme care or avoided altogether.
Let's now see what these variables contain by implementing our require() function:
var require = function(moduleName) {
console.log('Require invoked for module: ' + moduleName);
var id = require.resolve(moduleName);
//[1]
if(require.cache[id]) {
//[2]
return require.cache[id].exports;
}
//module metadata
var module = {
exports: {},
id: id
};
//[3]
[ 33 ]
//[4]
//[5]
//[6]
};
require.cache = {};
require.resolve = function(moduleName) {
/* resolve a full module id from the moduleName */
}
The preceding function simulates the behavior of the original require() function
of Node.js, which is used to load a module. Of course, this is just for educative
purposes and it does not accurately or completely reflect the internal behavior of
the real require() function, but it's great to understand the internals of the Node.js
module system, how a module is defined, and loaded. What our homemade module
system does is explained as follows:
1. A module name is accepted as input and the very first thing that we do is
resolve the full path of the module, which we call id. This task is delegated
to require.resolve(), which implements a specific resolving algorithm
(we will talk about it later).
2. If the module was already loaded in the past, it should be available in the
cache. In this case, we just return it immediately.
3. If the module was not yet loaded, we set up the environment for the first
load. In particular, we create a module object that contains an exports
property initialized with an empty object literal. This property will be used
by the code of the module to export any public API.
4. The module object is cached.
5. The module source code is read from its file and the code is evaluated, as we
have seen before. We provide to the module, the module object that we just
created, and a reference to the require() function. The module exports its
public API by manipulating or replacing the module.exports object.
6. Finally, the content of module.exports, which represents the public API of
the module, is returned to the caller.
As we see, there is nothing magical behind the workings of the Node.js module
system; the trick is all in the wrapper we create around a module's source code
and the artificial environment in which we run it.
[ 34 ]
Chapter 1
Defining a module
By looking at how our homemade require() function works, we should now know
how to define a module. The following code gives us an example:
//load another dependency
var dependency = require('./anotherModule');
//a private function
function log() {
console.log('Well done ' + dependency.username);
}
//the API to be exported for public use
module.exports.run = function() {
log();
};
Defining globals
Even if all the variables and functions that are declared in a module are defined
in its local scope, it is still possible to define a global variable. In fact, the module
system exposes a special variable called global, which can be used for this
purpose. Everything that is assigned to this variable will end up automatically
in the global scope.
Please note that polluting the global scope is considered a bad practice
and nullifies the advantage of having a module system. So, use it only
if you really know what you are doing.
module.exports vs exports
For many developers who are not yet familiar with Node.js, a common source of
confusion is the difference between using exports and module.exports to expose
a public API. The code of our homemade require function should again clear
any doubt. The variable exports is just a reference to the initial value of module.
exports; we have seen that such a value is essentially a simple object literal created
before the module is loaded.
[ 35 ]
This means that we can only attach new properties to the object referenced by the
exports variable, as shown in the following code:
exports.hello = function() {
console.log('Hello');
}
Reassigning the exports variable doesn't have any effect, because it doesn't
change the contents of module.exports, it will only reassign the variable itself.
The following code is therefore wrong:
exports = function() {
console.log('Hello');
}
If we want to export something other than an object literal, as for example a function,
an instance, or even a string, we have to reassign module.exports as follows:
module.exports = function() {
console.log('Hello');
}
require is synchronous
Another important detail that we should take into account is that our homemade
require function is synchronous. In fact, it returns the module contents using a
simple direct style, and no callback is required. This is true for the original Node.js
require() function too. As a consequence, any assignment to module.export must
be synchronous as well. For example, the following code is incorrect:
setTimeout(function() {
module.exports = function() {...};
}, 100);
[ 36 ]
Chapter 1
Core modules: If moduleName is not prefixed with "/" or "./", the algorithm
will first try to search within the core Node.js modules.
[ 37 ]
For file and package modules, both the individual files and directories can match
moduleName. In particular, the algorithm will try to match the following:
<moduleName>.js
<moduleName>/index.js
index.js
depB
bar.js
node_modules
depA
index.js
depC
foobar.js
node_modules
depA
index.js
[ 38 ]
Chapter 1
In the preceding example, myApp, depB, and depC all depend on depA; however,
they all have their own private version of the dependency! Following the rules of the
resolving algorithm, using require('depA') will load a different file depending on
the module that requires it, for example:
The resolving algorithm is the magic behind the robustness of the Node.js
dependency management, and is what makes it possible to have hundreds or even
thousands of packages in an application without having collisions or problems of
version compatibility.
The resolving algorithm is applied transparently for us when we
invoke require(); however, if needed, it can still be used directly
by any module by simply invoking require.resolve().
[ 39 ]
Cycles
Many consider circular dependencies as an intrinsic design issue, but it is something
which might actually happen in a real project, so it's useful for us to know at least how
this works in Node.js. If we look again at our homemade require() function, we
immediately get a glimpse of how this might work and what are its caveats.
Suppose we have two modules defined as follows:
Module a.js:
exports.loaded = false;
var b = require('./b');
module.exports = {
bWasLoaded: b.loaded,
loaded: true
};
Module b.js:
exports.loaded = false;
var a = require('./a');
module.exports = {
aWasLoaded: a.loaded,
loaded: true
};
Now, let's try to load these from another module, main.js, as follows:
var a = require('./a');
var b = require('./b');
console.log(a);
console.log(b);
This result reveals the caveats of circular dependencies. While both the modules
are completely initialized the moment they are required from the main module,
the a.js module will be incomplete when it is loaded from b.js. In particular,
its state will be the one that it reached the moment it required b.js. This behavior
should ring another bell, which will be confirmed if we swap the order in which
the two modules are required in main.js.
[ 40 ]
Chapter 1
If you try it, you will see that this time it will be the module a.js that will receive an
incomplete version of b.js. We understand now that this can become quite a fuzzy
business if we lose control of which module is loaded first, which can happen quite
easily if the project is big enough.
Named exports
The most basic method for exposing a public API is using named exports, which
consists in assigning all the values we want to make public to properties of the object
referenced by exports (or module.exports). In this way, the resulting exported
object becomes a container or namespace for a set of related functionality.
The following code shows a module implementing this pattern:
//file logger.js
exports.info = function(message) {
console.log('info: ' + message);
};
exports.verbose = function(message) {
console.log('verbose: ' + message);
};
The exported functions are then available as properties of the loaded module,
as shown in the following code:
//file main.js
var logger = require('./logger');
logger.info('This is an informational message');
logger.verbose('This is a verbose message');
[ 41 ]
Exporting a function
One of the most popular module definition patterns consists in reassigning the
whole module.exports variable to a function. Its main strength it's the fact that
it exposes only a single functionality, which provides a clear entry point for the
module, and makes it simple to understand and use; it also honors the principle
of small surface area very well. This way of defining modules is also known in the
community as substack pattern, after one of its most prolific adopters, James Halliday
(nickname substack). The following code is an example of this pattern:
//file logger.js
module.exports = function(message) {
console.log('info: ' + message);
};
The following code demonstrates how to use the module that we just defined:
//file main.js
var logger = require('./logger');
logger('This is an informational message');
logger.verbose('This is a verbose message');
[ 42 ]
Chapter 1
Even though exporting just a function might seem a limitation, in reality, it's a
perfect way to put the emphasis on a single functionalitythe most important for
the modulewhile giving less visibility to secondary aspects, which are instead
exposed as properties of the exported function itself.
Pattern (substack): expose the main functionality of a module by
exporting only one function. Use the exported function as namespace
to expose any auxiliary functionality.
Exporting a constructor
A module that exports a constructor is a specialization of a module that exports a
function. The difference is that with this new pattern, we allow the user to create
new instances using the constructor, but we also give them the ability to extend its
prototype and forge new classes. The following is an example of this pattern:
//file logger.js
function Logger(name) {
this.name = name;
};
Logger.prototype.log = function(message) {
console.log('[' + this.name + '] ' + message);
};
Logger.prototype.info = function(message) {
this.log('info: ' + message);
};
Logger.prototype.verbose = function(message) {
this.log('verbose: ' + message);
};
module.exports = Logger;
Exporting a constructor still provides a single entry point for the module, but
compared to the substack pattern, it exposes a lot more of the module internals;
however on the other side it allows much more power when it comes to extending
its functionality.
[ 43 ]
A variation of this pattern consists in applying a guard against invocations that don't
use the new instruction. This little trick allows us to use our module as a factory.
The following code shows you how this works:
function Logger(name) {
if(!(this instanceof Logger)) {
return new Logger(name);
}
this.name = name;
};
The trick is simple; we check whether this exists and is an instance of Logger. If any
of these conditions is false, it means that the Logger() function was invoked without
using new, so we proceed with creating the new instance properly and returning it to
the caller. This technique allows us to use the module also as a factory, as shown in
the following code:
//file logger.js
var Logger = require('./logger');
var dbLogger = Logger('DB');
accessLogger.verbose('This is a verbose message');
Exporting an instance
We can leverage the caching mechanism of require() to easily define stateful
instancesobjects with a state created from a constructor or a factory, which can be
shared across different modules. The following code shows an example of this pattern:
//file logger.js
function Logger(name) {
this.count = 0;
this.name = name;
};
Logger.prototype.log = function(message) {
this.count++;
console.log('[' + this.name + '] ' + message);
};
module.exports = new Logger('DEFAULT');
[ 44 ]
Chapter 1
Because the module is cached, every module that requires the logger module
will actually always retrieve the same instance of the object, thus sharing its state.
This pattern is very much like creating a Singleton, however, it does not guarantee
the uniqueness of the instance across the entire application, as it happens in the
traditional Singleton pattern. When analyzing the resolving algorithm, we have seen
in fact, that a module might be installed multiple times inside the dependency tree
of an application. This results with multiple instances of the same logical module, all
running in the context of the same Node.js application. In Chapter 5, Wiring Modules,
we will analyze the consequences of exporting stateful instances and some of the
patterns we can use as alternatives.
An extension to the pattern we just described, consists in exposing the constructor
used to create the instance, in addition to the instance itself. This allows the user to
create new instances of the same object, or even to extend it if necessary. To enable
this, we just need to assign a new property to the instance, as shown in the following
line of code:
module.exports.Logger = Logger;
Then, we can use the exported constructor to create other instances of the class,
as follows:
var customLogger = new logger.Logger('CUSTOM');
customLogger.log('This is an informational message');
[ 45 ]
The following example shows you how we can add a new function to
another module:
//file patcher.js
// ./logger is another module
require('./logger').customMessage = function() {
console.log('This is a new functionality');
};
Using our new patcher module would be as easy as writing the following code:
//file main.js
require('./patcher');
var logger = require('./logger');
logger.customMessage();
In the preceding code, patcher must be required before using the logger module
for the first time in order to allow the patch to be applied.
The techniques described here are all dangerous ones to apply. The main concern
is that, to have a module that modifies the global namespace or other modules is an
operation with side effects. In other words, it affects the state of entities outside their
scope, which can have consequences that are not always predictable, especially when
multiple modules interact with the same entities. Imagine to have two different
modules trying to set the same global variable, or modifying the same property
of the same module; the effects might be unpredictable (which module wins?),
but most importantly it would have repercussions on the entire application.
[ 46 ]
Chapter 1
The main difference from the callback pattern is that the subject can actually notify
multiple observers, while a traditional continuation-passing style callback will
usually propagate its result to only one listener, the callback.
The EventEmitter
In traditional object-oriented programming, the observer pattern requires interfaces,
concrete classes, and a hierarchy; in Node.js, all becomes much simpler. The observer
pattern is already built into the core and is available through the EventEmitter class.
The EventEmitter class allows us to register one or more functions as listeners,
which will be invoked when a particular event type is fired. The following image
visually explains the concept:
Listener
Event A
EventEmitter
Listener
Event B
Listener
The EventEmitter is a prototype, and it is exported from the events core module.
The following code shows how we can obtain a reference to it:
var EventEmitter = require('events').EventEmitter;
var eeInstance = new EventEmitter();
then removed after the event is emitted for the first time
[ 47 ]
All the preceding methods will return the EventEmitter instance to allow chaining.
The listener function has the signature, function([arg1], []), so it simply
accepts the arguments provided the moment the event is emitted. Inside the listener,
this refers to the instance of the EventEmitter that produces the event.
We can already see that there is a big difference between a listener and a traditional
Node.js callback; in particular, the first argument is not an error, but it can be any
data passed to emit() at the moment of its invocation.
The EventEmitter created by the preceding function will produce the following
three events:
error: This event occurs when an error has occurred during the reading
of the file
[ 48 ]
Chapter 1
In the preceding example, we registered a listener for each of the three event types
produced by the EventEmitter which was created by our findPattern() function.
Propagating errors
The EventEmitter - as it happens for callbacks - cannot just throw exceptions
when an error condition occurs, as they would be lost in the event loop if the
event is emitted asynchronously. Instead, the convention is to emit a special event,
called error, and to pass an Error object as an argument. That's exactly what we
are doing in the findPattern() function that we defined earlier.
It is always a good practice to register a listener for the
error event, as Node.js will treat it in a special way and will
automatically throw an exception and exit from the program if
no associated listener is found.
[ 49 ]
[ 50 ]
Chapter 1
The FindPattern prototype that we defined extends the EventEmitter using the
inherits() function provided by the core module util. This way, it becomes a
full-fledged observable class. The following is an example of its usage:
var findPatternObject = new FindPattern(/hello \w+/);
findPatternObject
.addFile('fileA.txt')
.addFile('fileB.json')
.find()
.on('found', function(file, match) {
console.log('Matched "' + match + '" in file ' + file);
})
.on('error', function(err) {
console.log('Error emitted ' + err.message);
});
We can now see how the FindPattern object has a full set of methods, in addition to
being observable by inheriting the functionality of the EventEmitter.
This is a pretty common pattern in the Node.js ecosystem, for example, the
Server object of the core http module defines methods such as listen(), close(),
setTimeout(), and internally it also inherits from the EventEmitter function,
thus allowing it to produce events, such as request, when a new request is received,
or connection, when a new connection is established, or closed, when the
server is closed.
Other notable examples of objects extending the EventEmitter are Node.js streams.
We will analyze streams in more detail in Chapter 3, Coding with Streams.
[ 51 ]
If the ready event was emitted asynchronously, then the previous code would
work perfectly; however, the event is produced synchronously and the listener is
registered after the event was already sent, so the result is that the listener is never
invoked; the code will print nothing to the console.
Contrarily to callbacks, there are situations where using an EventEmitter in a
synchronous fashion makes sense, given its different purpose. For this reason,
it's very important to clearly highlight the behavior of our EventEmitter in its
documentation to avoid confusion, and potentially a wrong usage.
EventEmitter vs Callbacks
A common dilemma when defining an asynchronous API is to check whether
to use an EventEmitter or simply accept a callback. The general differentiating
rule is semantic: callbacks should be used when a result must be returned in
an asynchronous way; events should instead be used when there is a need to
communicate that something has just happened.
[ 52 ]
Chapter 1
But besides this simple principle, a lot of confusion is generated from the fact that the
two paradigms are most of the time equivalent and allow you to achieve the same
results. Consider the following code for an example:
function helloEvents() {
var eventEmitter = new EventEmitter();
setTimeout(function() {
eventEmitter.emit('hello', 'world');
}, 100);
return eventEmitter;
}
function helloCallback(callback) {
setTimeout(function() {
callback('hello', 'world');
}, 100);
}
[ 53 ]
The function takes pattern as the first argument, a set of options, and a callback
function which is invoked with the list of all the files matching the provided pattern.
At the same time, the function returns an EventEmitter that provides a more
fine-grained report over the state of the process. For example, it is possible to be
notified in real-time when a match occurs by listening to the match event, to obtain
the list of all the matched files with the end event, or to know whether the process
was manually aborted by listening to the abort event. The following code shows
how this looks:
var glob = require('glob');
glob('data/*.txt', function(error, files) {
console.log('All files found: ' + JSON.stringify(files));
}).on('match', function(match) {
console.log('Match found: ' + match);
});
As we can see, the practice of exposing a simple, clean, and minimal entry point
while still providing more advanced or less important features with secondary
means is quite common in Node.js, and combining EventEmitter with traditional
callbacks is one of the ways to achieve that.
Pattern: create a function that accepts a callback and returns an
EventEmitter, thus providing a simple and clear entry point for
the main functionality, while emitting more fine-grained events
using the EventEmitter.
[ 54 ]
Chapter 1
Summary
In this chapter, we have seen how the Node.js platform is based on a few important
principles that provide the foundation to build efficient and reusable code. The
philosophy and the design choices behind the platform have, in fact, a strong
influence on the structure and behavior of every application and module we create.
Often, for a developer moving from another technology, these principles might
seem unfamiliar and the usual instinctive reaction is to fight the change by trying
to find more familiar patterns inside a world which in reality requires a real shift in
the mindset. On one hand, the asynchronous nature of the reactor pattern requires
a different programming style made of callbacks and things that happen at a later
time, without worrying too much about threads and race conditions. On the other
hand, the module pattern and its principles of simplicity and minimalism creates
interesting new scenarios in terms of reusability, maintenance, and usability.
Finally, besides the obvious technical advantages of being fast, efficient, and based
on JavaScript, Node.js is attracting so much interest because of the principles
we have just discovered. For many, grasping the essence of this world feels like
returning to the origins, to a more humane way of programming for both size
and complexity and that's why developers end up falling in love with Node.js.
In the next chapter, we will focus our attention on the mechanisms to handle
asynchronous code, we will see how callbacks can easily become our enemy, and
we will learn how to fix that by using some simple principles, patterns, or even
constructs that do not require a continuation-passing style programming.
[ 55 ]
www.PacktPub.com
Stay Connected: