To Node.js Or Not To Node.js

Intro

Node.js – it has rapidly become the “new hotness” in the tech start-up realm. With each passing day, the fan base of Node lovers grows larger, spreading their rhetoric like a religion. How do you spot a Node.js user? Don’t worry, they’ll let you know.

One day you’re at a regular user group meeting, sipping soda and talking with some colleagues, when the subject turns to Node. “Have you guys tried Node.js?” asks one of the people in your group. “It’s all the rage. All of the cool kids in Silicon Valley are using it!” “What does it do?” you ask, only to be bombarded with a sales pitch worthy of the best of used car lots. “Oh, it’s amazing!” they reply, sipping their diet coke and shuffling their hipster fedora and backpack with MacBook Pro in it (or something like that), “It’s server side JavaScript. It runs on a single thread and it can do 100,000 web requests a second!” They glance at the group for the oohs and ahhs, but most people just stare back with amazement in their eyes. Then, your hipster Node-loving friend drops the words that start wars: “It’s way better than .NET” – and just like that, your group is hooked. They go home, download the Node.js tools, write “Hello World”, and suddenly they’re on their way to the next user group meeting to talk about how great Node is.

Okay, so I might be exaggerating the appearance and demeanour of your average Node lover a little (read: a lot, almost entirely in fact). However, I have had this exact scenario happen repeatedly over the last six months, with ever-increasing intensity and frequency. Node users love Node. They want you to love Node. They’re excited about it.

Having given it some thought, why wouldn’t Node.js developers be excited? Want to fire up a “Hello World” web server in Node? It’s trivial:

// Load the http module to create an http server.
var http = require('http');

// Configure our HTTP server to respond with Hello World to all requests.
var server = http.createServer(function (request, response) {
  response.writeHead(200, {"Content-Type": "text/plain"});
  response.end("Hello World\n");
});

// Listen on port 8000, IP defaults to 127.0.0.1
server.listen(8000);

Want to do the same thing in .NET? Be prepared to learn about IIS, the Machine.config, the Web.config, the Process Model, how Global.asax works, either ASP.NET MVC or WebForms (huge paradigms in themselves), and how Visual Studio works. Don’t forget to learn how to create a solution, at least one web project, and how to deploy the application to IIS. Oh, and one little detail: go ahead and learn C# while you’re at it. All of that’s pretty much just as easy and intuitive as Node.js, right?

Complexity Matters

.NET is incredibly complicated. Node.js is incredibly simple. On the merits of that fact alone it’s no wonder that these .NET developers and fresh-out-of-college kids who have already dabbled in JavaScript are transferring these skills to the server side and standing up web servers in literally 5 minutes and 5 lines of code. How can you deny the sexiness of that? The bragging rights it gives you? The ease and comfort of a language you’re already familiar with?

This, in my opinion, is why Node.js is becoming huge. It has simplified and streamlined the development process and made programming very accessible to almost everyone (or at least anyone who has ever played with JavaScript).

However, to those who sell Node.js as single-threaded, and to those who sell Node.js as having significantly better performance than .NET, I say this: you are wrong.

Misunderstandings

With simplicity comes misunderstanding and the concept of “Leaky Abstractions.” As my good friend and colleague Thomas B. said to me during dinner last week: Node.js is opinionated. It has an opinion on how you should do things, and it forces you to do them a certain way.

Node.js is not single-threaded, though many Node developers in my experience believe it to be. Node’s creator believes that a single-threaded listener delegating I/O-bound work to a thread pool is the key to a highly available application. As a result, Node.js forces you into this paradigm of event-based asynchronous execution of I/O operations via a thread pool.

Node.js has a single thread listening for connections. All of the code which you as the Node developer write is executed on this single thread. This single thread is all that is exposed to you. As soon as a connection is received, Node’s listening thread executes your coded event on the same listener thread. This event either does quick, non-CPU intensive work (like returning static content to a client), or long-running I/O bound operations (like reading data from a database). In the case of the former, the listener thread does in fact block for the duration of the request, but the request happens so quickly that the delay is trivial. In the case of the latter, Node uses V8 and libuv (which it is built upon) to delegate the I/O work to a thread from an underlying pool of native C++ threads. The single listening thread kicks off the work to an I/O worker thread with a callback that says “tell me when you’re done” and immediately returns to listening for the next connection. It is thus plain to see that Node.js is indeed multi-threaded, though this functionality is not directly exposed to the Node developer.

