Why did a person from the Java world become an ardent supporter of Node.js and JavaScript?

Original author: David Herron
  • Transfer
David Harron, the author of the material we are translating today, asked the following question: “Should a person who worked for more than 10 years at Sun Microsystems, in the Java SE team, think only about Java bytecode and create instances of abstract interfaces until the last breath? ". He asked this question in relation to himself, and for him the Node.js platform, after Java, turned out to be like a breath of fresh air. David says that when he was fired from Sun in January 2009 (right before the takeover of this Oracle company), he learned about Node.js. This technology has hooked him. What does “hooked” mean? Since 2010, he has written a lot about programming for Node.js. Namely, he wrote several books, including Node.js Web Development, the fourth edition of which was published this year. He prepared a lot of small materials about Node.js published on the Internet. In fact, he devoted a lot of time and effort talking about the Node.js platform and the possibilities of JavaScript. Why was it that the person who used to do Java exclusively was so fascinated with Node.js and JavaScript?

image

About Java


Working at Sun, I believed in Java technology. I gave reports on JavaONE, participated in the development of the java.awt.Robot class, organized the event Mustang Regressions Contest (this was a contest aimed at finding errors in Java 1.6), helped launch the project “Distributions License for Java”, which served as the answer to the question about the Linux distributions of the JDK before the appearance of OpenJDK. Later, I played some role in the launch of the OpenJDK project. Along the way, for about 6 years, I have been publishing materials on a blog on java.net (now this site is closed). These were 1-2 articles per week devoted to significant events in the Java ecosystem. A significant role in my work was played by protecting Java from those who predicted this technology would have a bleak future.


This award, the Duke Award, was given to particularly distinguished Sun employees. I got it after I organized the Mustang Regressions Contest.

What happened to a man who did so much about everything related to Java? As a matter of fact, here I want to talk about how I turned from a Java follower into a hot supporter of Node.js and JavaScript.

I must say that what happened to me cannot be called a complete rejection of Java. I, over the last 3 years, wrote quite a lot of Java code, used Spring and Hibernate. Although I really like what I am doing in this area now (I work in the solar industry, I do what I like to do, for example - I write requests to work with data from the energy sector), Java programming is now in my eyes lost its former glory.

Two years of development using Spring allowed me to clearly understand one important thing: trying to hide complex mechanisms does not lead to simplicity, it only leads to the emergence of even more complex structures.

Here, in brief, are the main ideas that I will touch on in this material:

  • Java programs are full of template code that hides the programmer’s intentions.
  • Working with Spring and Spring Boot gave me a good lesson, which is that trying to hide complex mechanisms leads to even more complex structures.
  • The Java EE platform was a project created, so to speak, by “universal effort” that covers absolutely all the needs of developing enterprise applications. As a result, the Java EE platform was prohibitively complex.
  • Developing with Spring is, until a certain point, a pleasant occupation. This illusion disappears on that day when an exception emerges from the depths of a certain subsystem about which you have never heard, which is completely impossible to understand, and it takes at least three days to find out what the problem is.
  • What kind of auxiliary mechanisms that create an unnecessary load on the system need a framework that can “write” code for programmers?
  • Although IDEs like Eclipse are powerful applications, they are a measure of the complexity of the Java ecosystem.
  • Node.js platform emerged as a result of the efforts of one person aimed at improving his vision of a lightweight, event-driven architecture.
  • It seems that the JavaScript community is enthusiastic about the ideas of getting rid of the template code, which allows programmers to express their intentions as clearly as possible.
  • As a solution to the problem of hell callbacks in JS, there is the async / await construction, which is an example of the rejection of the template code and contributes to the clarity of the expression of programmer intentions.
  • Programming for Node.js is a pleasure.
  • JavaScript does not have strong typing specific to Java. This is the blessing and curse of the tongue. This makes it easier to write code, but in order to verify its correctness, you have to spend more time testing.
  • The package management system provided by npm / yarn is easy and pleasant to use. She is no match for Maven.
  • Both Java and Node.js offer excellent performance. This goes against the myth, according to which JavaScript is a slow language, the use of which leads to poor performance of the Node.js platform.
  • Performance Node.js relies on Google’s efforts to improve V8, the engine that drives the speed of Chrome’s browser.
  • The fierce competition between manufacturers of browser-based JS engines contributes to the development of JavaScript, and this is very beneficial to Node.js.

