# Asynchronous programming concepts

There are basically two kinds of processing; synchronous and asynchronous. Synchronous processing blocks the current Thread until processing is complete. Asynchronous processing doesn't block the current Thread, instead of waiting, Thread will work on other tasks in the mean time. Asynchronous applications are more difficult to design, but they can handle thousands of parallel requests, while synchronous applications are limited by the maximum number of Threads that can be run. There are many different forms and implementations of asynchronous processing in Java, such as:

To summarize the essence of "Moleculer for Java" in a similar way, we could describe this:

# JavaScript and Java parallels

Java and Node.js-based Moleculer have the same internal architecture as possible for these two languages. Most internal objects and properties have the same names in both implementations. After a relatively short period of time, Java programmers understand the code of a Node.js-based Moleculer service and vice versa. This help to increase the skills of the programmers and make their collaboration more efficient.

Most Moleculer modules do not require any specific explanation, they are quite similar in use in the two programming languages (such modules include Cachers, Transporters, Serializers and other high-level "building blocks"). However, a low-level "building block", the dynamic JavaScript object, cannot be easily implemented in a statically-typed language. The next chapter is about how to effectively handle the JavaScript/JSON data structures in Moleculer Java, and how to organize asynchronous "JSON functions" into a workflow.

# DataTree API for JavaScript objects

The Services send packets of structured hierarchical data to each other during communication. Because the Services can be remotely hosted, services send and receive JSON data during their communication. In the Node.js-based Moleculer implementation, the transferable data corresponds to JavaScript objects. A JavaScript object is a collection of named values, eg.:

// JavaScript code
var params = {param1: "value1",
              param2: "value2",
              param3: 12345678,
              param3: true};

There is no similar native support for dynamic creation of JSON objects in Java language. Because of this, Moleculer uses an abstract API instead of a certain JSON implementation. The io.datatree.Tree object is an abstract layer that uses an arbitrary JSON implementation. Tree API supports 18 popular JSON implementations (eg. Jackson, Gson, Boon, Jodd, FastJson), and 10 non-JSON data formats (YAML, ION, BSON, MessagePack, etc.). Java-based JSON (and non-JSON) APIs are constantly evolving, so no specific implementation is forced on developers. The following Java code snippet builds similar JSON structure like the previous JavaScript code:

// Java code
Tree params = new Tree();
params.put("param1", "value1");
params.put("param2", "value2");
params.put("param3", 12345678);
params.put("param4", true);

The following Java statement...

System.out.println(params);

... will print this:

{
  "param1" : "value1",
  "param2" : "value2",
  "param3" : 12345678,
  "param4" : true
}

In addition, the Tree API provides some useful features:

  • JSON path functions (tree.get("cities[2].location"))
  • Easy iteration over Java Collections and Maps (for (Tree child: parent))
  • Recursive deep cloning (Tree copy = tree.clone())
  • Support for all Java types of Appache Cassandra (BigDecimal, UUID, InetAddress, etc.)
  • Support for all Java types of MongoDB (BsonNumber, BsonNull, BsonString, BsonBoolean, etc.)
  • Root and parent pointers, methods to traverse the data structure (tree.getParent() or tree.getRoot())
  • Methods for type-check (Class valueClass = tree.getType())
  • Methods for modify the type of the underlying Java value (tree.setType(String.class))
  • Method chaining (tree.put("name1", "value1").put("name2", "value2"))
  • Merging, filtering structures (tree.copyFrom(source), tree.find(condition), etc.)

In summary, Node.js-based Moleculer Services sends and receives JavaScript objects. The equivalent is the Tree object in the Java-based Moleculer.

# Avoid reflection

The Reflection API is a powerful feature of the Java language. With the API, the Java program can create an object or call a method on the fly. From execution prospective, the calls to reflection API are quite expensive, it could have a performance impact on the applications. Because of this, Moleculer uses the reflection API in very few cases. For example Actions and event Listeners are not methods but Functional Interfaces. Calling them is much faster than calling methods using the Reflection API.

// Actions and Listeners are Functional Interfaces
Action action = ctx -> { ... };
Listener listener = ctx -> { ... };

# No object mapping

For the sake of simplicity, and similarity to Node.js version, Moleculer does not use Java Object Mappers. The data is received, processed and returned in "raw" JSON format. Object Mapper's are useful when starting the system, and we process configuration files using the Spring Framework, for example. However, it is faster at runtime if the incoming data packet is received immediately after parsing it from the binary (JSON, MessagePack, etc.) format. The Tree data type helps in accurate type conversion, even allowing you to specify default values.

// Input and output data are in "raw" JSON format
Action action = ctx -> {

    // Process input:
    // {
    //     "key1": "abc",
    //     "key2": 12345
    // }
    String value1 = ctx.params.get("key1", "default");
    double value2 = ctx.params.get("key2", 0d);

    // Generate output:
    // {
    //     "result": "ok"
    // }
    Tree out = new Tree();
    out.put("result", "ok");
    return out;
};

The input for Action and event Listener is always a Context (which has metadata besides input JSON, such as who sent the message). The returned value cannot be a POJO (Plain Old Java Object with getter/setter methods), such values must be converted to a Tree object. The output can be one of the following:

  • null
  • String
  • Numbers: byte, short, int, long, float, double, BigDecimal, BigInteger
  • boolean
  • byte array
  • java.util.Date
  • java.util.UUID
  • java.net.InetAddress
  • Tree object (hierarchical data structure from the above types)
  • Promise object (it's like an asynchronous Tree)
  • PacketStream object (for transferring large, binary files)

# Non-blocking JSON processing

Moleculer uses ES6-like Promises (based on the Java8's CompletableFuture API) to avoid callback hell. An io.datatree.Promise is an object that may produce a simple value (or a Tree object) some time in the future: either a resolved value, or a reason that it's not resolved (e.g., a network error occurred). Promise users can attach callbacks to handle the fulfilled Tree or the reason for rejection.

The main difference between Promise-based operation of other systems and Moleculer is that the Moleculer Promise object works with "raw" JSON objects. The value of a Moleculer Promise, which you get after the asynchronous processing, is always a Tree object. This Tree structure may come from other Services or from asynchronous APIs.

// Sequential "waterfall" processing of Promises
Action anAction = ctx -> {

    // Invoke the firs async method; this method returns a Promise,
    // the "next" is called when the Promise value is assigned
    return callAsyncMethod1(ctx)
      .then(rsp -> { // Response of previous call (in a "Tree" object)

        // Process response and call next async method
        return callAsyncMethod2(rsp);

    }).then(rsp -> { // Response of previous call

        // Process response and call next async method
        return callAsyncMethod3(rsp);

    }).catchError(err -> { // Throwable of previous calls

        // Handle error and call next async method
        return callAsyncMethod4(err);

    }).then(rsp -> { // Response of previous call

        // Process response and call last async method
        return callAsyncMethod5(rsp);
    });
}

Read more about Promises or continue to the next chapter.