The Mercury project is created with one primary objective -
to make software easy to write, read, test, deploy, scale and manage.
Mercury has been powering mission-critical cloud native production applications for the last few years. In version 3, Mercury has evolved to the next level. It is bringing event-driven design and the best of preemptive and cooperative multitasking as a foundation to build "composable applications."
You have low-level control to precisely tune your application for optimal performance and throughput using three function execution strategies:
Mercury version 3 achieves virtual threading using coroutine and suspend function.
In version 4, it fully embraces Java 21 virtual thread feature that was officially available since 12/2023.
August, 2024
Mercury version 3 is a software development toolkit for event-driven programming.
Event-driven programming allows functional modules to communicate with each other using events instead of direct method calls. This allows the functions to be executed asynchronously, improving overall application performance.
IMPORTANT: You should use Mercury version 4 unless you need backward compatibility for your production systems.
Please visit Mercury Version 4 using the links below:
Mercury v4: https://github.com/Accenture/mercury-composable
Documentation: https://accenture.github.io/mercury-composable/
The key differences of Mercury version 3 and the latest Mercury-Composable version 4 are:
| Category | Mercury 3.0 | Mercury 4.x |
|---|---|---|
| Java version | Supports Java 1.8 or higher | Requires Java 21 or higher |
| Event management | Orchestration by code | Event choreography by configuration |
| Multitasking | Coroutine and kernel | Java 21 virtual threads, coroutine and kernel |
| Functional isolation | KernelThreadRunner | Virtual Threads and KernelThreadRunner |
By default, the system runs all functions as "coroutines" where previous versions run them using a kernel thread pool.
The CoroutineRunner annotation has been removed and replaced with the new KernelThreadRunner annotation.
The "rest.automation.yaml" key is renamed as "yaml.rest.automation" after we unify the parsing behavior of application.properties with application.yml.
To get started with your first application, please refer to the Chapter 4, Developer Guide.
In cloud migration and IT modernization, we evaluate application portfolio and recommend different disposition strategies based on the 7R migration methodology.
7R: Retire, retain, re-host, re-platform, replace, re-architect and re-imagine.
The most common observation during IT modernization discovery is that there are many complex monolithic applications that are hard to modernize quickly.
IT modernization is like moving into a new home. It would be the opportunity to clean up and to improve for business agility and strategic competitiveness.
Composable architecture is gaining visibility recently because it accelerates organization transformation towards a cloud native future. We will discuss how we may reduce modernization risks with this approach.
Composability applies to both platform and application levels.
We can trace the root of composability to Service Oriented Architecture (SOA) in 2000 or a technical bulletin on "Flow-Based Programming" by IBM in 1971. This is the idea that architecture and applications are built using modular building blocks and each block is self-contained with predictable behavior.
At the platform level, composable architecture refers to loosely coupled platform services, utilities, and business applications. With modular design, you can assemble platform components and applications to create new use cases or to adjust for ever-changing business environment and requirements. Domain driven design (DDD), Command Query Responsibility Segregation (CQRS) and Microservices patterns are the popular tools that architects use to build composable architecture. You may deploy application in container, serverless or other means.
At the application level, a composable application means that an application is assembled from modular software components or functions that are self-contained and pluggable. You can mix-n-match functions to form new applications. You can retire outdated functions without adverse side effect to a production system. Multiple versions of a function can exist, and you can decide how to route user requests to different versions of a function. Applications would be easier to design, develop, maintain, deploy, and scale.
Composable architecture and applications contribute to business agility.
Since 2014, microservices architectural pattern helps to decompose a big application into smaller pieces of “self-contained” services. We also apply digital decoupling techniques to services and domains. Smaller is better. However, we are writing code in the same old fashion. One method is calling other methods directly. Functional and reactive programming techniques are means to run code in a non-blocking manner, for example Reactive Streams, Akka, Vertx, Quarkus Multi/Uni and Spring Reactive Flux/Mono. These are excellent tools, but they do not reduce the complexity of business applications.
To make an application composable, the software components within a single application should be loosely coupled where each component has zero or minimal dependencies.
Unlike traditional programming approach, composable application is built from the top down. First, we describe a business transaction as an event flow. Second, from the event flow, we identify individual functions for business logic. Third, we write user story for each function and write code in a self-contained manner. Finally, we write orchestration code to coordinate event flow among the functions, so they work together as a single application.
The individual functions become the building block for a composable application. We can mix-n-match different sets of functions to address different business use cases.
Cloud native applications are deployed as containers or serverless functions. Ideally, they communicate using events. For example, the CQRS design pattern is well accepted for building high performance cloud native applications.
Figure 1 - Cloud native applications use event streams to communicate