About Java Development Issues


Some tools or objects are the result of many years of efforts by engineers to improve them. Programmers experience different ideas, remove unnecessary attributes, and as a result they get entities that have only what is needed to solve a certain task. Often these technologies have something inherent in a very attractive simplicity that hides powerful capabilities. This is not applicable to Java.

Spring is a popular framework for developing Java-based web applications.

The main goal of Spring, and, in particular, Spring Boot, is to provide the ability to use a pre-configured Java EE stack. The programmer who uses Spring should not, in order to create a complete system, take care of servlets, permanent data storage systems, application servers, and it is not yet known about anything. All these concerns are shifted to Spring’s shoulders, and the programmer is writing code that implements the logic of the application. For example, the JPARepository mechanisms are responsible for generating database queries for methods that look like findUserByFirstName. The programmer does not have to write the code of such methods. It is enough to pass the description of the method to the system, and Spring will do the rest.

All this sounds very good, it is pleasant to work in such a style, but - until some surprise happens.

I mean a situation where, for example, a Hibernate exception comes PersistentObjectExceptionwith the messagedetached entity passed to persist. What does it mean? It took several days to figure it out. As it turned out, to describe everything in a very simplified way, this means that the JSON data received at the REST endpoint has ID fields with some values. Hibernate, again, without going into details, seeks to control ID values, and, as a result, throws the above-described obscure exception. There are thousands of such error messages that are confusing and difficult to read. Considering the fact that there are whole cascades of subsystems based on each other in Spring, the Spring stack looks like a sworn enemy programmer, who watches him and waits for the programmer to make the slightest mistake, and when this happens, he throws exceptions that are not compatible with normal operation of the application.

Next, you can immediately recall the longest stack traces. They represent several screens full of all sorts of abstract methods. Spring obviously creates the configuration necessary to implement what is expressed in the code. Such a level of abstraction, of course, requires a considerable amount of auxiliary logic, which is aimed at finding everything necessary for the operation of the code, for example, in order to execute queries. And long stack traces are not necessarily bad. Such things are, rather, a symptom, leading to the question of how much auxiliary mechanisms create a load on the system.

How the method is executedfindUserByFirstName, given that the programmer did not write the code of a similar method? The framework needs to parse the name of the method, understand the programmer’s intent, create something like an abstract syntax tree, generate some SQL code, and so on. How does all this load the system? And all this exists only so that the programmer does not need to write code?

After you have to go through a few dozen times through something like a search for the meaning of the above error, spending weeks trying to figure out secrets that, by and large, you don’t have to guess, you can come to the same conclusion that I came to . Its meaning is that the attempt to hide complex mechanisms does not lead to simplicity, it only leads to the emergence of even more complex structures. The Node.js platform is much simpler.

The “Compatibility Matters” slogan concealed a great idea, according to which the most important feature of the Java platform was backward compatibility. We took it seriously, putting on t-shirts images similar to the one you can see below.


Backward compatibility is very important.

Of course, this level of attention to backward compatibility can be a source of constant anxiety, and from time to time it is useful to move away from old mechanisms that no longer do any good.

Java and Node.js


Spring and Java EE are overly complex. The Node.js platform on their background is perceived as a breath of fresh air. The first thing you notice about getting acquainted with Node.js is Ryan Dahl’s approach to developing the platform’s core. His experience told him that platforms that use threads are needed to create complex, heavyweight systems. He was looking for something else, and spent a couple of years improving the set of basic mechanisms embodied in Node.js. As a result, he got a lightweight system that characterizes a single thread of execution, an inventive use of anonymous JavaScript functions as asynchronous callbacks, and a runtime library that originally implements asynchronous mechanisms.

Further, an important feature of Node.js is the use of JavaScript. There is a feeling that those who write to JS have a tendency to get rid of the template code, which makes it possible to clearly describe the intentions of the programmer.

As an example of the differences between Java and JavaScript, consider the implementation of listener functions (observers). In Java, to work with listeners, you need to create a specific instance of an abstract interface. This entails the use of cumbersome language constructs that hide the essence of what is happening. How to discern the intention of the programmer, hidden under the covers of the template code?

