Micronaut Integration with SPL to Implement Microservices
Microservices architecture, celebrated for its flexible deployment and strong scalability, has become a mainstream approach in modern enterprise applications. Yet in practice, it often faces the dilemma of being praised but seldom adopted. The root cause lies in traditional Java-based implementations: microservice code tends to be verbose, business logic is hard to decouple or update, and complex logic often ends up being pushed into the database layer—resulting in increased system coupling.
SPL (Structured Process Language) offers a solution. SPL is an interpreted scripting language built purely in Java, exceptionally good at processing complex data logic and supporting hot-swapping of business logic—allowing updates without restarting or redeploying the system. It’s naturally suited to serve as the business computation engine within microservices. When combined with a modern Java microservice framework like Micronaut, SPL preserves the native characteristics of microservices while improving development and operations efficiency.
Architecture Overview
The following steps walk through an example demonstrating how to integrate SPL with Micronaut to implement microservices.
Preparing the Micronaut environment
Refer to the official documentation for installing the Micronaut CLI.
Creating Projects
You can quickly create Maven-based Micronaut Java projects using the Micronaut CLI, enabling service discovery (Consul) in your projects.
Create the service provider and service consumer projects separately with the following commands:
mn create-app micronaut-pvovider --build maven --features discovery-consul
mn create-app micronaut-consumer --build maven --features discovery-consul
Open the provider project in your IDE (e.g., IntelliJ IDEA).
Starting the Applications
Before starting the application, you need to first start the service registry (Consul). For installation and setup, please refer to the official documentation.
For convenience, this article uses a Docker image to start Consul with the following command:
docker run -p 8500:8500 consul:1.15.4
To start the Micronaut application, you can either run the main class directly from your IDE or start it via the command line from the project root directory:
./mvnw mn:run
Developing and deploying microservicces
Integrating SPL
add dependencies (pom.xml)
<dependency>
<groupId>com.scudata.esproc</groupId>
<artifactId>esproc</artifactId>
<version>20250605</version>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.7.4</version>
</dependency>
In practice, since the central Maven repository is updated infrequently, if you need the most complete and up-to-date version of SPL, it is recommended to download the standard edition directly from the official website and synchronize the JAR files through a private Maven repository. You can download the standard edition from HERE and install it.
The two required JAR files can be found in the \esProc\lib directory under the installation folder:
esproc-bin-xxxx.jar //the computation engine and JDBC driver
icu4j-60.3.jar //used for internationalization support
Deploying SPL configuration file
raqsoftConfig.xml is SPL’s main configuration file, where the data source, main directory, SPL script’s search path, and other information are configured. Just copy the file to Micronaut’s classpath. Below is a sample of configuring the data source:
……
<DBList encryptLevel="0">
<DB name="hsql">
<property name="url" value="jdbc:hsqldb:hsql://127.0.01:9001/demo"/>
<property name="driver" value="org.hsqldb.jdbcDriver"/>
<property name="type" value="13"/>
<property name="user" value="sa"/>
<property name="password"/>
<property name="batchSize" value="0"/>
<property name="autoConnect" value="false"/>
<property name="useSchema" value="false"/>
<property name="addTilde" value="false"/>
<property name="caseSentence" value="false"/>
</DB>
</DBList>
……
Service provider side
Invoke SPL scripts via the SPL JDBC driver.
package micronaut.pvovider;
import io.micronaut.http.annotation.*;
import io.micronaut.http.MediaType;
import java.sql.*;
import java.util.*;
@Controller("/spl")
public class SPLProviderController {
@Post(uri = "/call", consumes = MediaType.APPLICATION_JSON, produces = MediaType.APPLICATION_JSON)
public Map<String, Object> execute(@Body Map<String, String> request) {
Map<String, Object> response = new HashMap<>();
String splxName = request.get("splxName");
String jsonParam = request.get("jsonParam");
if (splxName == null || splxName.isBlank()) {
response.put("code", 400);
response.put("message", "Missing splxName");
return response;
}
try {
Class.forName("com.esproc.jdbc.InternalDriver");
try (Connection con = DriverManager.getConnection("jdbc:esproc:local://");
CallableStatement st = con.prepareCall("call " + splxName + "(?)")) {
if (jsonParam != null && !jsonParam.isEmpty()) {
st.setString(1, jsonParam);
} else {
st.setNull(1, Types.VARCHAR);
}
boolean hasResult = st.execute();
List<List<Object>> allResults = new ArrayList<>();
int resultSetCount = 0;
do {
if (hasResult) {
try (ResultSet rs = st.getResultSet()) {
ResultSetMetaData metaData = rs.getMetaData();
int columnCount = metaData.getColumnCount();
List<Object> currentResult = new ArrayList<>();
while (rs.next()) {
if (columnCount == 1) {
currentResult.add(rs.getObject(1));
} else {
Map<String, Object> row = new HashMap<>();
for (int i = 1; i <= columnCount; i++) {
row.put(metaData.getColumnLabel(i), rs.getObject(i));
}
currentResult.add(row);
}
}
if (!currentResult.isEmpty()) {
allResults.add(currentResult);
resultSetCount++;
}
}
}
} while ((hasResult = st.getMoreResults()) || st.getUpdateCount() != -1);
if (resultSetCount > 0) {
response.put("code", 200);
response.put("message", "success");
response.put("data", resultSetCount == 1 ? allResults.get(0) : allResults);
} else {
response.put("code", 404);
response.put("message", "No result data");
}
}
} catch (Exception e) {
response.put("code", 500);
response.put("message", "Execution failed: " + e.getMessage());
}
return response;
}
}
Service consumer side
Open the micronaut-consumer project created before. Same as the service provider, it is also locally started. So in the configuration file application.properties, change the application port number to 8081 (default is 8080) to prevent collision.
micronaut.server.port=${SERVER_PORT:8081}
package micronaut.consumer;
import io.micronaut.core.type.Argument;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.*;
import io.micronaut.http.client.HttpClient;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.scheduling.TaskExecutors;
import io.micronaut.scheduling.annotation.ExecuteOn;
import java.util.Map;
@Controller("/consumer")
@Produces(MediaType.APPLICATION_JSON)
@ExecuteOn(TaskExecutors.BLOCKING)
public class SPLConsumerController {
private final HttpClient httpClient;
public SPLConsumerController(@Client("micronaut-pvovider") HttpClient httpClient) {
this.httpClient = httpClient;
}
@Post("/call-spl")
public Map<String, Object> callSPL(@Body Map<String, Object> body) {
return httpClient.toBlocking().retrieve(
HttpRequest.POST("/spl/call", body),
Argument.of(Map.class)
);
}
}
At this stage, the integration of SPL with Micronaut has been fully realized. The service-layer code and configuration constitute a stable and reusable foundational execution framework, while the core business logic is entirely encapsulated within SPL scripts that support dynamic hot updates. This architecture enables a clear separation of concerns between business logic and service implementation, thereby enhancing modularity, flexibility, and maintainability.
Implementing Business Logic in Microservices Using SPL
Business Use Case: Identifying Annual Key Customers
List the top customers by sales amount within a given year. Those customers whose cumulative sales amount reaches half of the company's total sales are considered"key customers."
Name the SPL script top-customer-ids.splx. Its content is as follows:
A |
B |
|
1 |
=year=json(jsonParam).year |
/Parse the input parameter jsonParam as a JSON object and extract the year field |
2 |
=connect@l("hsql") |
/Establish a connection to the local HSQL database |
3 |
=A2.query@x("select customer_id,order_amount from orders where year(order_date) = ?",year) |
/Execute a parameterized SQL query to fetch customer_id and order_amount for the specified year |
4 |
=A3.groups(customer_id;sum(order_amount):total_amount).sort(total_amount:-1) |
/Group the results by customer_id, calculate total order_amount as total_amount, and sort in descending order |
5 |
=a=0,half=A4.sum(total_amount)*0.5,A4.pselect((a+=total_amount,a>=half)) |
/Compute half of the total sales and find the index where the cumulative sum first reaches or exceeds this half |
The parameter jsonParam is a script input in the format: {year:2012}.
The script only has 5 lines code. You can imagine how complex the Java code will be in implementing the same logic.
The script should first be written and debugged in SPL IDE, whose functionalities are not our focus in this article. You can find related illustrations in SPL documentation.
An edited SPL script should be placed in SPL script search path configured in raqsoftConfig.xml. For example:
<splPathList>
<splPath>/home/raqsoft/SPL</splPath>
</splPathList>
Service consumer request test
Request headers
POST http://localhost:8081/consumer/call-spl
Content-Type: application/json
Request body
{
"splxName": "top-customer-ids",
"jsonParam": "{year:2012}"
}
Response
{
"code": 200,
"data": [
"ERNSH",
"BLONP",
"QUICK",
"QUEEN",
"RATTC",
"PICCO",
"FRANK",
"SPLIR",
"SAVEA",
"SUPRD"
],
"message": "success"
}
Handling Business Logic Changes
We have raised the "key customer" criterion from a 50% sales share threshold to 80% to better meet the demands of refined customer analysis and service.
In SPL, it is very simple to modify the business logic script. You just need to change 0.5 in A5 to 0.8. More importantly, SPL script uses interpreted execution mechanism and supports hot-swapping during run. Users only need to re-upload the modified script to the server side and make the request again to get a new computing result, without compilation and restarting the application. The modification takes effect instantly.
In contrast, Java lacks such flexibility. Since rules like sales share thresholds are typically hard-coded within the application logic, even a minor parameter adjustment requires recompiling and restarting the service. This makes deployment cumbersome and slow to respond, poorly suited for frequently changing business requirements.
Summary
This case demonstrates that incorporating SPL into the Micronaut microservice framework can effectively address many pain points faced by conventional Java microservices when handling complex business logic. Thanks to SPL’s interpreted execution, business logic updates become hot-swappable — the computational code can be dynamically modified without restarting the service, significantly increasing system flexibility as well as operational and maintenance efficiency.
In contrast, conventional Java implementations tend to hardcode rules into the microservices. Any changes require recompilation and redeployment, resulting in higher development costs and longer response cycles, which make the application difficult to adapt to highly dynamic business scenarios.
As a scripting language designed for structured data processing, SPL not only offers powerful computational and logic description capabilities but also integrates seamlessly with Micronaut and other modern microservice frameworks. This enables the microservices design principle of “scripting computing logic, standardizing service frameworks, and minimizing deployment and operations and maintenance.”
In real-world projects, SPL-driven microservice computations boost development efficiency and enable rapid updates to business rules, making SPL an optimal solution for agile and dynamic business environments.
SPL Official Website 👉 https://www.esproc.com
SPL Feedback and Help 👉 https://www.reddit.com/r/esProcSPL
SPL Learning Material 👉 https://c.esproc.com
SPL Source Code and Package 👉 https://github.com/SPLWare/esProc
Discord 👉 https://discord.gg/sxd59A8F2W
Youtube 👉 https://www.youtube.com/@esProc_SPL