However, within a single application unit, the application is mostly built in a traditional way. i.e. one function is calling other functions and libraries directly, thus making the modules and libraries tightly coupled. As a result, microservices may become smaller monolithic applications.
To overcome this limitation, we can employ “event-driven design” to make the microservices application unit composable.
An application unit is a collection of functions in memory and an “event bus” is the communication conduit to connect the functions together to form a single executable.
Figure 2 – Functions use in-memory event bus to communicate

For a composable application, each function is written using the first principle of “input-process-output” where input and output payloads are delivered as events. All input and output are immutable to reduce unintended bugs and side effects.
Since input and output for each function is well-defined, test-driven development (TDD) can be done naturally. It is also easier to define a user story for each function and the developer does not need to study and integrate multiple levels of dependencies, resulting in higher quality code.
Figure 3 - The first principle of a function

What is a “function”? For example, reading a record from a database and performing some data transformation, doing a calculation with a formula, etc.
Figure 4 - Connecting output of one function to input of another

As shown in Figure 4, if function-1 wants to send a request to function-2, we can write “event orchestration code” to put the output from function-1 into an event envelope and send it over an in-memory event bus. The event system will transport the event envelope to function-2, extract the payload and submit it as “input” to function-2
In event-driven application design, a function is executed when an event arrives as “input.” When a function finishes processing, your application can command the event system to route the result set (“output”) as an event to another function.
Figure 5 - Executing function through event flow

As shown in Figure 5, functions can send/receive events using an in-memory event bus (aka "event loop").
This event-driven architecture provides the foundation to design and implement composable applications. Each function is self-contained and loosely coupled by event flow.
A function receiving an event needs to be executed. There are three ways to do that:
Java supports “preemptive multitasking” using kernel threads. Multiple functions can execute in parallel. Preemptive multitasking leverages the multiple cores of a CPU to yield higher performance.
Preemptive multitasking is performed at the kernel level and the operating system is doing the context switching. As a result, the maximum number of kernel threads is small. As a rule of thumb, a moderately fast computer can support ~200 kernel threads.
Figure 6 - Multitasking of kernel threads at the hardware and OS level

Many modern programming languages such as GoLang, Kotlin, Python and Node.js support “cooperative multitasking” using “event loop” or “coroutine.” Instead of context switching at the kernel level, functions are executed orderly by yielding to each other. The order of execution depends on the event flow of each business transaction.
Since the functions are running cooperatively, the overheads of context switching are low. “Event loop” or “Coroutine” technology usually can support tens of thousands of “functions” running in “parallel.” Technically, the functions are running sequentially. When each function finishes execution very quickly, they appear as running concurrently.
Figure 7 - Cooperative multitasking of coroutines

Java 1.8 and higher versions support event loop with open sources libraries such as Lightbend Akka and Eclipse Vertx. A preview “virtual thread” technology is available in Java version 19. It brings cooperative multitasking by running tens of thousands of “virtual threads” in a single kernel thread. This is a major technological breakthrough to close the gap with other modern programming languages.
In a typical enterprise application, many functions are waiting for responses most of the time. In preemptive multitasking, these functions are using kernel threads and consuming CPU time. Too many active kernel threads would turn the application into slow motion.
“Suspend function” not only avoids overwhelming the CPU with excessive kernel threads but also leverages the synchronous request-response opportunity into high throughput non-blocking operation.
As the name indicates, “suspend function” can be suspended and resumed. When it is suspended, it yields control to the event loop so that other coroutines or suspend functions can run.
In Node.js and GoLang, coroutine and suspend function are the same. Suspend function refers to the “async/await” keywords or API of coroutine. In Kotlin, the suspend function extends a coroutine to have the suspend/resume ability.
A function is suspended when it is waiting for a response from the network, a database or from another function. It is resumed when a response is received.
Figure 8 - Improving throughput with suspend function

As shown in Figure 8, a “suspend function” can suspend and resume multiple times during its execution. When it suspends, it is not using any CPU time, thus the application has more time to serve other functions. This mechanism is so efficient that it can significantly increase the throughput of the application. i.e. it can handle many concurrent users, and process more requests.
The ability to select an optimal function execution strategy for a function is critical to the success of a composable application. This allows the developer to have low level control of how the application performs and scales.
Without an optimal function execution strategy, performance tuning is usually an educated guess.
In composable application architecture, each function is self-contained and stateless. We can predict the performance of each function by selecting an optimal function execution strategy and evaluate it with unit tests and observability. Predicting application performance and throughput at design and development time reduces modernization risks.
The pros and cons of each function execution strategy are summarized below:
| Strategy | Advantage
$ claude mcp add mercury \
-- python -m otcore.mcp_server <graph>