Messaging naturally provides high availability, scalability, location transparency, prioritization, stage transactionality, fault tolerance, great monitoring, simple error handling, and efficient and flexible resource management. However, messaging can be much harder to employ in practice due to a fundamentally different programming model.

Mats3 is a client side Java library that makes asynchronous message-oriented interservice communications mimic a synchronous, blocking and linear/sequential way of coding, greatly reducing the mental shift and cognitive load, and increases developer productivity when using messaging. You gain all the positives of messaging, while virtually eliminating the negatives.

ISC using HTTP

Using synchronous protocols like HTTP as your inter-service communications layer in a multi-service architecture, you naturally get a “top-to-bottom” sequential code style, since HTTP itself is a blocking protocol: When you need to communicate with a collaborating service, you open a TCP connection to a server, send your request, and block while waiting for the response. You eventually get the response, at which point your thread continues the process.

If you had some necessary variables present before the request, these are naturally still present after the response has come back, so you can calculate your result based on the sum of information. You can then finish of the process, which might be to return the response to whoever asked (and thus blocked on you).

ISC using ordinary Messaging

In a messaging-based, asynchronous, stateless architecture, you’re in a different situation. A receiver typically picks up a message from an incoming queue, performs the necessary actions, and finishes its part of the job - which may involve putting the result on a different outgoing queue. The executing thread then typically goes back to picking up a new message from its incoming queue.

To follow the process, you’ll need to find the receiver that listens to the previous stage’s outgoing queue. This might reside in a different codebase. If you have state outside the request that needs to be present for downstream processing, this either needs to be put on some shared storage, or sent along with the messages for the downstream stages. The distribution of code and logic complicates reasoning, system comprehension, and reusability.

ISC using Mats3

But, what if you in an Endpoint could simply invoke a request(..)-method, and have the corresponding Reply message show up in a code block right below? And where any state present in the first code block, still is present in the second code block? And you didn’t have to think about JMS Consumers, Producers, Messages or other intricacies of the messaging API? All while these codeblocks are independent stateless and transactional consumers of message from independent message queues, where it doesn’t matter if the second code block ends up executing on a different node than the first?

That is, something like this (code):

class Example {
    void setupMainMultiStagedService(MatsFactory matsFactory) {
        // Create three-stage "Main" Endpoint
        var ep = matsFactory.staged(ENDPOINT_MAIN, MainRequestDto.class, State.class);

        // Initial Stage, receives incoming message to this Main service
        ep.stage(MainRequestDto.class, (context, state, dto) -> {
            // State object is empty at initial stage.
            Assert.assertEquals(0, state.number1);
            Assert.assertEquals(0, state.number2, 0);
            // Setting some state variables.
            state.number1 = Integer.MAX_VALUE;
            state.number2 = Math.E;
            // Perform request to "Mid" Endpoint...
            context.request(ENDPOINT_MID, new MidRequestDto(dto.number, dto.string));
        });
        ep.stage(MidReplyDto.class, (context, state, dto) -> {
            // .. continuing after the Mid Endpoint has replied.
            // Assert that state variables set in previous stage are still with us.
            Assert.assertEquals(Integer.MAX_VALUE, state.number1);
            Assert.assertEquals(Math.E, state.number2, 0);
            // Changing the state variables.
            state.number1 = Integer.MIN_VALUE;
            state.number2 = Math.E * 2;
            // Perform request to "Leaf" Endpoint...
            context.request(ENDPOINT_LEAF, new LeafRequestDto(dto.number, dto.string));
        });
        ep.lastStage(LeafReplyDto.class, (context, state, dto) -> {
            // .. continuing" after the Leaf Endpoint has replied.
            // Assert that state variables changed in previous stage are still with us.
            Assert.assertEquals(Integer.MIN_VALUE, state.number1);
            Assert.assertEquals(Math.E * 2, state.number2, 0);
            // Return result to caller
            return new MainReplyDto(dto.number * 5, dto.string + ":FromMainService");
        });
    }

    // State class
    static class State {
        int number1;
        double number2;
    }

    // DTOs omitted
}

Each of the three lambdas are independent message consumers, consuming from three different queues, but you can mentally view them as a single Endpoint which executes linearly, and as part of the processing invokes two external Endpoints.

This is what Mats3 enables.

Mats3 stands for Message-oriented Asynchronous, Transactional, Staged, Stateless Services!
Mats3 is Messaging with a call stack!
Mats3 is Message-Oriented Asynchronous RPC!

There’s a bit more about the rationale for Mats, and the benefits of messaging-based architectures here, and some further musings on what Mats is here

So, how to start Mats Flows? Go to the next chapter!

Updated: