Slide 1

Slide 1 text

How and why I turned my old Java projects into a first-class serverless component by Mario Fusco Red Hat – Principal Software Engineer @mariofusco

Slide 2

Slide 2 text

Agenda ➢ Declarative vs. Imperative programming ➢ What is a rule engine and how it works ➢ A simple example ➢ From drl to Java: the executable model

Slide 3

Slide 3 text

Agenda ➢ Declarative vs. Imperative programming ➢ What is a rule engine and how it works ➢ A simple example ➢ From drl to Java: the executable model ➢ What is GraalVM ➢ AoT compilation ➢ Limitations ➢ Refactoring Drools to (natively) compile on GraalVM

Slide 4

Slide 4 text

Agenda ➢ Declarative vs. Imperative programming ➢ What is a rule engine and how it works ➢ A simple example ➢ From drl to Java: the executable model ➢ What is GraalVM ➢ AoT compilation ➢ Limitations ➢ Refactoring Drools to (natively) compile on GraalVM ➢ What is Quarkus ➢ Quarkus features ➢ Integrating Drools with Quarkus ➢ Writing a Quarkus extension for Drools

Slide 5

Slide 5 text

What a rule-based program is ➢ A rule-based program is made up of discrete rules, each of which applies to a subset of the problem ➢ It is simpler, because you can concentrate on the rules for one situation at a time ➢ It can be more flexible in the face of fragmentary or poorly conditioned inputs ➢ Used for problems involving control, diagnosis, prediction, classification, pattern recognition … in short, all problems without clear algorithmic solutions

Slide 6

Slide 6 text