JavaScript uses simple anonymous functions instead. Implementing the listener, you do not need to look for a suitable abstract interface. It is enough, without the need to use a variety of auxiliary texts, to write the necessary code.

So, here is one important idea that can be derived from the analysis of the mechanisms described above: most programming languages ​​hide the programmer's intentions, which leads to the fact that the code is difficult to understand.

The solution for using callback functions offered by Node.js looks very attractive. But it is not without problems.

Problem Solving and Problem Solving


In JavaScript, there have long been two problems associated with asynchronous programming. The first is what is called callback hell in Node.js. This problem lies in the fact that, in the course of development, it is easy to fall into a trap built from deeply nested callback functions, where each level of nesting complicates the program, as well as processing the results of the code and errors. There was another problem associated with this, the essence of which is that the JavaScript language mechanisms did not help the programmer to properly express the ideas of asynchronous code execution.

To simplify asynchronous development in JS, several libraries have emerged. But this is another example of an attempt to hide complex mechanisms, which only leads to the appearance of even more complex structures.

Consider an example:

constasync = require(‘async’);
const fs = require(‘fs’);
const cat = function(filez, fini) {
  async.eachSeries(filez, function(filenm, next) {
    fs.readFile(filenm, ‘utf8’, function(err, data) {
      if (err) return next(err);
      process.stdout.write(data, ‘utf8’, function(err) {
        if (err) next(err);
        else next();
      });
    });
  },
  function(err) {
    if (err) fini(err);
    else fini();
  });
};
cat(process.argv.slice(2), function(err) {
  if (err) console.error(err.stack);
});

This is a nondescript imitation of the Unix command cat. The library is asyncexcellent in simplifying asynchronous call sequences. However, its use requires a large amount of template code that hides the programmer’s intent.

In essence, this code contains a loop. It is not written as a normal cycle, it does not use natural constructions describing cycles. Further, the results of executing the code and the errors it generates do not go where it would be right for them. They are locked in callbacks, and this is inconvenient. But, before the introduction of ES2015 / 2016 standards in Node.js, nothing better could be done.

If we rewrite this code taking into account the new features that, in particular, are available in Node.js 10.x, we get the following:

const fs = require(‘fs’).promises;
asyncfunctioncat(filenmz) {
  for (var filenm of filenmz) {
    let data = await fs.readFile(filenm, ‘utf8’);
    awaitnewPromise((resolve, reject) => {
      process.stdout.write(data, ‘utf8’, (err) => {
        if (err) reject(err);
        else resolve();
      });
    });
  }
}
cat(process.argv.slice(2)).catch(err => { 
    console.error(err.stack); 
});

In this example, we used a construct async/await. Here the same asynchronous mechanisms are presented as in the previous example, but here the usual structures used in the organization of cycles are used. Working with the results and errors looks quite normal. Such code is easier to read and write. This approach makes it easy to understand the intent of the programmer.

The only drawback is that it process.stdout.writedoes not have a Promise interface, as a result, this mechanism cannot be used in async functions without wrapping it in promis.

Now we can conclude that the problem of hell callbacks in JavaScript was solved in a way that differs from trying to hide complex mechanisms. Instead, changes were made to the language, which solved the problem itself and saved us from the inconvenience caused by the need to use large amounts of sample code in the temporary solution. In addition, using the async / await mechanism, the code simply became more beautiful.

We started this section by discussing the shortcomings of Node.js, but an excellent solution to the problem of callback hell led to the fact that talking about shortcomings turned into a conversation about the strengths of Node.js and JavaScript.

Strong typing, interfaces, and imaginary code clarity


In those days, when I was engaged in protecting Java from all sorts of attacks, I pressed the fact that strong typing allows you to write huge applications. At that time, the development of monolithic systems was in progress (there were no microservices, there was no Docker, and so on). Since Java is a strongly typed language, the Java compiler helps the programmer avoid many problems by preventing him from compiling the wrong code.

JavaScript, unlike Java, is not distinguished by strong typing. From this we can make an obvious conclusion that the programmer does not know exactly which objects he has to work with. How does a programmer know what to do, for example, with some object obtained from somewhere?

