Skip to main content

Local Multi-Process Deployment

tip

ionet supports deploying external services, registry services, and logic services in one process, and it also supports splitting them into multiple processes. This section uses the ionet-multi-process example to explain the basic approach to local multi-process deployment.

Introduction

During development, we usually start services in a single process because debugging is simpler. As the business grows, or when different logic modules need to scale independently, services can be split into multiple processes.

The core idea of local multi-process deployment is:

  1. Keep one aggregation process responsible for CenterServer, the external service, and Aeron MediaDriver.
  2. Start each logic service as an independent process and connect it to the aggregation process through Aeron IPC.
  3. Logic services can still call each other through FlowContext.call() as if they were local.

The benefits of this local multi-process approach are:

  1. Logic modules can be deployed and restarted independently.
  2. Development can still follow a monolithic workflow, and business code rarely needs to change because of deployment mode.
  3. When deployed on the same machine, Aeron IPC still provides low-latency inter-process communication.

For the basic concepts of RunOne, CenterServer, and Aeron, see RunOne.

Example Structure

Example source code:

The project structure is:

ionet-multi-process
├── common-data # Shared data: command ids, Aeron configuration, utility classes
├── logic-book-library # Book library logic service (independent process)
├── logic-author # Author logic service (independent process)
├── one-application # Aggregation service (independent process)
└── one-client # Test client

The responsibilities of each module are:

ModuleResponsibility
one-applicationStarts Aeron MediaDriver, CenterServer, and the external service
logic-book-libraryBook library logic service that provides listBook
logic-authorAuthor logic service that exposes hello and listBook
common-dataStores shared content such as module ids, command ids, and Aeron connection configuration
one-clientWebSocket client used to verify the deployment result

Architecture

one-client (WebSocket)


one-application ←── Aeron IPC ──► logic-book-library
(CenterServer (BookLibraryLogicServer)
ExternalServer cmd=1: listBook
MediaDriver)

└─── Aeron IPC ──────────► logic-author
(AuthorLogicServer)
cmd=2: hello, listBook

In this structure:

  1. The client only connects to the WebSocket endpoint exposed by one-application.
  2. one-application embeds MediaDriver and serves as the unified access point for all logic services.
  3. logic-book-library and logic-author connect to MediaDriver through the same Aeron directory.
  4. AuthorAction.listBook makes a cross-process call to BookLibraryAction.listBook.

Aggregation Process

one-application is the key process in local multi-process deployment. It is responsible for three things:

  1. Starting Aeron MediaDriver.
  2. Starting the only CenterServer.
  3. Starting the external service to receive client requests.
public final class OneApplication {

static void main() {
var aeron = new EmbeddedAeronRuntime().getAeron();

new RunOne()
.setAeron(aeron)
.enableCenterServer()
.setExternalServer(
ExternalMapper.builder(ExternalGlobalConfig.externalPort).build()
)
.startup();
}
}

From the code above:

  1. enableCenterServer() starts the registry service and is only called once in the aggregation process.
  2. The external service is also started only here.
  3. The logic service processes themselves are not directly exposed to clients.
tip

We can also start the external service in a separate process, which gives us more flexible control over its deployment.

Logic Service Processes

Each logic service can be started independently. It only needs to register its own LogicServer with RunOne.

public final class BookLibraryApplication {

static void main() {
CoreGlobalConfig.setting.parseDoc = true;
var aeron = LogicAeronClientFactory.create();

new RunOne()
.setAeron(aeron)
.setLogicServerList(List.of(new BookLibraryLogicServer()))
.startup();
}
}

logic-author starts in the same way, except that it registers AuthorLogicServer.

This means each logic module can:

  1. Run as its own process.
  2. Be released independently.
  3. Scale independently based on business load.

Aeron IPC Connection

The reason local multi-process deployment works is that multiple processes use the same Aeron directory for IPC communication.

In the example, both the aggregation process and the logic service processes use the same directory name:

