Last active
March 13, 2025 09:41
-
-
Save MBoegers/87fa0928c46e037155dbc7d48aaf7553 to your computer and use it in GitHub Desktop.
RuleEngine with Modern Java Concurrency
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import javax.net.ssl.SSLContext; | |
import java.security.NoSuchAlgorithmException; | |
import java.time.Duration; | |
import java.util.List; | |
import java.util.Map; | |
/// This engine uses [JEP 481](https://openjdk.org/jeps/481) {@link ScopedValue} technique to forward execution context thread save. | |
class Engine { | |
private final SSLContext sslContext; | |
Engine(SSLContext sslContext) { | |
this.sslContext = sslContext; | |
} | |
public static abstract class Rule { | |
/// Models the Adress of an attendee. | |
/// The `record` implementation ensures that the data is immutable. | |
record Address(String city, String street, int house) { | |
} | |
/// Read the address from the **context** as via the scoped value API and generates the `Address` PoJo | |
protected Address readAddress() throws InterruptedException { | |
String city = CONTEXT.get().get("city"); | |
String street = CONTEXT.get().get("street"); | |
int house = Integer.parseInt(CONTEXT.get().get("house")); | |
Thread.sleep(Duration.ofSeconds(1)); // simulate a time consuming operation | |
return new Address(city, street, house); | |
} | |
protected String readName() { | |
return CONTEXT.get().getOrDefault("name", "UNKNOWN"); | |
} | |
/// Method to implement by the business rules. | |
/// The implementations can Thread-save leverage the `Rule.read*` methods. | |
abstract void fire(); | |
} | |
private final static ScopedValue<Map<String, String>> CONTEXT = ScopedValue.newInstance(); | |
private final static ScopedValue<SSLContext> SSL_CTX = ScopedValue.newInstance(); | |
void execute(List<Rule> rules, Map<String, String> data) { | |
ScopedValue.Carrier executionScope = ScopedValue | |
.where(CONTEXT, data) | |
.where(SSL_CTX, sslContext); | |
for (var r : rules) { | |
executionScope.run(r::fire); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.List; | |
import java.util.Map; | |
import java.util.concurrent.Executors; | |
void main() throws NoSuchAlgorithmException { | |
var merlin = Map.of( | |
"name", "Merlin", | |
"city", "Herne", | |
"street", "Mont-Cenis-Str.", | |
"house", "294"); | |
var falk = Map.of( | |
"name", "Falk", | |
"city", "Darmstadt", | |
"street", "Donno", | |
"house", "12"); | |
var engine = new Engine(SSLContext.getDefault()); | |
var rules = List.<Engine.Rule>of(new PrintAttendeeInfos()); | |
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { | |
for (int i = 0; i < 10; i++) { | |
executor.submit(() -> engine.execute(rules, merlin)); | |
executor.submit(() -> engine.execute(rules, falk)); | |
} | |
} | |
System.out.println("Execution finished"); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.concurrent.ExecutionException; | |
import java.util.concurrent.StructuredTaskScope; | |
import java.util.function.Supplier; | |
/// This Rule leverages the [JEP 480](https://openjdk.org/jeps/480) StructuredConcurrency API to obtain data for the logic | |
class PrintAttendeeInfos extends Engine.Rule{ | |
@Override | |
void fire() { | |
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { | |
Supplier<String> name = scope.fork(() -> { | |
System.out.println("\tRead Name"); | |
var tmp = readName(); | |
System.out.println("\tGot Name"); | |
return tmp; | |
}); | |
Supplier<Address> adress = scope.fork(() ->{ | |
System.out.println("\tRead Address"); | |
var tmp = readAddress(); | |
System.out.println("\tGot Address"); | |
return tmp; | |
}); | |
scope.join().throwIfFailed(); // Wait for both forks | |
var addr = adress.get(); | |
System.out.printf("%s's home is %s %d, %s%n", name.get(), addr.street(), addr.house(), addr.city()); | |
} catch (ExecutionException | InterruptedException e) { | |
throw new RuntimeException(e); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment