Creating custom module
Since the Application Engine is implemented in Java and Spring Boot, a module is also a Spring Boot application. The main difference is that module's main class is not executed when it is loaded into the Application Engine context, and only those Spring beans discovered through Spring’s component scanning are loaded.
Maven configuration
First, it is recommended to fill out the pom.xml file. When a module is compiled using our Maven plugin, several Maven attributes are inserted into the resulting JAR archive. These attributes are used to identify and describe the module when it is loaded into the Application Engine.
Module building Maven plugin recognises these attributes from the pom.xml file:
- Project name -
<name> - Version -
<version> - Group ID -
<groupId> - Artifact ID -
<artifactId> - Description -
<description> - Source control details (SCM connection and URL) -
<scm> ... <url></url></scm> - Issue management information (issue system and URL)-
<issueManagement><system></system><url></url></issueManagement> - Licensing (name and URL) -
<licenses><license><name></name><url></url></license></licenses> - Organisation details -
<organization><name></name><url></url></organization> - A combined list of developers associated with the project, including their names, emails, and organizations -
<developers> - Build details such as the JDK version and build timestamp
To configure your Maven project as an Application Engine module, you need to add the Application Engine as a dependency with the provided scope, since you do not want to compile the entire Engine together with your module.
<dependency>
<groupId>com.netgrif</groupId>
<artifactId>application-engine</artifactId>
<version>7.0.0</version>
<scope>provided</scope>
</dependency>NAE Module Maven Plugin
To package your project as an Application Engine Module, you can use the nae-module-maven-plugin. This maven plugin takes the JAR archive created during the Maven package phase and wraps it into a ZIP archive with the structure required for an Application Engine module.
It is important not to build the project as a "fat JAR" using the Spring Boot Maven plugin. The module must be a plain JAR containing only of your application classes.
To use the plugin, add the following snippet to the <build><plugins> section of your pom.xml file:
<plugin>
<groupId>com.netgrif</groupId>
<artifactId>nae-module-maven-plugin</artifactId>
<version>1.2.0</version>
<configuration>
<hostApp>
<groupId>com.netgrif</groupId>
<artifactId>application-engine</artifactId>
<version>7.0.0</version>
</hostApp>
<singleOutput>true</singleOutput>
<excludes>
<exclude>*log4j</exclude>
<exclude>*logback*</exclude>
<exclude>*lombok*</exclude>
</excludes>
</configuration>
<executions>
<execution>
<id>build-module</id>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
</plugin>The nae-module-maven-plugin creates a ZIP archive that contains the module's JAR file and a libs directory with all dependency JARs that are not already included in the <hostApp> (which is almost always the Application Engine).
Excluding module dependency
When your module uses libraries that are not required at runtime (e.g., Lombok), it is recommended to exclude these dependencies from being packaged into the module.
To achieve this, specify the unnecessary dependencies in the <excludes> section of the Maven plugin configuration. Each <exclude> entry uses an ant-matcher in the format groupId:artifactId:version to identify the dependency. A dependency is excluded only if it appears in the module's dependency tree (even if it is a transitive dependency).
Spring configuration
Since a module is a Spring Boot project that is loaded into another context (the Application Engine context), you must configure component scanning so that all Spring beans defined in the module are registered within the Application Engine context when the module is loaded.
For testing purposes, if you want to start the module as a Spring application, include the com.netgrif.application.engine package in the component scan so that the Application Engine beans are also registered. This way allows you to run the module in an environment that closely resembles how it behaves when loaded into the Application Engine.
You can configure this in the module's main class, as shown in the example below.
@Slf4j
@EnableAsync
@EnableCaching
@SpringBootApplication
@ComponentScan({"com.netgrif.application.engine", "org.example"})
public class ExampleModuleApplication {
public static void main(String[] args) {
new SpringApplicationBuilder()
.sources(ApplicationEngine.class, ExampleModuleApplication.class)
.run(args);
}
}IMPORTANT
Remember that the main class is not executed when the module is loaded into the Application Engine from the modules folder.
To ensure that component scanning for module Spring beans works correctly, even in the production environment, you need to define a configuration bean annotation with a component scan. For example:
package org.example.module;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Slf4j
@Configuration
@ComponentScan("org.example.module")
public class ExampleModuleConfiguration {
}The important part is to set the @ComponentScan annotation to the java root package of your module project.
Module service
The main purpose of a module is to extend the functionality of an Application Engine worker, mainly through the Action API. To provide a Spring @Service bean from a module to the Action API so it can be called from a process, you need to annotate the bean with the @ModuleService Java annotation.
@Service
@ModuleService("example")
public class ExampleService {
public String getHelloWorld() {
return "Hello World";
}
}Since more than one module can be loaded in the worker at the same time, the annotation value can be used to group service beans together. Calling a method from the service in the process action can be done with pattern Module.<service group>.<service bean name>.<service method>.
To call the service from the previous example in an action, type the following into an action code:
Module.example.exampleService.getHelloWorld()In the module service you can inject (@Autowire) all services in the Application Engine.
Do you need to create a new case? - Autowire WorkflowService.
Do you need to get information about users? - Autowire UserService.
Startup Runner
A module may require initialization or may need to do some business logic before the worker fully starts. To supports this, you can define a startup runner bean within the module.
Spring Boot provides a framework with its CommandLineRunner class. The Application Engine wrapped it with additional logic to ensure that runner classes in the module are executed in correct order.
To implement a startup runner, you need to create Spring bean (e.g., @Component), that implements ProcessApplicationStartupRunner interface and it is annotated with @RunnerOrder annotation to set order in which the runner should be executed.
@Slf4j
@Component
@RunnerOrder(100)
@AllArgsConstructor
public class ExmapleRunner implements ProcessApplicationStartupRunner {
@Override
public void run(ApplicationArguments args) {
log.info("My runner has started");
}
}The value of the @RunnerOrder annotation is an integer that defines the execution order of the runner. Runners with smaller values are executed earlier.
The ProcessApplicationStartupRunner interface has only one method public void run(ApplicationArguments args) which is a proxy method of Spring CommandLineRunner#run method. The ApplicationArguments are arguments provided to JVM at the start of the worker, or in the case of local testing the module.
Deploy
To deploy a module to the Application Engine, extract the module package (the ZIP archive from the nae-module-maven-plugin) to modules directory in the working directory of the Application Engine instance. There should be at least a JAR archive of the module (and libs directory if the module has other dependencies).
If you run the Application Engine as a docker container, you can mount a directory from the host machine where you extract the module package to the container modules directory.
docker-compose.yaml service:
nae-worker:
image: netgrif/application-engine:7.0.0
container_name: netgrif-platform-engine
environment:
...
ports:
- "8080:8080"
- "9090:9090"
... other configuration
volumes:
- ./folder_on_host:/opt/netgrif/engine/modules/customThe Application Engine scans the modules directory recursively for JAR archives, so any nested directory structure is supported.