String aeronDirectoryName =
"%s-%s".formatted(CommonContext.getAeronDirectoryName(), "ionet");

The aggregation process creates MediaDriver inside EmbeddedAeronRuntime and cleans up the directory on shutdown. The logic service processes connect to that directory through LogicAeronClientFactory.create().

Because of this, startup order matters:

  1. Start one-application first.
  2. Start the logic services afterward.

If a logic service starts first, it will not find an available MediaDriver to connect to.

Inter-Process Invocation

Although logic-author and logic-book-library have already been split into two processes, the business call remains straightforward:

@ActionMethod(AuthorCmd.listBook)
public List<String> listBook(FlowContext flowContext) {
var response = flowContext.call(BookLibraryCmd.of(BookLibraryCmd.listBook));
return response.listString();
}

The callee only needs to declare its Action normally:

@ActionController(BookLibraryCmd.cmd)
public final class BookLibraryAction {
AtomicInteger inc = new AtomicInteger(0);

@ActionMethod(BookLibraryCmd.listBook)
public List<String> listBook() {
return List.of("book-" + inc.incrementAndGet(), "book-" + inc.incrementAndGet());
}
}

From the business-code perspective:

  1. The caller does not need to care whether the target logic service is in the same process.
  2. Changes in deployment topology do not break the existing invocation style.

This is one reason ionet still provides a good development experience in local multi-process scenarios.

Command Id Layout

The module ids and command ids in the example are:

ModulecmdsubCmdDescription
logic-book-library11listBook, gets the book list
logic-author21hello, greeting endpoint
logic-author22listBook, gets the book list across processes

The shared command definitions are placed in common-data, so multiple processes can reuse the same protocol definitions.

Startup Steps

Run packaging in the example root directory:

mvn clean package

This generates the following executable jars:

ModuleJAR Path
one-applicationone-application/target/one-application.jar
logic-book-librarylogic-book-library/target/logic-book-library.jar
logic-authorlogic-author/target/logic-author.jar
one-clientone-client/target/one-client.jar

Step 1: Start the aggregation process

java --add-opens java.base/jdk.internal.misc=ALL-UNNAMED \
--enable-native-access=ALL-UNNAMED \
-jar one-application/target/one-application.jar

This process starts:

  1. MediaDriver
  2. CenterServer
  3. The WebSocket external service

If no custom port is configured, the external service uses ExternalGlobalConfig.externalPort by default, and the WebSocket endpoint is usually ws://127.0.0.1:10100/websocket.

Step 2: Start the logic service processes

# Book library logic service
java --add-opens java.base/jdk.internal.misc=ALL-UNNAMED \
--enable-native-access=ALL-UNNAMED \
-jar logic-book-library/target/logic-book-library.jar

# Author logic service
java --add-opens java.base/jdk.internal.misc=ALL-UNNAMED \
--enable-native-access=ALL-UNNAMED \
-jar logic-author/target/logic-author.jar

These two processes can be started in any order, but both must start after one-application.

Step 3: Start the client for verification

java -jar one-client/target/one-client.jar

The client automatically sends two requests:

  1. AuthorCmd.hello
  2. AuthorCmd.listBook

AuthorCmd.listBook eventually makes a cross-process call to logic-book-library. If you see the returned book list in the logs, the whole multi-process call chain is working.

Usage Suggestions

In real projects, you can split modules with this pattern:

  1. Put the external service, registry center, and gateway responsibilities into one or a small number of entry processes.
  2. Split logic services by business domain, such as hall, room, backpack, or matchmaking services.
  3. Put shared protocols, command ids, and common DTOs into a separate module for reuse across processes.

Summary

Local multi-process deployment in ionet does not change the business programming model. You only need to:

  1. Make sure there is only one CenterServer.
  2. Start MediaDriver in the aggregation process first.
  3. Let all logic services connect with the same Aeron configuration.

This gives you a more flexible deployment topology and clearer service boundaries while keeping the development experience intact.