An important note regarding Node.js is that any CPU-intensive code which you write will block the entire system and make your application scale poorly or become entirely unresponsive. As a result, you would not want to use Node.js when you need to write an application that will do CPU-intensive work such as performing calculations or creating reports.

This is how a single thread can handle multiple requests at once; receiving a request and either serving static/simple content or delegating it to an I/O thread from a thread pool are both very cheap and quick operations. When the thread pool thread that is doing the long-running I/O work signals to the single listener thread that the work is done, the listener thread picks up the response and sends it back to the user; this is another very cheap operation. The core idea is that the single listener thread never blocks: it only does fast, cheap processing or delegation of requests to other threads and the serving of responses to clients. The diagram below (taken from Stack Overflow) explains this visually:

/img/node-processing-model.png

Node.js Processing Model

This is a very good, scalable, highly-available way to write code; Node.js nailed their approach and developers benefit from it. However, as of .NET 4.5, you can easily create this exact paradigm/pattern in your .NET applications. The difference is that .NET does not force you to do so.

OWIN & A Better .NET

With the introduction of a very tidy wrapper around asynchronous programming in .NET 4.5 (async/await keywords), Microsoft made asynchronous, event-based programming quite a bit easier and more intuitive. And with recent conformance by Microsoft to the jointly-created OWIN specification, the web pipeline of .NET has become much simpler.

In fact, you can now write the “Hello World” asynchronous web server in .NET in about as few lines as Node.js! In this example, I host a web server in a console application which is terminated when a key is pressed:

/// <summary>
/// A simple program to show off an OWIN self-hosted web app.
/// </summary>
public class Program
{
    /// <summary>
    /// The entry point for the console application.
    /// </summary>
    /// <param name="args">The args to the console application. Ignored.</param>
    static void Main(string[] args)
    {
        // Start OWIN host
        using (WebApp.Start<Startup>(url: "http://localhost:8000"))
        {
            // Runs until a key is pressed
            Console.ReadKey();
        }
    }

    /// <summary>
    /// This code configures the OWIN web app. The Startup class is specified as a 
	/// type parameter in the WebApp.Start method.
    /// </summary>
    private class Startup
    {
        /// <summary>
        /// Configures the web app.
        /// </summary>
        /// <param name="app">The app builder.</param>
        public void Configuration(IAppBuilder app)
        {
            // We ignore any rules here and just return the same response for any request
            app.Run(context =>
            {
                context.Response.ContentType = "text/plain";
                return context.Response.WriteAsync("Hello World\n");
            } );
        }
    }
}

One of the big positives of Node.js is that you opt-in to complexity. You start very simply and add on functionality as you need it. I’m a big fan of this approach and I feel that this is where Node really shines. Nothing bothers me more than starting an “Empty MVC 4 Web Application” from template in Visual Studio only to have it install about 15 Nuget packages, one of which is Entity Framework. Great, I fired up a blank application and already my ORM has been decided for me. Who said I even needed one in the first place?!

The above OWIN-based approach to hosting a web app in .NET allows you to benefit from Node’s simplistic approach. I have started out simply, and can now add Dapper if I need an ORM, Newtonsoft.Json if I need to serialize to and from JSON, Unity if I care about dependency injection, etc. It’s a nice, clean slate upon which I can build any framework that I desire.

OWIN .NET vs Node.js

The OWIN approach in .NET is very comparable to Node.js, with a few differences:

  • Node.js uses 1 listener thread, while .NET uses N listener threads. If your Node.js application does CPU-intensive work at all, it will block the entire system and potentially cause your application to become unresponsive. .NET, on the other hand, is designed to do CPU intensive work. Tying up a thread to do some CPU work is not of concern because there are other threads available in the listener thread pool to take other requests while this is happening. However, both Node.js and .NET are limited by the server resources; in either case, if you max out the CPU or RAM, your app will perform horribly, regardless of thread delegation. This is known as resource contention.
  • Node.js delegates I/O bound work to an I/O thread worker pool, and .NET implemented asynchronously (async methods and the async/await keywords) does the same.
  • Node.js uses an event-based paradigm, and .NET does also when implemented asynchronously.
  • Node.js offers high performance for I/O bound, low CPU operations, and .NET offers comparable performance when you skip the IIS pipeline. IIS tacks on a significant amount of performance overhead due to things like session state management, forms authentication, the process model, request lifecycle events, etc. These are not bad things to have and use, but if you don’t need IIS, session state, forms auth, request lifecycle events, or the process model, then don’t use them!
  • Node.js must parse/serialize to and from JSON, and .NET must serialize to and from JSON to interact with .NET objects. Parsing is going to be much cheaper in Node.js than serializing is in .NET, but .NET also enables you to serialize to XML, Atom RSS, and anything else that you desire. With Node, this is a bit trickier, and the serialization overhead comes back into play to even the field.

When someone compares Node.js to .NET, I find that they often actually compare Node.js to IIS hosted frameworks such as ASP.NET MVC, ASP.NET WebForms, and ASP.NET Web API (in IIS hosted mode). These are all tools built on top of ASP.NET to simplify enterprise web development and to do CPU-intensive calculations. In these scenarios, Node.js will have an advantage, because it is designed specifically to NOT do CPU-intensive calculations. You are effectively comparing CPU-intensive Apples to low-CPU-usage Oranges. It is not a fair comparison.

When someone compares Node.js to a self-hosted .NET web app which does I/O-bound long-running operations via asynchronous thread pool delegation, they find that there is not much of a difference in performance between the two runtimes. In fact, comparing Node.js to self-hosted Web API (NOT using IIS) doing low-CPU work, the numbers are very close:

/img/webapi-vs-node.png

WebAPI vs Node.js

This image was taken from a benchmark done in 2012 with the Web API Release Candidate (not Web API 2, and NOT OWIN hosted). Given that Web API 2 exists, and can be self-hosted via OWIN, I’d love to see a more recent benchmark comparing the two. In fact, I will try and do this for my next blog post.

Final Thoughts

I guess the point of all of this has been that neither Node.js or .NET is necessarily better/the best/the new hotness. They both serve a purpose, and while Node.js is much easier to use and more accessible to developers, .NET is very versatile and powerful as well. They are built to do different things: Node.js specializes in performing and scaling well for low-CPU, highly I/O-bound operations. .NET can perform well in this scenario as well, but can also perform well with high-CPU operations. In fact, I would argue that .NET excels at CPU-intensive operations, especially when compared to Node.

There are many .NET developers in the current tech scene that are capable and competent. This means that it’s not too hard to find, hire, and retain good .NET talent. They can pick up self-hosted OWIN web apps in a matter of days, and begin to write very scalable, high-performance web apps and services based on it. They can even easily host Web API in an OWIN web app via console, a Windows service, or Azure. There’s a community that has existed for over a decade that evolves the .NET framework with amazing tools and add-ons like Dapper, Unity, and Newtonsoft.Json. This community is mature and there are many prominent voices within it that offer advice and help.

Relative to .NET, there aren’t as many Node.js developers in the current tech scene that are capable and competent. This is because fortune favours the bold, and only a few have been early adopters of Node.js. In my experience, few Node developers will truly understand what is going on in the Node.js processing model and how to exploit it for maximum performance, though the opinionated paradigm of Node.js will force developers to write good asynchronous code. It can be hard to find, hire, and retain good Node.js talent. This will become less of a concern as Node’s following grows. The Node.js community is budding and has created some amazing tools and add-ons for Node.js as well such as ORMs and DI frameworks. This community is not yet mature and I am not aware of many prominent voices within it that offer advice and help. As a result, it could be difficult to find support and tools for Node.js if you encounter a problem.

Conclusions

In conclusion, both Node.js and .NET are great. Which one to pick for a particular solution/application, however, depends on many factors; it is not black and white but a full colour spectrum. It would be very foolish and naive for a .NET developer to use .NET to solve every single problem just because “that’s what we use.” It would be similarly foolish for a Node.js developer to propose Node.js as a solution for every project or problem that he or she encounters. One must choose the right tool for a given job, and be open to different paradigms in order to truly excel.

In general, use Node.js when you have highly I/O-bound operations that don’t use much CPU. Use .NET when you need to calculate things and use a lot of CPU.

Don’t use Node.js solely on the reasoning that it’s much faster and performs way better than .NET: it depends on how you use .NET. And don’t use .NET if all you’re doing is heavily I/O-bound operations with low CPU usage: that’s where Node.js excels.


See also