The reverse side of strong Java typing is the need to constantly perform template actions. The programmer constantly performs type casting or checks that everything is exactly as expected. The developer spends time writing code, does it with exceptional accuracy, uses considerable amounts of patterned constructions, and hopes that all this will help him save time by detecting and correcting early errors.

The problem of programming in a language with strict typing is so great that a programmer, with virtually no options, has to use a large, complex IDE. A simple code editor is not enough here. The only way to keep a Java programmer in adequate condition (with the exception of pizza) is to constantly show him drop-down lists containing the available fields of objects or descriptions of method parameters. This and other supporting mechanisms of IDEs such as Eclipse, NetBeans, or IntelliJ help in creating classes, facilitates refactoring and solving other problems.

And ... I will not talk about Maven. This is just a dreadful tool.

In JavaScript, variable types are not specified when they are declared, type casting is usually not used, and so on. As a result, the code is easier to read, but this state of affairs also means the risk of programming errors that are difficult to detect.

Whether the above applies to Java pluses or minuses depends on the point of view.

Ten years ago, I thought that all these difficulties justify themselves by giving the programmer a lot of confidence in the code he writes. Today, I believe that strict typing increases the amount of work a programmer has and projects are much easier to develop in the same way as in JavaScript.

Fighting bugs with small modules that are easy to test


Node.js pushes the programmer to split his projects into small fragments, into so-called modules. Perhaps this fact will seem insignificant to you, but it partially solves the problem we just mentioned.

Here are the main characteristics of the module:

  • Independence. The module combines interconnected code into a single entity.
  • Clear boundaries. The code inside the module is protected from interference by any external mechanisms.
  • Explicit export. By default, the module code and data are not exported. The developer independently decides which functions and data should be made publicly available.
  • Explicit import. When developing a module, a programmer decides on which modules it will depend on.
  • Potential independence. Modules can be made publicly available, in a very broad sense of the word, by publishing them in npm, or, if they are intended for the internal needs of the company, by publishing in closed repositories. This makes it easy to use the same modules in different applications.
  • Easy to understand code. The fact that the modules are small in size makes it easier to read and understand their code, it opens the way for free discussion about them.
  • Facilitate testing. A small module, if implemented correctly, is easily modularly tested.

All this makes Node.js modules entities with clearly defined boundaries whose code is easy to write, read, and test.

However, anxiety when working with JavaScript is caused by the fact that the lack of strong typing can easily lead to the fact that the code will do something wrong. In a small module aimed at solving a narrow problem with clear boundaries, “something is not right” can affect only the code of the module itself. This leads to the fact that problems that can be caused by the lack of strong typing turn out to be locked in the module boundaries.

Another solution to the problem of dynamic typing in JavaScript is to thoroughly test the code.

A developer has to take a serious approach to testing, which takes away from him some of the benefits that come from the simplicity of the JS development process. Testing systems created by a JS programmer should find the errors that, developed by something like Java, could automatically find a compiler. You are writing tests for your JS applications?

For those who need a static typing system in JavaScript, it may be helpful to have a look at TypeScript. I do not use this language, but I have heard many good things about it. It is compatible with JavaScript and extends the language with a type control system and other useful features.

As a result, we can say that using a modular approach to development is the strength of Node.js and JavaScript.

Package management


I feel bad at the thought of Maven, so I can't even write about it normally. And, as I understand it, Maven, without compromise, either love or hate.

The problem here is that in the Java environment there is no complete package management system. Maven packages exist, you can work with them normally, they are supported by Gradle. But how work with them is organized is not close to what the package management system for Node.js gives the developer.

In the world of Node.js, there are two excellent package managers that work closely with each other. At first, the only such tool was the npm repository and the command line tool of the same name.

Thanks to npm, we have a great scheme for describing package dependencies. Dependencies can be strict (for example, it is indicated that version 1.2.3 of a certain package is needed only), or specified with several degrees of freedom - up to *, which means using the most recent version of a certain package.

The Node.js community has published hundreds of thousands of packages in the npm repository. In this case, using packages that are not in npm is as easy as packages from npm.

