( Node.js ) => { Do More With Less }
After 18 years of hardcore Java, I gave Node.js a serious test-drive. I've now been using it for about a year. Node is more productive than Java in nearly every single respect for building JSON based microservies. While the individual benefits are not game-changers by themselves, when taken together, the overall productivity boost is overwhelmingly compelling.
With the increased productivity and the significant advancements in Node, especially over the last year, it has become a much more capable and production worthy platform. For those who haven't tried Node or used it recently, you might want to give it a whirl.
I'll present industry data, from the trenches advantages and disadvantages, metrics, and compare Javascript and Node with Java and Spring Boot, et. al. There are even a few simple examples showing significantly less code is required with Javascript/Node.
Industry Data
- It was stated by Mikeal Rogers, Node.js Community Organizer during a June 2017 interview, that Node.js will overtake Java within one year, and analyzing the dataseems to support that speculation.
- Well known companies are supporting Node.js. Google, in Oct. 2017, joined IBM, Microsoft, Intel, Joyent and Red Hat as a platinum Node.js foundation member. There are also numerous gold and silver corporate members too.
- Node.js applications are implemented in Javascript which is the most widely known and utilized language by a substantial margin.
- JavaScript appears to be rising faster than Java which is currently declining.
- Node.js popularity appears to be rising substantially faster than Spring and Spring Boot even when they are combined.
- Javascript has less conceptual weight and is easier to learn than Java. In February 2017, Stanford University announced its intro to Computer Science course will now be taught in Javascript instead of Java because, "Java was showing its age", and, "Javascript has taken its place as a web language".
- Many high profile companies have embraced Node.js and are claiming significant productivity and efficiency benefits similar to what my team and I experienced, see here and here. Some notable companies utilizing Node include Netflix, PayPal, LinkedIn, Walmart, Uber, Groupon, Ebay, Nasa, Mozilla, Microsoft, Cisco, IBM.
Javascript/Node Advantages
- When the front-end and back-end services are both implemented with Javascript, libraries can be shared front and back (utilities, parsing, and validation logic among others), not so with other languages. It requires less mental context switching when working across the front-end and back-end. It improves developer fungibility since it's more practical for developers to learn and work across layers implemented in one language. With Java, there are typically three languages involved, Javascript for front-end, Java for server, and Groovy for the Gradle build tool. In contrast, Node.js requires only Javascript.
- In Node there is less impedance mismatch because JSON is used nearly everywhere and by every library for configuration and data interchange. The consistency makes everything simpler, easier, and quicker to author and comprehend.
- JSON in Javascript is intuitive to write and read, and with syntax conveniences, it's far less verbose than idiomatic Java OO and static typing.
Initial Java prototypes revealed Javascript required about 10% of the code of idiomatic Java OO (i.e., JavaBean style) with static typing, primarily due to JSON. The simple example above indicates the same when the Java is properly expanded and completed. It's even longer with idiomatic JavaDoc. Notice in the Javascript on the right, comments can be placed in the JSON. In all fairness, the Java IDEs auto-creates most of the boilerplate code with a few clicks, but the reality, it's not necessary to create it to get the job done. If that's true, why create it and check it into your repo? The same level of testing is necessary (or should be done) either way.
A couple other attempts to create JSON in Java with brevity. The first isn't very clear and becomes unwieldy as nesting increases. The second is shorter but ridiculous, even more so with variables. Multi-line becomes even more hideous. The moral, Javascript for JSON.
- Node is lighter weight. Node starts/restarts substantially faster than Java/Spring boot, significantly improving update turn-around and developer productivity. This also impacts time-to-service first user request in production.
- Requires less memory than Java/Spring Boot processes. More economically efficient, consumes less hardware, more Node services can be operated on the same hardware.
- Javascript applications and Node libraries promote entirely asynchronousimplementation/execution. Async is more efficient and scalable with less context switching (lower overhead) than Java's typically highly threaded or thread per request paradigms.
- Node Package Manager (NPM) for library dependencies is easier to use improving productivity. Generally no need to edit a separate config file and then load dependencies. It's a single operation. NPM avoids dependency conflicts because any libraries conflicting dependencies are included under the libraries directory to prevent any conflict issues, in contrast, Java's Jar/Classpath/Dependency Hell issues explained.
- Node is lightweight and ala carte compared to Java. Java 9 takes a step toward being more lightweight and ala carte with project Jigsaw. It's still more complex than NPM though.
- NPM repository has more than 2x libraries than Java's Maven Central repository.
- Typical Node libraries (interpreted Javascript, not native C++ modules) are source and always at your fingertips under node_modules to inspect, debug, and modify without any extra time or effort. With Java's Maven or Gradle, it's necessary to update the configuration to include source which increases the number of downloads and takes longer. If you want to modify a Java library, it's more cumbersome because you have to create a project and add the project dependency to the applications project in the IDE so it will compile and be included in the application.
- Javascript testing, with ava for example, requires less verbosity than Java's OO. Javascript mocking with the sinon library, for example, is easier without the complexities or limitations of Java with respect to private and static members and functions. Sinon wraps the target before loading avoiding the need to dependency inject the mock as is typical in Java tests. While it's possible to accomplish in Java with AOP/Spring AOP, it's more complex/conceptual weight to work with than Javascript's sinon library.
- Javascript's single-threaded application code is simpler to reason about and doesn't require Java's final, volatile, synchronized or concurrent collections to be safe, assuming no concurrently manipulated buffers by underlying I/O handlers. I haven't had to deal with this issue yet with any of the services I've implemented.
- Both browser and Node server can be debugged and profiled with the same chrome:inspector. Even if not started with debugging enabled, the debugger can be enabled at anytime with kill -10 (SIGUSR1). It even supports live code editingwithout the need to restart the Node server. I've only successfully made trivial changes. Integrated IDE debugging also works with many Javascript/Node IDEs. Excellent video on Node debugging by Paul Irish, Performance Engineer, Google Chrome at Node Summit.
- With Node's Sequelize ORM library and JSON, it's practical to create a customized connection pool, define the database structures, create and persist instances, query the database, and log the results in less code than is required to define a single POJO entity class for Java Spring Data. With simple flags, Sequelize also supports soft delete, optimistic locking (versioning), auto create/update/delete date/time stamping, and validations. Sequelize also provides comprehensive incremental forward/backward migration support. Spring Data is definitely impressive, however, Javascript and JSON's expressiveness is impossible to beat in idiomatic Java OO (i.e., JavaBean style), no matter how much functionality Java and/or Spring bury under annotations or templates. BTW, if something goes wrong in Spring's black magic, it will be much harder to debug than a Node module whose interpreted source is always in your project.
- Javascript doesn't have Java's Generics or Runtime vs Caught Exceptions complexity/conceptual weight.
- Creating and initializing arrays/lists and maps/sets is far simpler and less verbose in Javascript than Java.
- Consuming a REST service in Node, with axios for example, is simpler and less verbose than with Java Spring Boot RestTemplate.
- There are Node libraries that support background threads that take advantage of all the CPU cores. They use the Web Workers API so the same application code will execute in browsers using Web Workers or in the server using child processes.
- Babel transpiling enables using the latest and greatest (future) Javascript features in both browsers and Node before they are supported by the browser or Node, like import and async/await.
- Node Version Manger (NVM) makes it super simple and quick to list local and remote versions, install any or multiple versions, and switch between versions of Node and NPM. Much more complex in Java.
Javascript/Node Disadvantages
It's not all roses and no comparison would be complete or fair without full disclosure of the pain points.
- While Node has capable logging libraries, I haven't yet found anything as flexible as log4j or logback. log4js is similar, but doesn't support run-time configuration changes.
- Unlike Eclipse and IntelliJ for Java, the free Atom or Sublime, out of the box, don't support jump to declaration. Both can be configured to accomplish it. The commercial WebStorm IDE does standard. Not sure about the free Visual Studio or IntelliJ for Node.
- Async style programming requires a little more care, but is nearly as easy as synchronous programming with try/catch and async/await. I haven't yet figured out how to configure lint to catch missing Promise returns or missing async awaits. When forgotten, server requests hang till timeout, and that can be a pain and time consuming to track down.
- Refactoring is less automatic due to lack of static typing, however with smaller cohesive microservices this has not been a problem. Robert C. Martin points out in The Last Programming Language, if you are properly applying TDD principles, you should have nearly 100% test coverage and static typing isn't necessary for safety. He also stated that one of the reasons dynamically typed languages are substantially more productive is lack of static typing. There are Javascript typing add-ons; flow and Typescript that should enable better refactoring. I'm not using them and so far we haven't experienced issues.
- Multiple equality operators == and === and the concept of truthy and falsyincreases complexity and conceptual weight. It's comparable complexity with Java's == and .equals(Object obj) method.
- Having undefined, null, and NaN values increases complexity and conceptual weight.
- Javascript/Node tooling options like JMX, debugging, and profiling are less mature and have less options than Java and Spring Boot. The current solutions are quite good and I've found them adequate to get the job done.
- Due to Node's ala carte nature and excessive number of NPM libraries, it's more difficult to select and/or select a quality library. There are more low quality libraries in NPM to be wary of than Apache for Java. Better curation of quality Javascript/Node libraries is needed.
- Many libraries are implemented with event style or error-first callback or Continuation-Passing Style (CPS) and are less convenient until wrapped to convert them to promises for use with async/await style programming. It's not difficult and a one-time operation per library. Eventually they will all have wrappers or will be converted into newer promise/async style libraries.
- Neither Java or Node support tail-call optimization (TCO), proper-tail calls (PTC), or syntactic-tail calls (STC) for large recursion without blowing out the stack. Node 6 did have support via a flag. Maybe soon for Javascript.
- Have not found a swagger library for Node that auto-creates swagger API with minimal effort, like what's available for Java and Spring Boot with annotations. The hapi-swagger lib apparently supports this. It's just not practical without OO and/or static typing which flow and typescript provide. Typically, developers define the API first with yaml, so the same up front effort is required/expended with Node or Java.
- In Node, it's still necessary to Babel transpile in order to use import or to use await in loops. Await loops are now available as a flag feature in Node 9 and 10. Babel is an easy one-time setup and then it just transparently happens automatically before tests or starting the server. It's so quick I don't even notice it. Possibly, it might be noticeable if, for some reason, you were still building a monolith.
Backstory
Skip this section if you're not interested in my journey of avoidance. On the other hand, it could save you from similar foolishness and waste of time. (tl;dr)
Before I tried Node, our team of six, prototyped with each of the top microservice contenders, including Play, Dropwizard, Spark Framework, Vertx, and Spring Boot. At least two developers tried each framework. One developer who had tinkered with Node tried it. Node was significantly quicker to implement the same features and far less code, however, incorrectly, NOBODY had the confidence the team could learn and switch to Node and its async paradigm and be productive quickly enough to meet the tight deadlines. We selected Vertx because it's Java, lightweight, a polyglot (supports 7 languages) and touts similar benefits to Node; lightweight, fast, modular, and scalable. Vertx also has executeBlocking to support the almost exclusively non-async Java libraries. All told, I utilized Vertx for more than two years on two projects and two companies. While I like Vertx a lot, unfortunately, it suffers from many of Java's disadvantages, and using executeBlocking undermines the benefits of async everywhere. In retrospect, it was a foolish attempt to continue to leverage current Java expertise and avoid learning and adopting Node.
I also foolishly utilized the Java Nashorn Javascript engine on multiple projects in an attempt to make working with JSON in Java as simple and easy as in Javascript. Unfortunately, it's cumbersome/fragmented to develop and import Javascript libraries and Nashorn doesn't support ES6/7/etc. In addition, NPM native libraries aren't compatible. Vertx utilizes Nashorn, so these apply to Vertx as well.
Eventually, after considerable reflection, I came to my senses and made the old college try to learn and apply Node. I spent a few days brushing up on Javascript, completed the Risings stack Node Hero tutorial series, and a couple weeks reading a Node.js Web Development book cover to cover. After I successfully completed my first microservice, which took about a month, I was feeling confident, but certainly didn't comprehend all the benefits. I worked the next twelve months with two other developers, learning and applying Babel transpiling of ES6/7/etc., async/await, import, etc. In that time, we built and deployed to production, a dozen, mostly horizontal scale, microservices. They included a search system, interests service, ratings service, provisioning system, and recommendations system. The Node.js services integrated with PostgreSQL, Kafka, Cassandra, Elasitcsearch, Salesforce, and PeopleSoft, as well as other custom services implemented in .NET, Ruby, Elixir, and Spring Boot.
Summary
The biggest Javascript/Node advantages:
- Javascript required significantly less code than Java
- JSON everywhere and authoring simplicity is extremely productive
- Sequelize ORM is simpler, less verbose, and more productive than JPA/Hibernate.
The biggest Javascript/Node disadvantages:
- lint doesn't catch missing promise return or missing async await
- continuation-local-storage (CLS) didn't work everywhere, Node 8, CLS now appears to finally work everywhere I've tried and need it.
- lack of a logging library similar to log4j or logback. log4js is similar, but doesn't support run-time configuration changes.
Conclusion
Node has come a long way in the last year. It's repeatedly proving itself by being adopted and utilized by so many notable companies in massive and critical production deployments. The productivity and efficiency benefits being touted by early adopters are consistent and pervasive. My personal experience with Node over the last year echoes the claims that it's far more productive than Java for implementing JSON based microservices. Since Node is programmed in Javascript, the most widely known and utilized programming language, human resources with Javascript experience are plentiful and easy to get up to speed and productive in Node. If you aren't using Node or haven't taken a look recently, I encourage you to do so. I'm convinced Node will soon displace Java as the most widely used web services platform, if it hasn't already. It certainly has for my team and I.
After many years as a practitioner of idiomatic Java OO and static typing, I'm happily embracing Javascript's more lean and productive styles of programming. I've created lots of services with copious tests, and I'm not experiencing any negative effects from less OO and lack of static typing in my microservice designs and implementations.
Comments