What a rule-based program is ➢ A rule-based program is made up of discrete rules, each of which applies to a subset of the problem ➢ It is simpler, because you can concentrate on the rules for one situation at a time ➢ It can be more flexible in the face of fragmentary or poorly conditioned inputs ➢ Used for problems involving control, diagnosis, prediction, classification, pattern recognition … in short, all problems without clear algorithmic solutions Declarative (What to do) Imperative (How to do it) Vs. public void helloMark(Person person) { if ( person.getName().equals( “Mark” ) { System.out.println( “Hello Mark” ); } } rule “Hello Mark” when Person( name == “Mark” ) then System.out.println( “Hello Mark” ); end

Slide 7

Slide 7 text

What a rule-based program is ➢ A rule-based program is made up of discrete rules, each of which applies to a subset of the problem ➢ It is simpler, because you can concentrate on the rules for one situation at a time ➢ It can be more flexible in the face of fragmentary or poorly conditioned inputs ➢ Used for problems involving control, diagnosis, prediction, classification, pattern recognition … in short, all problems without clear algorithmic solutions Declarative (What to do) Imperative (How to do it) Vs. public void helloMark(Person person) { if ( person.getName().equals( “Mark” ) { System.out.println( “Hello Mark” ); } } rule “Hello Mark” when Person( name == “Mark” ) then System.out.println( “Hello Mark” ); end A method must be called directly Rules can never be called directly

Slide 8

Slide 8 text

What a rule-based program is ➢ A rule-based program is made up of discrete rules, each of which applies to a subset of the problem ➢ It is simpler, because you can concentrate on the rules for one situation at a time ➢ It can be more flexible in the face of fragmentary or poorly conditioned inputs ➢ Used for problems involving control, diagnosis, prediction, classification, pattern recognition … in short, all problems without clear algorithmic solutions Declarative (What to do) Imperative (How to do it) Vs. public void helloMark(Person person) { if ( person.getName().equals( “Mark” ) { System.out.println( “Hello Mark” ); } } rule “Hello Mark” when Person( name == “Mark” ) then System.out.println( “Hello Mark” ); end A method must be called directly Specific passing of arguments Rules can never be called directly Specific instances cannot be passed but are automatically selected with pattern-matching

Slide 9

Slide 9 text

Introducing ➢ Easier to understand → Requirements can be more naturally translated into rules. It is more likely for a technically skilled business analyst to verify, validate or even change a rule than a piece of Java code ➢ Improved maintainability → We don't care about how to implement a solution only what needs to be done to solve a problem ➢ Deals with evolving complexity → It's easier to modify a rule than a Java program and to determine the impact of this change on the rest of the application ➢ Modularity → Each rule models an isolated and small portion of your business logic and is not part of a monolithic program ➢ Clear separation of business logic from the rest of the system → Business and infrastructural code have very different lifecycles ➢ Complex Event Processing → Facts can be handled like timestamped events allowing temporal reasoning on them RULES

Slide 10

Slide 10 text

How a rule-based system works ➢ The Rule Base contains a computation efficient representation of the set of the defined rules ➢ The Working Memory contains the set of facts inserted into sessions ➢ The engine matches the fact in the working memory against the rules set ➢ When a match is found it creates an activation and put it into the agenda ➢ An activation is the tuple of facts matching the conditions of a rule plus the rule itself ➢ When all activations have been created the agenda elects through a conflict resolution strategy the one to be executed ➢ The elected activation is passed to the execution engine and the fired

Slide 11

Slide 11 text

A simple rule set rule RaiseAlarm when exists Fire() then insert( new Alarm( "house1" ) ); System.out.println( "Raise the Alarm"); end rule CancelAlarm when not Fire() a : Alarm() then delete( a ); System.out.println( "Cancel the Alarm"); end rule TurnSprinklerOn when s : Sprinkler( on == false ) f : Fire( room == s.room ) then modify( s ) { setOn( true ) } System.out.println( "Turn on the sprinkler for room " + f.getRoom().getName() ); end rule TurnSprinklerOff when s : Sprinkler( on == true ) not Fire( room == s.room ) then modify( s ) { setOn( false ) } System.out.println( "Turn off the sprinkler for room " + s.getRoom().getName() ); end rule OK when not Alarm() not Sprinkler( on == true ) then System.out.println( "Everything is ok" ); end Pattern-matching against objects in the Working Memory Code executed when a match is found

Slide 12

Slide 12 text

What is used by Quarkus ➢ A polyglot VM with cross-language JIT supporting ● Java Bytecode and JVM languages ● Interop with different languages ● Dynamic languages through Truffle API ➢ Cross-language interop out of the box ● Simple AST-based interpreter ● JIT across language boundaries ➢ Support for native binary compilation (SubstrateVM) ● faster boot-up ● lower memory footprint

Slide 13

Slide 13 text

AoT compilation with GraalVM ➢ Static analysis ➢ Closed world assumption ➢ Dead code elimination: classes, fields, methods, branches

Slide 14

Slide 14 text

AoT compilation with GraalVM ➢ Static analysis ➢ Closed world assumption ➢ Dead code elimination: classes, fields, methods, branches 🚀 Fast process start 🔬 Less memory 💾 Small size on disk

Slide 15

Slide 15 text

GraalVM Limitations Dynaminc Classloading Deloying jars, wars, etc. at runtime impossible public class InternalClassLoader extends ClassLoader { public Class> defineClass( String name, byte[] bytecode ) { return defineClass( name, bytecode, 0, bytecode.length ); } }

Slide 16

Slide 16 text

GraalVM Limitations Dynaminc Classloading Deloying jars, wars, etc. at runtime impossible public class InternalClassLoader extends ClassLoader { public Class> defineClass( String name, byte[] bytecode ) { return defineClass( name, bytecode, 0, bytecode.length ); } }

Slide 17

Slide 17 text

GraalVM Limitations Dynaminc Classloading Deloying jars, wars, etc. at runtime impossible public class InternalClassLoader extends ClassLoader { public Class> defineClass( String name, byte[] bytecode ) { return defineClass( name, bytecode, 0, bytecode.length ); } } JVMTI, JMX + other native VM interfaces No agents → No JRebel, Byteman, profilers, tracers Miscellaneous ➢ Security Manager ➢ finalize() (depreceated anyway) ➢ InvokeDynamic and MethodHandle

Slide 18

Slide 18 text

GraalVM Limitations Dynaminc Classloading Deloying jars, wars, etc. at runtime impossible public class InternalClassLoader extends ClassLoader { public Class> defineClass( String name, byte[] bytecode ) { return defineClass( name, bytecode, 0, bytecode.length ); } } JVMTI, JMX + other native VM interfaces No agents → No JRebel, Byteman, profilers, tracers Miscellaneous ➢ Security Manager ➢ finalize() (depreceated anyway) ➢ InvokeDynamic and MethodHandle

Slide 19

Slide 19 text

GraalVM Limitations Dynaminc Classloading Deloying jars, wars, etc. at runtime impossible public class InternalClassLoader extends ClassLoader { public Class> defineClass( String name, byte[] bytecode ) { return defineClass( name, bytecode, 0, bytecode.length ); } } JVMTI, JMX + other native VM interfaces No agents → No JRebel, Byteman, profilers, tracers Miscellaneous ➢ Security Manager ➢ finalize() (depreceated anyway) ➢ InvokeDynamic and MethodHandle Reflection Requires registration (closed world assumption)

Slide 20

Slide 20 text

GraalVM Limitations Dynaminc Classloading Deloying jars, wars, etc. at runtime impossible public class InternalClassLoader extends ClassLoader { public Class> defineClass( String name, byte[] bytecode ) { return defineClass( name, bytecode, 0, bytecode.length ); } } JVMTI, JMX + other native VM interfaces No agents → No JRebel, Byteman, profilers, tracers Miscellaneous ➢ Security Manager ➢ finalize() (depreceated anyway) ➢ InvokeDynamic and MethodHandle [ { "name" : "org.domain.model.Person", "allPublicConstructors" : true, "allPublicMethods" : true } ] Reflection Requires registration (closed world assumption) -H:ReflectionConfigurationFiles=src/main/resources/reflection.json

Slide 21

Slide 21 text

Drools on GraalVM - Executable Model rule "Older than Mark" when $p1: Person( name == "Mark" ) $p2: Person( name != "Mark", age > $p1.age ) then System.out.println( $p1.getName() + " is older than " + $p2.getName() ); end Variable markV = declarationOf( Person.class ); Variable olderV = declarationOf( Person.class ); Rule rule = rule( "Older than Mark" ) .build( pattern(markV) .expr("exprA", p -> p.getName().equals( "Mark" ), alphaIndexedBy( String.class, ConstraintType.EQUAL, 1, p -> p.getName(), "Mark" ), reactOn( "name", "age" )), pattern(olderV) .expr("exprB", p -> !p.getName().equals("Mark"), alphaIndexedBy( String.class, ConstraintType.NOT_EQUAL, 1, p -> p.getName(), "Mark" ), reactOn( "name" )) .expr("exprC", markV, (p1, p2) -> p1.getAge() > p2.getAge(), betaIndexedBy( int.class, ConstraintType.GREATER_THAN, 0, p -> p.getAge(), p -> p.getAge() ), reactOn( "age" )), on(olderV, markV).execute((p1, p2) -> System.out.println( p1.getName() + " is older than " + p2.getName() )) )

Slide 22

Slide 22 text

Drools on GraalVM - Executable Model rule "Older than Mark" when $p1: Person( name == "Mark" ) $p2: Person( name != "Mark", age > $p1.age ) then System.out.println( $p1.getName() + " is older than " + $p2.getName() ); end Variable markV = declarationOf( Person.class ); Variable olderV = declarationOf( Person.class ); Rule rule = rule( "Older than Mark" ) .build( pattern(markV) .expr("exprA", p -> p.getName().equals( "Mark" ), alphaIndexedBy( String.class, ConstraintType.EQUAL, 1, p -> p.getName(), "Mark" ), reactOn( "name", "age" )), pattern(olderV) .expr("exprB", p -> !p.getName().equals("Mark"), alphaIndexedBy( String.class, ConstraintType.NOT_EQUAL, 1, p -> p.getName(), "Mark" ), reactOn( "name" )) .expr("exprC", markV, (p1, p2) -> p1.getAge() > p2.getAge(), betaIndexedBy( int.class, ConstraintType.GREATER_THAN, 0, p -> p.getAge(), p -> p.getAge() ), reactOn( "age" )), on(olderV, markV).execute((p1, p2) -> System.out.println( p1.getName() + " is older than " + p2.getName() )) )

Slide 23

Slide 23 text

Drools on GraalVM - Executable Model rule "Older than Mark" when $p1: Person( name == "Mark" ) $p2: Person( name != "Mark", age > $p1.age ) then System.out.println( $p1.getName() + " is older than " + $p2.getName() ); end Variable markV = declarationOf( Person.class ); Variable olderV = declarationOf( Person.class ); Rule rule = rule( "Older than Mark" ) .build( pattern(markV) .expr("exprA", p -> p.getName().equals( "Mark" ), alphaIndexedBy( String.class, ConstraintType.EQUAL, 1, p -> p.getName(), "Mark" ), reactOn( "name", "age" )), pattern(olderV) .expr("exprB", p -> !p.getName().equals("Mark"), alphaIndexedBy( String.class, ConstraintType.NOT_EQUAL, 1, p -> p.getName(), "Mark" ), reactOn( "name" )) .expr("exprC", markV, (p1, p2) -> p1.getAge() > p2.getAge(), betaIndexedBy( int.class, ConstraintType.GREATER_THAN, 0, p -> p.getAge(), p -> p.getAge() ), reactOn( "age" )), on(olderV, markV).execute((p1, p2) -> System.out.println( p1.getName() + " is older than " + p2.getName() )) ) Executable model Indexes and reactivity explicitly defined

Slide 24

Slide 24 text

Executable Model at a glance ➢ A pure Java DSL for Drools rules authoring ➢ A pure Java canonical representation of a rule base ➢ Automatically generated by Maven plugin or Quarkus extension ● Can be embedded in jar ● Faster boot ➢ Improve Backward/Forward compatibility ➢ Allow for faster prototyping and experimentation of new features ➢ Prerequisite to make Drools natively compilable on GraalVM

Slide 25

Slide 25 text

public class InternalClassLoader extends ClassLoader { public Class> defineClass( String name, byte[] bytecode ) { throw new UnsupportedOperationException(); } } Drools on GraalVM – other refactors Dynamic class definition is no longer necessary

Slide 26

Slide 26 text

public class InternalClassLoader extends ClassLoader { public Class> defineClass( String name, byte[] bytecode ) { throw new UnsupportedOperationException(); } } Drools on GraalVM – other refactors Dynamic class definition is no longer necessary var m = KieServices.get().newKieModuleModel(); var kb = m.newKieBaseModel("simpleKB"); kb.setEventProcessingMode(CLOUD); kb.addPackage("org.drools.simple.project"); var ks = kb.newKieSessionModel("simpleKS"); ks.setDefault(true); ks.setType(STATEFUL); ks.setClockType(ClockTypeOption.get("realtime"));

Slide 27

Slide 27 text

public class InternalClassLoader extends ClassLoader { public Class> defineClass( String name, byte[] bytecode ) { throw new UnsupportedOperationException(); } } Drools on GraalVM – other refactors Dynamic class definition is no longer necessary var m = KieServices.get().newKieModuleModel(); var kb = m.newKieBaseModel("simpleKB"); kb.setEventProcessingMode(CLOUD); kb.addPackage("org.drools.simple.project"); var ks = kb.newKieSessionModel("simpleKS"); ks.setDefault(true); ks.setType(STATEFUL); ks.setClockType(ClockTypeOption.get("realtime")); org.kie.api.io.KieResources = org.drools.core.io.impl.ResourceFactoryServiceImpl org.kie.api.marshalling.KieMarshallers = org.drools.core.marshalling.MarshallerProviderImpl org.kie.api.concurrent.KieExecutors = org.drools.core.concurrent.ExecutorProviderImpl Map, Object> serviceMap = new HashMap<>(); void wireServices() { serviceMap.put( ServiceInterface.class, Class.forName("org.drools.ServiceImpl") .newInstance()); // … more services here }

Slide 28

Slide 28 text

Demo 1 Drools on GraalVM

Slide 29

Slide 29 text

JVM vs. Native

Slide 30

Slide 30 text

Introducing ➢ A Framework for writing (fast and lightweight) Java applications

Slide 31

Slide 31 text

Introducing ➢ A Framework for writing (fast and lightweight) Java applications ➢ (Optionally) allowing generation of native executable via GraalVM *.class QUARKUS optimized jar native executable JVM Maven/Gradle plugin

Slide 32

Slide 32 text

Introducing ➢ A Framework for writing (fast and lightweight) Java applications ➢ (Optionally) allowing generation of native executable via GraalVM ➢ Based on existing standard ● Servlet ● JAX-RS ● JPA, JDBC ● CDI ● Bean Validation ● Transactions ● Logging ● Microprofile *.class QUARKUS optimized jar native executable JVM Maven/Gradle plugin

Slide 33

Slide 33 text

Introducing ➢ A Framework for writing (fast and lightweight) Java applications ➢ (Optionally) allowing generation of native executable via GraalVM ➢ Based on existing standard ● Servlet ● JAX-RS ● JPA, JDBC ● CDI ● Bean Validation ● Transactions ● Logging ● Microprofile ➢ Out-of-the-box integration with libraries that you already know *.class QUARKUS optimized jar native executable JVM Maven/Gradle plugin

Slide 34

Slide 34 text

Why Quarkus Lower memory usage Faster startup Optimized for short-lived processes Kubernetes Native Live reload Microservices

Slide 35

Slide 35 text

Why Quarkus Lower memory usage Faster startup Optimized for short-lived processes Kubernetes Native Live reload Microservices

Slide 36

Slide 36 text

● A cloud-native development, deployment and execution platform for business automation: ○ Rules and Decisions ○ Processes and Cases ● ... under the covers ○ the backbone is code generation based on business assets ○ executable model for the process/rule/decision definitions ○ type safe data model that encapsulates variables ○ REST api for each public business process/decision/rule Introducing

Slide 37

Slide 37 text

Compile generate Java sources in memory Integrating Quarkus and Kogito Writing a Quarkus extension public class KogitoAssetProcessor { @BuildStep(providesCapabilities = "io.quarkus.kogito") public void generateModel( ArchiveRootBuildItem root, BuildProducer generatedBeans, LaunchModeBuildItem launchMode) throws IOException { LaunchMode launchMode = launchModeItem.getLaunchMode(); if (hotReload(launchMode)) { return; } ApplicationGenerator appGen = createApplicationGenerator(root, launchMode); Collection generatedFiles = appGen.generate(); if (!generatedFiles.isEmpty()) { MemoryFileSystem trgMfs = new MemoryFileSystem(); CompilationResult result = compile( root, trgMfs, generatedFiles, generatedBeans, launchMode ); register( trgMfs, generatedBeans, launchMode, result ); } } } Avoid recompiling everything during an hot reload Generate executable model source files Register generated classes into Quarkus runtime

Slide 38

Slide 38 text

Kogito hot reload public class KogitoCompilationProvider extends JavaCompilationProvider { @Override public final void compile(Set filesToCompile, Context context) { File outputDirectory = context.getOutputDirectory(); try { ApplicationGenerator appGen = new ApplicationGenerator( appPackageName, outputDirectory ); Generator generator = addGenerator(appGen, filesToCompile, context); Collection generatedFiles = generator.generate(); HashSet generatedSourceFiles = new HashSet<>(); for (GeneratedFile file : generatedFiles) { Path path = pathOf(outputDirectory.getPath(), file.relativePath()); Files.write(path, file.contents()); generatedSourceFiles.add(path.toFile()); } super.compile(generatedSourceFiles, context); } catch (IOException e) { throw new KogitoCompilerException(e); } } } Write regenerated source in the file system Extends Java hot reload mechanism provided by Quarkus Regenerate executable model only for changed sources Pass the list of generated sources to super in order to recompile them

Slide 39

Slide 39 text

A simple Quarkus-based REST endpoint using Kogito @Path("/candrink/{name}/{age}") public class CanDrinkResource { @Inject @Named("canDrinkKS") RuleUnit ruleUnit; @GET @Produces(MediaType.TEXT_PLAIN) public String canDrink( @PathParam("name") String name, @PathParam("age") int age ) { SessionMemory memory = new SessionMemory(); Result result = new Result(); memory.add(result); memory.add(new Person( name, age )); ruleUnit.evaluate(memory); return result.toString(); } }

Slide 40

Slide 40 text

Demo 2 Drools on Quarkus

Slide 41

Slide 41 text

Mario Fusco Red Hat – Principal Software Engineer mario.fusco@gmail.com twitter: @mariofusco Q A Thanks ... Questions?