efesto/EfestoDeveloperGuide.md
Efesto framework feature a plugin style following the "Clean Architecture" principles.
This, basically means that:
Those simple guidelines are needed to maintain the good shape of the framework, preventing it to become a de-facto monolith.
Efesto framework is based on the clear separation of two components: compilation and runtime. The first is responsible to perform all the action necessary to take a model definition and make it executable, like validate, instantiate objects or code-generate classes (this definition may change on a plugin-in base). The latter is responsible to use the above executable to evaluate responses based on user input.
The compilation part is defined by the CompilationManager, whose work is to
The runtime part is defined by the RuntimeManager, whose work is to
The result of a compilation is listed inside a model-specific IndexFile. For each model (e.g. drl, dmn, pmml, etc) there should be at most one of such file. The file name format is
IndexFile.{model}_json (e.g. IndexFile.drl_json, IndexFile.dmn_json, IndexFile.pmml_json).
This file contains:
Currently, this file is written on the target directory, but this may be overridden using the indexfile.directory system property.
The plugins, in turns, are composed on a compilation component and a runtime component. To maintain clear dependency separation, those two components should be in separated modules. If a common class is needed by both, it should be put in a third module. In that case, this third module should not depend on any core components, because otherwise it would create an indirect binding between all the components. The plugin should implement org.kie.efesto.compilationmanager.api.service.KieCompilerService and/or org.kie.efesto.runtimemanager.api.KieRuntimeService, that are discovered at execution with SPI.
It is also possible to create multiple jar compilation modules and/or jar runtime modules: as example, see the kie-drl structure.
src/main/resources/META-INF/services, write the org.kie.efesto.compilationmanager.api.service.KieCompilerService file, with the full class name of the implementing classThere are two methods to implement:
<T extends EfestoResource> boolean canManageResource(T toProcess);<T extends EfestoResource, E extends EfestoCompilationOutput> List<E> processResource(T toProcess, EfestoCompilationContext context);The first method is used by the framework to know if that specific plugin is able to process a given resource. For the moment being, as a design decision, there must be at most one plugin that returns true for any given resource instance.
The expression given resource instance means that the same kind of resource may be managed by different plugins, but there is some further identifier based on which only one plugin can manage a specific resource.
An example of this is the EfestoFileResource, that is simply a File wrapper. Most/all plugins may be able to manage an EfestoFileResource, but this class has also a getModelType() method, based on which every plugin may "decide" if it can manage it or not.
There is an enforcing check that eventually throws an Exception when multiple plugins returns true, but developers are the first responsible for that.
The logic behind this boolean is based on the actual type of the resource, on the identifier of the resource, and on other plugin-specific logic.
The second method is responsible to actually process the resource.
The T parameter is actually an org.kie.efesto.compilationmanager.api.model.EfestoResource.
The E parameter is actually an org.kie.efesto.compilationmanager.api.model.EfestoCompilationOutput.
Beside implementing those two methods, there are no strict rules to follow. For example, it is possible that one single engine is able to manage different format of inputs (e.g. the rule engine). In this case, it is possible to create one single implementation to the different kind of inputs, or it is possible to create one implementation for every kind of input (in which case, anyone of them should be defined in the org.kie.efesto.compilationmanager.api.service.KieCompilerService SPI file).
The org.kie.efesto.compilationmanager.api.model.EfestoResource contains the data to be processed.
The org.kie.efesto.compilationmanager.api.model package contains all the default EfestoResource implementations.
Every plugin could create its own subclass of org.kie.efesto.compilationmanager.api.model.EfestoResource to fullfill its goals; e.g. for differentiate the different kind of inputs.
For the moment being, the container project (i.e., the project that uses the efesto framework) is responsible to instantiate the resource. Following iterations will provide helper classes for resource instantiation.
The org.kie.efesto.compilationmanager.api.model.EfestoCompilationOutput contains the results of the processing.
Every plugin could create its own subclass of org.kie.efesto.compilationmanager.api.model.EfestoCompilationOutput to fullfill its goals; e.g. to create a "redirection" resource, i.e. a "compilation output" that it is also a resource to be processed by another plugin, as for DrlPackageDescrSetResource. In case of code-generation, it is possible that EfestoCallableOutputClassesContainer would be enough to use.
The org.kie.efesto.compilationmanager.api.model package contains all the default EfestoCompilationOutput implementations.
The org.kie.efesto.common.api.model.EfestoCompilationContext define the environment to use for the processing and requires a Classloader as building parameter; EfestoCompilationContext embed a ClassLoader that wrap the one provided for instantiation. Since the most common use case is to do code-generation, that embedded classloader is capable of dynamic class loading, and is responsible to class compilation and classloader manipulation; it should not be exposed publicly to avoid uncontrolled and unforeseeable behaviors.
The org.kie.efesto.compilationmanager.api.model package contains the default EfestoCompilationContextImpl implementation.
Every plugin could also create its own subclass of org.kie.efesto.common.api.model.EfestoCompilationContext; e.g. DrlCompilationContext subclass it to also use KnowledgeBuilderConfiguration.
This subclassing activity is important to avoid that plugin-specific needs leaks out of the plugin boundary.
For the moment being, the container project (i.e., the project that uses the efesto framework) is responsible to instantiate the context (providing the required classloader). Following iterations will provide helper classes for context instantiation.
src/main/resources/META-INF/services, write the org.kie.efesto.runtimemanager.api.service.KieRuntimeService file, with the full class name of the implementing classThere are two methods to implement:
boolean canManageInput(EfestoInput toEvaluate, EfestoRuntimeContext context);Optional<E> evaluateInput(T toEvaluate, EfestoRuntimeContext context);The first method is used by the framework to know if that specific plugin is able to evaluate a given input. For the moment being, as a design decision, there must be at most one plugin that returns true for any given input.
The expression given input means that the same kind of input may be managed by different plugins, but there is some further identifier based on which only one plugin can manage a specific input.
There is an enforcing check that eventually throws an Exception when multiple plugins returns true, but developers are the first responsible for that.
The logic behind this boolean is based on the actual type of the input, on the identifier of the input, and on other plugin-specific logic.
The second method is responsible to actually evalute the input.
The T parameter is actually an org.kie.efesto.runtimemanager.api.model.EfestoInput
The E parameter is actually an org.kie.efesto.runtimemanager.api.model.EfestoOutput
Beside implementing those two methods, there are no strict rules to follow. For example, it is possible that one single engine is able to evaluate different format of inputs (e.g. the rule engine). In this case, it is possible to create one single implementation to the different kind of inputs, or it is possible to create one implementation for every kind of input (in which case, anyone of them should be defined in the org.kie.efesto.runtimemanager.api.service.KieRuntimeService SPI file).
The org.kie.efesto.runtimemanager.api.model.EfestoInput contains the data to be evaluated.
Every plugin could create its own subclass of org.kie.efesto.runtimemanager.api.model.EfestoInput to fullfill its goals; e.g. for differentiate the different kind of inputs.
The org.kie.efesto.runtimemanager.api.model package contains all the default EfestoInput implementations.
For the moment being, the container project (i.e., the project that uses the efesto framework) is responsible to instantiate the input. Following iterations will provide helper classes for input instantiation.
The org.kie.efesto.runtimemanager.api.model.EfestoOutput contains the result of the evaluation.
Every plugin should create its own subclass of org.kie.efesto.runtimemanager.api.model.EfestoOutput to fullfill its goals; e.g. to provide better typed kind of outputs.
The org.kie.efesto.runtimemanager.api.model package contains the EfestoOutput definition and the default abstract AbstractEfestoOutput implementation.
he org.kie.efesto.common.api.model.EfestoRuntimeContext define the environment to use for the evaluation and requires a Classloader as building parameter; EfestoRuntimeContext embed a ClassLoader that wrap the one provided for instantiation. Since the most common use case is to do code-generation, that embedded classloader is capable of dynamic class loading, and is responsible to class compilation and classloader manipulation; it should not be exposed publicly to avoid uncontrolled and unforeseeable behaviors.
The org.kie.efesto.runtimemanager.api.model package contains the default EfestoRuntimeContextImpl implementation.
Every plugin could also create its own subclass of org.kie.efesto.runtimemanager.core.model.EfestoRuntimeContextImpl.
This subclassing activity is important to avoid that plugin-specific needs leaks out of the plugin boundary.
For the moment being, the container project (i.e., the project that uses the efesto framework) is responsible to instantiate the context (providing the required classloader). Especially for code-generating plugins, it is important that the provided classloader is the same as the one used for compilation (for on-the-fly compilation) or it contains all the classes compiled at compilation time. Following iterations will provide helper classes for context instantiation.
Chainability refers to the ability of the plugin to invoke each other. To provide that and avoid coupling/binding between them the following guidelines apply
EfestoCompilationOutput that also implements EfestoResource (see DrlPackageDescrSetResource). The compilation manager will be responsible to forward that intermediary artifact to the latter plugin. As usual, care must be giving to avoid leaking of plugin-specific classes outside the plugin parameters.EfestoInput containing all the required data, and explicitily send it to the RuntimeManager. The compilation manager will be responsible to forward that intermediary input to the latter plugin. As usual, care must be giving to avoid leaking of plugin-specific classes outside the plugin parameters.