The npm system was so successful that not only Node.js server products developers use it, but also front-end programmers. Previously, there were tools like Bower used to manage packages. Bower was considered obsolete, and now you can find that all JS libraries for frontend development exist in the form of npm packages. Many support tools for client development, like the Vue.js CLI and Webpack, are written as Node.js applications.

Another package management system for Node.js, yarn, loads packages from the npm repository and uses the same configuration files. The main advantage of yarn over the npm package manager is its higher speed.

The npm repository, regardless of whether you are working with it using the npm package manager or using the yarn package manager, is a powerful basis for making development for Node.js so simple and enjoyable.


Once, after I helped in the development of java.awt.Robot, I was inspired to create this thing. While the official Duke image consists of curves, RoboDuke is built from straight lines. Only the elbow joints of this robot are round

Performance


Both Java and JavaScript have been criticized for their poor performance. In both cases, the compiler converts the source code of the program into a bytecode executed on a virtual machine implemented for a specific platform. The virtual machine, in turn, converts the bytecode into machine code using various optimizations.

Both Java and JavaScript have reasons to strive for high performance. If we talk about Java and Node.js, then they have in common the desire for fast server code. In the case of browser-based JavaScript, the incentive for high performance is improving the quality of client applications. We will talk about this in the section on rich Internet applications.

Sun / Oracle JDK uses HotSpot — a virtual machine that supports many bytecode compilation strategies. The name of this virtual machine hints at the optimization technique, during the implementation of which the code that is executed most often is detected, and more and more optimizations are being applied to such code, the more intensively it is used. HotSpot is a highly optimized system that produces very fast code.

If we talk about JavaScript, before we wondered about how you can expect from the JS-code running in the browser, the capabilities necessary to implement any complex applications. It was believed that on browser JavaScript it would be almost impossible to create something like a set of traditional office applications. Today, in order to prove that this is possible, it is not necessary to go far. For example, I write this material in Google Docs, and I am completely satisfied with the performance. The speed of the browser JS is improving every year.

Node.js follows the same direction, as it uses the Google Chrome V8 engine.

An example of this isspeech by Peter Marshall, a Google engineer who is working on a V8, and whose main task is to improve the performance of this engine. Here he talks about why the V8 switched from Crankshaft to Turbofan.

Machine learning is an area in which heavy computations are used, for which you usually use R or Python languages. Machine learning, like some other areas, needs tools to quickly perform numerical calculations. Here, JavaScript, for various reasons, is not particularly strong, but work is underway to create a standardized library for organizing numerical computations in JavaScript.

From thisVideo can be learned about using a new library in JavaScript, TensorFlow.js. The API of this library is similar to the TensorFlow API for Python, it supports the import of pre-trained models. This library can be used, for example, to analyze a video in order to recognize objects, with all the necessary calculations performed in the browser.

Here isChris Bailey from IBM, where he touches on the performance and scalability of Node.js, particularly when using Docker / Kubernetes based configurations. He begins the story by reviewing a set of benchmarks that demonstrate Node.js significantly better performance compared to Spring Boot. We are talking about the bandwidth of the I / O subsystem, the time of launch of the application and the memory consumption. Moreover, from release to release, the performance of Node.js is seriously improved, in part due to improvements made in V8.

Here Bailey says that Node.js is not suitable for performing intensive calculations. It is important for us to understand the reason for this recommendation. Because Node.js uses a single-threaded code execution model, lengthy calculations block event processing. I, in my book “Node.js Web Development”, touch upon this problem, citing three approaches to its solution:

  • Algorithm refactoring - identifying the slower parts of the algorithm and refactoring it to achieve higher speeds.
  • Splitting the computation process into small fragments using an event dispatcher, which allows Node.js to regularly return to the main thread.
  • Transfer of heavy computing tasks to an auxiliary server.

If JavaScript performance is not enough for your tasks, take a look at the following two ways to integrate native code into Node.js. The easiest way is to use native Node.js modules. The toolkit for Node.js development contains a tool node-gypthat helps you work with such modules. Here is a video that demonstrates the integration of the Rust library with Node.js.

WebAssembly allows you to compile programs written in different languages ​​into a subset of JavaScript that has a very high execution speed. WebAssembly represents the code that runs inside the JavaScript engine. In this video given a good overview of the technology and demonstrates the use of WebAssembly in Node.js. environment

