Local Multi-Process Deployment
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:
- Keep one aggregation process responsible for
CenterServer, the external service, andAeron MediaDriver. - Start each logic service as an independent process and connect it to the aggregation process through Aeron IPC.
- Logic services can still call each other through
FlowContext.call()as if they were local.
The benefits of this local multi-process approach are:
- Logic modules can be deployed and restarted independently.
- Development can still follow a monolithic workflow, and business code rarely needs to change because of deployment mode.
- 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:
- https://github.com/iohao/ionet-examples
- path :
ionet-multi-process
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:
| Module | Responsibility |
|---|---|
one-application | Starts Aeron MediaDriver, CenterServer, and the external service |
logic-book-library | Book library logic service that provides listBook |
logic-author | Author logic service that exposes hello and listBook |
common-data | Stores shared content such as module ids, command ids, and Aeron connection configuration |
one-client | WebSocket 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:
- The client only connects to the WebSocket endpoint exposed by
one-application. one-applicationembedsMediaDriverand serves as the unified access point for all logic services.logic-book-libraryandlogic-authorconnect toMediaDriverthrough the same Aeron directory.AuthorAction.listBookmakes a cross-process call toBookLibraryAction.listBook.
Aggregation Process
one-application is the key process in local multi-process deployment. It is responsible for three things:
- Starting
Aeron MediaDriver. - Starting the only
CenterServer. - 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:
enableCenterServer()starts the registry service and is only called once in the aggregation process.- The external service is also started only here.
- The logic service processes themselves are not directly exposed to clients.
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:
- Run as its own process.
- Be released independently.
- 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:
- Start
one-applicationfirst. - 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:
- The caller does not need to care whether the target logic service is in the same process.
- 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:
| Module | cmd | subCmd | Description |
|---|---|---|---|
logic-book-library | 1 | 1 | listBook, gets the book list |
logic-author | 2 | 1 | hello, greeting endpoint |
logic-author | 2 | 2 | listBook, 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:
| Module | JAR Path |
|---|---|
one-application | one-application/target/one-application.jar |
logic-book-library | logic-book-library/target/logic-book-library.jar |
logic-author | logic-author/target/logic-author.jar |
one-client | one-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:
MediaDriverCenterServer- 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:
AuthorCmd.helloAuthorCmd.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:
- Put the external service, registry center, and gateway responsibilities into one or a small number of entry processes.
- Split logic services by business domain, such as hall, room, backpack, or matchmaking services.
- 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:
- Make sure there is only one
CenterServer. - Start
MediaDriverin the aggregation process first. - 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.