{"id":25776044,"url":"https://github.com/centiservice/mats3","last_synced_at":"2026-01-03T11:21:18.756Z","repository":{"id":42665043,"uuid":"372971426","full_name":"centiservice/mats3","owner":"centiservice","description":"Mats3: Message-based Asynchronous Transactional Staged Stateless Services","archived":false,"fork":false,"pushed_at":"2024-05-07T07:54:37.000Z","size":2200,"stargazers_count":59,"open_issues_count":43,"forks_count":4,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-05-07T18:23:14.306Z","etag":null,"topics":["java","mats3","messaging","messaging-api","messaging-library","messaging-system","microservices","spring"],"latest_commit_sha":null,"homepage":"https://mats3.io/","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/centiservice.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-06-01T22:00:46.000Z","updated_at":"2024-06-16T12:42:50.218Z","dependencies_parsed_at":"2024-03-18T00:28:13.919Z","dependency_job_id":"0cb55701-1604-45f6-b2f2-af4038f34b40","html_url":"https://github.com/centiservice/mats3","commit_stats":null,"previous_names":[],"tags_count":39,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/centiservice%2Fmats3","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/centiservice%2Fmats3/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/centiservice%2Fmats3/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/centiservice%2Fmats3/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/centiservice","download_url":"https://codeload.github.com/centiservice/mats3/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240987435,"owners_count":19889333,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["java","mats3","messaging","messaging-api","messaging-library","messaging-system","microservices","spring"],"created_at":"2025-02-27T06:01:16.751Z","updated_at":"2026-01-03T11:21:18.688Z","avatar_url":"https://github.com/centiservice.png","language":"Java","funding_links":[],"categories":["微服务库"],"sub_categories":[],"readme":"# Mats\u003csup\u003e3\u003c/sup\u003e - Message-based Asynchronous Transactional Staged Stateless Services\n\n*Introducing \"MOARPC\" - Message-Oriented Asynchronous Remote Procedure Calls!*\n\nWebpage: https://mats3.io/\n\n\u003cimg src=\"docs/img/Mats3Logo-text-to-path.svg\" alt=\"Mats3 logo\" width=\"100\"/\u003e\n\n*Mats\u003csup\u003e3\u003c/sup\u003e* is a Java library that facilitates the development of asynchronous, stateless (or stateful, depending\non your point of view), multi-stage, message-based services. Mats Endpoints immediately provide all the benefits you get\nfrom a fully asynchronous messaging-based architecture, while being *almost* as simple to code as blocking, synchronous\nJSON-over-HTTP \"REST\" endpoints.\n\n*To explore the library, check out the [Explore Mats\u003csup\u003e3\u003c/sup\u003e](https://mats3.io/explore/) page. In particular, there\nis [tooling for Mats\u003csup\u003e3\u003c/sup\u003e using JBang](https://mats3.io/explore/jbang-mats/): JBang makes it possible to make\nsmall self-executable source files. With the all-in-one `mats-jbangkit` dependency, you can literally pull up a Mats\nEndpoints in 10 lines, and start an ActiveMQ Classic instance with a single command: `jbang activemq@centiservice`. Also, the\nsource is a good place to learn about Mats\u003csup\u003e3\u003c/sup\u003e, explained [here](https://mats3.io/explore/mats-source-code/).*\n\nTo use Mats in a project, fetch [`mats-impl-jms`](https://mvnrepository.com/artifact/io.mats3/mats-impl-jms)\nfrom [Maven Central](https://mvnrepository.com/artifact/io.mats3).\n\nRuns on Java 11+. _(Developed on Java 11, runs in prod on 11, 17 and 21)_\n\nLicense: [Polyform Perimeter 1.0.0 with examples](LICENSE.md)\n\nIf you find Mats interesting, you might want to check out the \"companion\nproject\" [MatsSocket](https://github.com/centiservice/matssocket).\n\n# Documentation\n\nGo to the [Documentation](https://mats3.io/docs/) page of [mats3.io](https://mats3.io/).\n\n# What is Mats?\n\nThere's a document going into details [here](https://mats3.io/background/what-is-mats/), and for the rationale behind\nMats [here](https://mats3.io/background/rationale-for-mats/).\n\nDescription of what Mats is, in multiple short forms, from several different angles:\n\n### A picture is worth...\n\n![Standard Example Mats Flow](docs/img/StandardExampleMatsFlow-halfsize-pagescaled.svg)\n\nThe above illustration can be viewed as REST-endpoints invoking other REST-endpoints, the dotted lines representing\nblocking waits on the other endpoints responses. Or it can be viewed as Mats Endpoints (light blue boxes) invoking other\nMats Endpoints, the dotted lines representing the state which are \"magically\" present between the Stages (dark blue\nboxes) of the Endpoints, but which are passed along with the message envelopes. The full execution from Initiator to\nTerminator is called a _Mats Flow_.\n\nUnit test code for this setup can be\nfound [here](mats-api-test/src/test/java/io/mats3/api_test/stdexampleflow/Test_StandardExampleMatsFlow.java).\n\n### Better Inter Service Communication with Messaging\n\nIn a system made up of multiple services, e.g. a microservice architecture, asynchronous messaging gives many benefits\ncompared to the more classical synchronous JSON-over-HTTP-based communications.\n\nMats lets you use messaging without having to massively change your mental model of how to create inter service\nendpoints: You might code your Mats Endpoint to receive a request, invoke a few collaborating services, calculate the\nresult based on values both from the request and the responses from the services you invoked, and then return the\nresult.\n\n### Multi-stage, message-based Endpoints\n\nA Mats Endpoint may consist of multiple _Stages_, where each Stage is an independent consumer and producer of\nmessages, each stage consuming from a stage-specific queue on a message broker. The message queue name is simply a\ndirect mapping from the StageId, prefixed with (default) \"mats.\".\n\n### Invokable message-based Endpoints\n\nA Mats Endpoint's Stage may invoke another Mats Endpoint by issuing a Request-call (`processContext.request(..)`)\ntargeting the EndpointId of the Endpoint you want to invoke. The Reply from this Request will be received by next Stage.\n\n### Messaging with a Call Stack!\n\nInvoked Mats Endpoints does not need to know who called them, they just return their result which is packaged up in a\nmessage called a Reply. The Stage that performs the invocation doesn't have to specify which StageId the Reply should go\nto, as Mats already knows this: It is the next stage of the Endpoint. The invoked Endpoint doesn't have to look up\nthe Reply queue address, as it is automatically fetched from the message's envelope's call stack.\n\nWhen a Stage N performs such a Request to another Mats Endpoint, a State Object is left behind on the call stack,\nwhich carries the information that is needed from Stage N to Stage N+1. When Stage N+1 receives the Reply from the\ninvoked Endpoint, the state object which was present on Stage N will \"magically\" be provided.\n\nThere is no theoretical limit to the nesting level of the call stack - but there is a \"stack overflow\" protection\nmechanism which kicks in if an Endpoint for example invokes itself.\n\n### Stateless\n\nThe entire state of a _Mats Flow_ is kept in the messages' envelope. No state is kept on the service instances\n(nodes / pods / hosts / servers - whatever unit carries the service instances' JVMs).\n\nThis means that a service instance may be shut down without any consequences, and without the need of taking it out of\nany cluster or load balancer - it simply disconnects from the message broker. If there are multiple instances, the other\ninstances will just continue consuming from those queues as if nothing has happened (they will get some more load than\nwhen they shared it with the now-offline instance). If there are no instances of this service left, inbound messages\ntargeting this service's Endpoints' Stages will just be queued up on the broker's queues - and when a service instance\ncomes back online, its Mats Endpoints will start consuming again, and the Mats Flows will continue as if nothing\nhappened. This makes maintenance, high availability, and deployment of new versions of the services effortless.\n\n### Stateful\n\nThe multiple Stages of a Mats Endpoint are connected with a State Object, which are meant to emulate the local variables\nyou'd have in the handling method of an ordinary HTTP-endpoint which performs synchronous HTTP calls to other services.\n\n### Asynchronous\n\nWhen a Stage _N_ of some Endpoint performs a request to another Endpoint, no thread will explicitly be waiting for the\nReply. The thread that executed Stage N for this Flow will go back to pick up new messages for Stage N. When the\nrequested Endpoint eventually replies, the Reply will be put on the incoming queue for Stage _N+1_, where another\nthread, possibly on another node, will pick it up and continue the execution for this Flow.\n\n### Distributed\n\nA Mats Flow's execution will be spread out in pieces - over the Stages of the Endpoints involved in the Flow, and over\nthe instances of the services that holds those Endpoints. The Mats Flow weaves a single thread through the\n_Mats Fabric_, jumping from StageProcessor to StageProcessor through the different instances of the services. However,\nthis is just an emergent property of the system: When you code an Endpoint, you code and reason locally within that\nEndpoint.\n\nEach Stage's concurrency, i.e. the number of StageProcessors running, can be tuned independently to handle the total\nload of all the Mats Flows. If there is a choke point, you'll find it using metrics: There will typically be a build-up\nof messages on that Stage's queue (which also makes the head-of-queue message old). Intermittent small build-ups are\ntotally fine if the overall total latency of the involved Mats Flows doesn't become a problem - but otherwise you may\nincrease the concurrency for this particular Stage, or even increase the number of instances of the service holding the\nproblematic Endpoint. Or, it might be external resources at this stage making the problem, e.g. the database which is\nemployed: The query might be slow, or the database isn't beefy enough - this you will understand by looking at the stage\nmetrics, the StageProcessing being slow.\n\n_Interactive_ Mats Flows, defined as \"a human is waiting for the result\", are given a \"cut the line\"-flag, and it\ncarries this through all the stages it passes through. This means that even if a massive batch of Mats Flows is\ncurrently being processed with queues forming on several high-demand Endpoints, a user logging in to your system's user\nportal, or looking up a customer on the backoffice application, will still get \"immediate\" response, pretty much as if\nthere was no load.\n\n### Centralized error handling\n\nIf a Mats Flow, while weaving its line through the Mats Fabric, encounters an error, the message broker initiates\nautomatic retrying. But if this doesn't pan out either, the current message will \"pop out\" of the Mats Fabric, ending up\non the Dead Letter Queue. (The broker should preferably be set up to have individual DLQs per queue). This is monitored\non the message broker.\n\nThis makes the total system exceptionally distributed, but the _error handling_ is centralized.\n\nThere is a separate project providing a Mats-specific view of the queues and DLQs on the message broker:\n[MatsBrokerMonitor](https://github.com/centiservice/matsbrokermonitor).\n\n### Familiar feel\n\nThe hope is that coding a message-based Mats Endpoint _feels like_ coding an ordinary HTTP-based endpoint where you may\nsynchronously invoke other endpoints as part of the processing. You code \"linearly\" and reason locally - but you gain\nall features of a fully Asynchronous Message Oriented Architecture.\n\n### Production Ready\n\nThis code has for several years been running in production as the sole inter service communication layer in a quite\nlarge financial system consisting of \u003e50 services and applications and several hundred Mats Endpoints. This system has\nmultiple customer frontends, both web and apps, and several backend UIs for backoffice functions. Several million\nmessages are produced and consumed each day. The Message Broker in use in this production setup is Apache ActiveMQ Classic, but\nall unit and integration tests also run on Apache ActiveMQ Artemis (formerly JBoss HornetQ, and what Red Hat AMQ 7+ is built on).\n\nFor more, read [this](docs/WhatIsMats.md), then [this](docs/RationaleForMats.md).\n\n## What Mats is not\n\nMats is not meant for e.g. stream processing of large amounts of small events. Mats is geared more towards \"weighty\"\nprocesses and messages, and trades some performance for developer friendliness, transactionality and debugging features.\nSolutions like Kafka might be what you want if you need to receive tens-of-thousands of events per second from your\nmassive fleet of IoT devices. Use Mats for the subsequent, more coarse-grained handling of the results of such\ningestion. _(That said, the throughput of a large Mats fabric with lots of Endpoints scales very well, and is only\nlimited by the throughput of your message broker setup as well as external resources as your services' databases)_\n\nAlso, Mats is not meant for events which require handling in a specific order, e.g. arrival order. The intention with\nMats is that you run multiple instances/replicas of each service. And on each instance, there are multiple\nStageProcessors for each Stage. A Mats Flow typically consist of processing on multiple Endpoints with multiple Stages,\nand there is no way to guarantee that two flows which are started in some close order, will run through those stages,\nand finish in the same order they were started.\n\n# Examples\n\nSome examples taken from the [unit tests](mats-api-test/src/test/java/io/mats3/api_test). Notice the use of the JUnit\nRule `Rule_Mats`, which sets up an ActiveMQ Classic in-memory server and creates a JMS-backed MatsFactory based on that.\n\nIn these examples, all the endpoints and stages are set up in one test class, and when invoked by the JUnit runner,\nobviously runs on the same machine - but in actual usage, you would typically have each endpoint run in a different\nprocess on different nodes. The DTO-classes, which acts as the interface between the different endpoints' requests and\nreplies, would in real usage be copied between the projects (in this testing-scenario, one DTO class is used as all\ninterfaces).\n\nIt is important to appreciate that each of the stages - also each stage in multi-stage endpoints - are handled by\nseparate threads. The state, request and reply objects are not shared references within a JVM, as they are marshalled\nwith the message that are passed between each stage of the endpoints. *All information for a process (a Mats Flow)\nresides as contents of the Mats envelope which is being passed with the message.* In particular, if you have a process\nrunning a three-stage Mats endpoint which is deployed on two nodes A and B, for a particular request, the first stage\nmight execute on node A, while the next stage on node B, and the last stage on node A again.\n\nThis means that what *looks like* a blocking, synchronous request/reply \"method call\" is actually fully asynchronous,\nwhere a reply from an invoked service will be handled in the next stage by a different thread, quite possibly on a\ndifferent node if you have deployed the service in multiple instances.\n\n## Simple send-receive\n\nThe following class exercises the simplest functionality: Sets up a Terminator endpoint, and then an initiator sends a\nmessage to that endpoint. *(This example neither demonstrate the stack (request/reply), nor state keeping - it is just\nthe simplest possible message passing, sending some information directly from an initiator to a terminator endpoint)*\n\nASCII-artsy, it looks like this:\n\u003cpre\u003e\n[Initiator]   {sends to}\n[Terminator]\n\u003c/pre\u003e\n\n```java\npublic class Test_SimplestSendReceive {\n    private static final Logger log = MatsTestHelp.getClassLogger();\n\n    @ClassRule\n    public static final Rule_Mats MATS = Rule_Mats.create();\n\n    private static final String TERMINATOR = MatsTestHelp.terminator();\n\n    @BeforeClass\n    public static void setupTerminator() {\n        // A \"Terminator\" is a service which does not reply, i.e. it \"consumes\" any incoming messages.\n        // However, in this test, it countdowns the test-latch, so that the main test thread can assert.\n        MATS.getMatsFactory().terminator(TERMINATOR, StateTO.class, DataTO.class,\n                (context, sto, dto) -\u003e {\n                    log.debug(\"TERMINATOR MatsTrace:\\n\" + context.toString());\n                    MATS.getMatsTestLatch().resolve(sto, dto);\n                });\n    }\n\n    @Test\n    public void doTest() {\n        // Send message directly to the \"Terminator\" endpoint.\n        DataTO dto = new DataTO(42, \"TheAnswer\");\n        MATS.getMatsInitiator().initiateUnchecked(\n                (msg) -\u003e msg.traceId(MatsTestHelp.traceId())\n                        .from(MatsTestHelp.from(\"test\"))\n                        .to(TERMINATOR)\n                        .send(dto));\n\n        // Wait synchronously for terminator to finish. NOTE: Such synchronicity is not a typical Mats flow!\n        Result\u003cStateTO, DataTO\u003e result = MATS.getMatsTestLatch().waitForResult();\n        Assert.assertEquals(dto, result.getData());\n    }\n}\n```\n\n## Simple request to single stage \"leaf-service\"\n\nExercises the simplest request functionality: A single-stage service is set up. A Terminator is set up. Then an\ninitiator does a request to the service, setting replyTo(Terminator). *(This example demonstrates one stack level\nrequest/reply, and state keeping between initiator and terminator)*\n\nASCII-artsy, it looks like this, the line (pipe-char) representing the state that goes between Initiator and Terminator,\nbut which is kept \"on the wire\" along with the message flow, i.e. along with the request out to Service, and then reply\nback to Terminator:\n\u003cpre\u003e\n[Initiator]    {request}\n |  [Service]  {reply}\n[Terminator]\n\u003c/pre\u003e\n\n```java\npublic class Test_SimplestServiceRequest {\n    private static final Logger log = MatsTestHelp.getClassLogger();\n\n    @ClassRule\n    public static final Rule_Mats MATS = Rule_Mats.create();\n\n    private static final String ENDPOINT = MatsTestHelp.endpoint();\n    private static final String TERMINATOR = MatsTestHelp.terminator();\n\n    @BeforeClass\n    public static void setupService() {\n        // This service is very simple, where it simply returns with an alteration of what it gets input.\n        MATS.getMatsFactory().single(ENDPOINT, DataTO.class, DataTO.class,\n                (context, dto) -\u003e {\n                    return new DataTO(dto.number * 2, dto.string + \":FromService\");\n                });\n    }\n\n    @BeforeClass\n    public static void setupTerminator() {\n        // A \"Terminator\" is a service which does not reply, i.e. it \"consumes\" any incoming messages.\n        // However, in this test, it resolves the test-latch, so that the main test thread can assert.\n        MATS.getMatsFactory().terminator(TERMINATOR, StateTO.class, DataTO.class,\n                (context, sto, dto) -\u003e {\n                    log.debug(\"TERMINATOR MatsTrace:\\n\" + context.toString());\n                    MATS.getMatsTestLatch().resolve(sto, dto);\n                });\n\n    }\n\n    @Test\n    public void doTest() {\n        // Send request to \"Service\", specifying reply to \"Terminator\".\n        DataTO dto = new DataTO(42, \"TheAnswer\");\n        StateTO sto = new StateTO(420, 420.024);\n        MATS.getMatsInitiator().initiateUnchecked(\n                (msg) -\u003e msg.traceId(MatsTestHelp.traceId())\n                        .from(MatsTestHelp.from(\"test\"))\n                        .to(ENDPOINT)\n                        .replyTo(TERMINATOR, sto)\n                        .request(dto));\n\n        // Wait synchronously for terminator to finish. NOTE: Such synchronicity is not a typical Mats flow!\n        Result\u003cStateTO, DataTO\u003e result = MATS.getMatsTestLatch().waitForResult();\n        Assert.assertEquals(sto, result.getState());\n        Assert.assertEquals(new DataTO(dto.number * 2, dto.string + \":FromService\"), result.getData());\n    }\n}\n```\n\n## Multi-stage service and multi-level requests\n\nSets up a somewhat complex test scenario, testing request/reply message passing and state keeping between stages in\nseveral multi-stage endpoints, at different levels in the stack. The code is a tad long, but it should be simple to read\nthrough.\n\nThe main aspects of Mats are demonstrated here, notice in particular how the code of\n\u003ccode\u003esetupMainMultiStagedService()\u003c/code\u003e *looks like* - if you squint a little - a linear \"straight down\" method with\ntwo \"blocking requests\" out to other services, where the last stage ends with a return statement, sending off the Reply\nto whoever invoked it.\n\nSets up these services:\n\n* Leaf service: Single stage: Replies directly.\n* Mid service: Two stages: Requests \"Leaf\" service, then replies.\n* Main service: Three stages: First requests \"Mid\" service, then requests \"Leaf\" service, then replies.\n\nA Terminator is also set up, and then the initiator sends a request to \"Main\", setting replyTo(Terminator).\n\nASCII-artsy, it looks like this, the lines representing the state that goes between the Initiator and Terminator and the\nstages of the endpoints, but which is kept \"on the wire\" along with the message flow through the different requests and\nreplies:\n\u003cpre\u003e\n[Initiator]              {request}\n |  [Main S0 (init)]   {request}\n |   |  [Mid S0 (init)]  {request}\n |   |   |  [Leaf]       {reply}\n |   |  [Mid S1 (last)]  {reply}\n |  [Main S1]          {request}\n |   |  [Leaf]           {reply}\n |  [Main S2 (last)]   {reply}\n[Terminator]\n\u003c/pre\u003e\n\n**Again, it is important to realize that the three stages of the Main service (and the two of the Mid service) are\nactually fully independent messaging endpoints (with their own JMS queue when run on a JMS backend), and if you've\ndeployed the service to multiple nodes, each stage in a particular invocation flow might run on a different node.**\n\nThe Mats API and implementation sets up a call stack that can be of arbitrary depth, along with stack frames whose state\nflows along with the message passing, so that you can code *as if* you were coding a normal service method that invokes\nremote services synchronously.\n\n```java\npublic class Test_MultiLevelMultiStage {\n    private static final Logger log = MatsTestHelp.getClassLogger();\n\n    @ClassRule\n    public static final Rule_Mats MATS = Rule_Mats.create();\n\n    private static final String ENDPOINT_MAIN = MatsTestHelp.endpoint(\"MAIN\");\n    private static final String ENDPOINT_MID = MatsTestHelp.endpoint(\"MID\");\n    private static final String ENDPOINT_LEAF = MatsTestHelp.endpoint(\"LEAF\");\n    private static final String TERMINATOR = MatsTestHelp.terminator();\n\n    @BeforeClass\n    public static void setupLeafService() {\n        // Create single-stage \"Leaf\" endpoint. Single stage, thus the processor is defined directly.\n        MATS.getMatsFactory().single(ENDPOINT_LEAF, DataTO.class, DataTO.class,\n                (context, dto) -\u003e {\n                    // Returns a Reply to the calling service with a alteration of incoming message\n                    return new DataTO(dto.number * 2, dto.string + \":FromLeafService\");\n                });\n    }\n\n    @BeforeClass\n    public static void setupMidMultiStagedService() {\n        // Create two-stage \"Mid\" endpoint\n        MatsEndpoint\u003cDataTO, StateTO\u003e ep = MATS.getMatsFactory()\n                .staged(ENDPOINT_MID, DataTO.class, StateTO.class);\n\n        // Initial stage, receives incoming message to this \"Mid\" service\n        ep.stage(DataTO.class, (context, sto, dto) -\u003e {\n            // State object is \"empty\" at initial stage.\n            Assert.assertEquals(0, sto.number1);\n            Assert.assertEquals(0, sto.number2, 0);\n            // Setting state some variables.\n            sto.number1 = 10;\n            sto.number2 = Math.PI;\n            // Perform request to \"Leaf\" Service...\n            context.request(ENDPOINT_LEAF, dto);\n        });\n\n        // Next, and last, stage, receives replies from the \"Leaf\" service, and returns a Reply\n        ep.lastStage(DataTO.class, (context, sto, dto) -\u003e {\n            // .. \"continuing\" after the \"Leaf\" Service has replied.\n            // Assert that state variables set in previous stage are still with us.\n            Assert.assertEquals(new StateTO(10, Math.PI), sto);\n            // Returning Reply to calling service.\n            return new DataTO(dto.number * 3, dto.string + \":FromMidService\");\n        });\n    }\n\n    @BeforeClass\n    public static void setupMainMultiStagedService() {\n        // Create three-stage \"Main\" endpoint\n        MatsEndpoint\u003cDataTO, StateTO\u003e ep = MATS.getMatsFactory()\n                .staged(ENDPOINT_MAIN, DataTO.class, StateTO.class);\n\n        // Initial stage, receives incoming message to this \"Main\" service\n        ep.stage(DataTO.class, (context, sto, dto) -\u003e {\n            // State object is \"empty\" at initial stage.\n            Assert.assertEquals(0, sto.number1);\n            Assert.assertEquals(0, sto.number2, 0);\n            // Setting state some variables.\n            sto.number1 = Integer.MAX_VALUE;\n            sto.number2 = Math.E;\n            // Perform request to \"Mid\" Service...\n            context.request(ENDPOINT_MID, dto);\n        });\n        ep.stage(DataTO.class, (context, sto, dto) -\u003e {\n            // .. \"continuing\" after the \"Mid\" Service has replied.\n            // Assert that state variables set in previous stage are still with us.\n            Assert.assertEquals(Integer.MAX_VALUE, sto.number1);\n            Assert.assertEquals(Math.E, sto.number2, 0);\n            // Changing the state variables.\n            sto.number1 = Integer.MIN_VALUE;\n            sto.number2 = Math.E * 2;\n            // Perform request to \"Leaf\" Service...\n            context.request(ENDPOINT_LEAF, dto);\n        });\n        ep.lastStage(DataTO.class, (context, sto, dto) -\u003e {\n            // .. \"continuing\" after the \"Leaf\" Service has replied.\n            // Assert that state variables changed in previous stage are still with us.\n            Assert.assertEquals(Integer.MIN_VALUE, sto.number1);\n            Assert.assertEquals(Math.E * 2, sto.number2, 0);\n            // Returning Reply to \"caller\"\n            // (in this test it will be what the initiation specified as replyTo; \"Terminator\")\n            return new DataTO(dto.number * 5, dto.string + \":FromMainService\");\n        });\n    }\n\n    @BeforeClass\n    public static void setupTerminator() {\n        // A \"Terminator\" is a service which does not reply, i.e. it \"consumes\" any incoming messages.\n        // However, in this test, it resolves the test-latch, so that the main test thread can assert.\n        MATS.getMatsFactory().terminator(TERMINATOR, StateTO.class, DataTO.class,\n                (context, sto, dto) -\u003e {\n                    log.debug(\"TERMINATOR MatsTrace:\\n\" + context.toString());\n                    MATS.getMatsTestLatch().resolve(sto, dto);\n                });\n    }\n\n    @Test\n    public void doTest() {\n        // :: Arrange\n        // State object for \"Terminator\".\n        StateTO sto = new StateTO((int) Math.round(123 * Math.random()), 321 * Math.random());\n        // Request object to \"Main\" Service.\n        DataTO dto = new DataTO(42 + Math.random(), \"TheRequest:\" + Math.random());\n\n        // :: Act\n        // Perform the Request to \"Main\", setting the replyTo to \"Terminator\".\n        MATS.getMatsInitiator().initiateUnchecked(\n                (msg) -\u003e msg.traceId(MatsTestHelp.traceId())\n                        .from(MatsTestHelp.from(\"test\"))\n                        .to(ENDPOINT_MAIN)\n                        .replyTo(TERMINATOR, sto)\n                        .request(dto));\n\n        // Wait synchronously for terminator to finish.\n        // NOTE: Such synchronous wait is not a typical Mats flow!\n        Result\u003cStateTO, DataTO\u003e result = MATS.getMatsTestLatch().waitForResult();\n\n        // :: Assert\n        // Assert that the State to the \"Terminator\" was what we wanted him to get\n        Assert.assertEquals(sto, result.getState());\n        // Assert that the Mats flow has gone through the stages, being modified as it went along\n        Assert.assertEquals(new DataTO(dto.number * 2 * 3 * 2 * 5,\n                        dto.string + \":FromLeafService\" + \":FromMidService\"\n                                + \":FromLeafService\" + \":FromMainService\"),\n                result.getData());\n    }\n}\n```\n\n## MatsFuturizer - the sync-async bridge\n\nThis class tests the most basic [`MatsFuturizer`](mats-util/src/main/java/io/mats3/util/MatsFuturizer.java) situation:\nSets up a single-stage endpoint, and then uses the MatsFuturizer to invoke this service, which returns\na `CompletableFuture` that will be completed when the Reply comes back from the requested endpoint.\n\nHow does this work in a multi-node setup, where the Reply should randomly come to this node, or any other node? Behind\nthe scenes, the MatsFuturizer sets up a `subscriptionTerminator` (a topic) that has the nodename as a part of the topic\nname. Furthermore, the request uses the special `replyToSubscription` feature of an initiation, targeting this\nnode-specific topic. Thus, the Reply will only be picked up by this node.\n\nWhy a topic? Not because of a topic's \"broadcast\" functionality (it is only a single consumer on this specific node that\nlistens to this specific topic), but the topic's \"if you're not there, the message is gone\" effect: If the node is gone\nin the time since the Mats flow was initiated, then so is obviously the Future's waiting thread, so no need to have any\nmessages stuck on the MQ.\n\nThere are multiple modes of operation of the MatsFuturizer, but all of them share the fact that completion of the future\ncannot be guaranteed - simply because the node might be booted or die while the Mats flow is running. You can however\ndecide whether the Mats flow should run using persistent (\"guaranteed\") or non-persistent messaging.\n\n**The futurizer should _only_ be employed on the \"edges\" of the Mats fabric**, where synchronous processes like\nREST-endpoints needs to communicate with the asynchronous Mats fabric. _Never_ use the MatsFuturizer as a part of your\napplication-internal API - [read this](docs/developing/MatsComposition.md).\n\n\u003e As an alternative to the MatsFuturizer, you should check out [MatsSocket](https://github.com/centiservice/matssocket),\n\u003e which bridges the asynchronous nature of Mats all the way out to the end-user clients by using WebSockets.\n\n```java\npublic class Test_MatsFuturizer_Basics {\n    private static final Logger log = MatsTestHelp.getClassLogger();\n\n    @ClassRule\n    public static final Rule_Mats MATS = Rule_Mats.create();\n\n    private static final String ENDPOINT = MatsTestHelp.endpoint();\n\n    @BeforeClass\n    public static void setupService() {\n        MATS.getMatsFactory().single(ENDPOINT, DataTO.class, DataTO.class,\n                (context, msg) -\u003e new DataTO(msg.number * 2, msg.string + \":FromService\"));\n    }\n\n    @Test\n    public void normalMessage() throws ExecutionException, InterruptedException, TimeoutException {\n        MatsFuturizer futurizer = MATS.getMatsFuturizer();\n\n        DataTO dto = new DataTO(42, \"TheAnswer\");\n        CompletableFuture\u003cReply\u003cDataTO\u003e\u003e future = futurizer.futurizeNonessential(\n                \"traceId\", \"OneSingleMessage\", ENDPOINT, DataTO.class, dto);\n\n        Reply\u003cDataTO\u003e result = future.get(1, TimeUnit.SECONDS);\n\n        Assert.assertEquals(new DataTO(dto.number * 2, dto.string + \":FromService\"), result.reply);\n    }\n}\n```\n\n# Try it out!\n\nIf you want to try this out in your project, I will support you!\n\n-Endre, endre@stolsvik.com\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcentiservice%2Fmats3","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcentiservice%2Fmats3","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcentiservice%2Fmats3/lists"}