Rich internet applications


Saturated Internet applications (Rich Internet Applications, RIA) were widely known ten years ago. Then they talked about the fact that they, implemented on the basis of fast (for their time) JS engines, are able to make traditional desktop applications irrelevant.

In fact, this story began more than 20 years ago. Sun and Netscape agreed to use Java applets in Netscape Navigator. JavaScript was, in part, designed as a scripting language for Java applets. At that time, the industry hoped that Java servlets would be used on servers, and Java applets on clients. As a result, developers will find themselves in a wonderful situation, when it will be possible to write in the same language for both servers and clients. This did not happen for various reasons.

Ten years ago, JavaScript was powerful enough for complex applications to be implemented solely by its means. As a result, the term RIA appeared fashionable then, and it was expected that rich Internet applications would kill Java as a platform for client web applications.

Today we are starting to see how the idea of ​​the RIA began to bear fruit. Thanks to Node.js, the situation that was desired by many people became real when the same language was used on the server and on the client. Only now is javascript.

Here are some examples:

  • A set of Google Docs applications (used to write this article), which is very similar to a typical office suite, but running in a browser.
  • Powerful frameworks like React, Angular and Vue.js simplify the development of browser based applications based on HTML, CSS and JavaScript.
  • Electron is a mix of Node.js and the Chromium browser. This framework is designed to develop cross-platform desktop applications. With its use, such highly popular and high-quality applications as Visual Studio Code, Atom, GitKraken, and Postman are created.
  • Since Electron / NW.js uses a browser engine, web frameworks such as React, Angular, and Vue can be used to develop desktop applications .

Java technology, as a platform for developing desktop applications, did not die because of rich Internet applications written in JavaScript. She died predominantly due to a lack of client technology at Sun Microsystems. Sun focused on corporate users who need high performance server applications. I saw it all with my own eyes. The real killer of Java applets was a security issue that was discovered several years ago in a Java plugin and in Java Web Start. This has led to a worldwide reduction in the use of Java applets and Webstart applications.

Other types of desktop applications can still be developed in Java, and as a result, NetBeans IDE and Eclipse are still struggling with each other. However, there is stagnation in this area of ​​Java application, and very few Java-based applications exist outside of development tools.

The exception is JavaFX technology.

JavaFX technology, 10 years ago, was planned as Sun’s response to the emergence of the iPhone. It was planned that this technology will allow the development of applications with a rich graphical interface on the Java platform available on mobile devices. This would allow Flash and application development tools for iOS to be removed from the game in one fell swoop. But nothing happened. JavaFX is still in use, but this technology has not seen such a massive take-off that its creators expected. Nowadays, web frameworks like React, Vue.js and the like attract everyone's attention.

In this situation, JavaScript and Node seriously overtook Java.

Here is a photo of a Java ring that was somehow distributed at a JavaONE conference. Such rings contained a chip with a full implementation of Java on board. They were mainly used to unlock computers installed in the lobby.


Ring Java


Instructions to the ring

Results


These days, server project developers have plenty to choose from. The industry is no longer limited to the so-called “P-languages” (Perl, PHP, Python) and Java, since now there is the Node.js platform, there are the Ruby, Haskell, Go, Rust, and many other languages. As a result, server programmers now have just a huge selection of great technologies.

If we talk about why I, a person who lived exclusively in Java, switched to the side of Node.js, then it is clear that I was attracted by the freedom that is characteristic of Node.js development. The Java ecosystem has become a burden, and when using Node.js, nothing like that is felt. Of course, if I, by work, have to write in Java, I will complete this task.

Each application has its own needs. And, of course, it is wrong to always and exclusively use Node.js solely because of the fact that someone likes this platform. The choice of a particular language or framework should be determined by technical considerations. For example, I recently had to work with XBRL documents. Since the best libraries for working with XBRL are written in Python, in order to use them, you need to know Python. Therefore, choosing the technology, you need to sensibly evaluate the real tasks of the projects and dwell precisely on what is best suited to solve these problems.

Dear readers! If you, like the author of this article, switched to JavaScript from some other language, or changed some server platform to Node.js, we ask you to tell about it in a few words.


Also popular now: