Rhizomatic is a runtime built on the Java Platform Module System (JPMS). It provides an extensibility model, a service programming model based on Guice, REST endpoint publishing using JAX-RS annotations, and a web application server.
Services are implemented as simple Java classes using @Service
. Their dependencies are injected using Guice and therefore adhere to Guice IoC semantics:
@Service
public class ClientService {
@Inject
MessageService service;
//...
}
Classes marked with @Service
will be registered when the runtime is started.
If you do not want to use service class scanning or your services require special setup, application modules can provide implementations of com.google.inject.Module
:
open module module.a {
requires io.rhizomatic.api;
provides com.google.inject.Module with CustomModule;
}
When the application module is loaded, CustomModule
will be installed in the Guice Injector.
REST endpoints are services decorated with JAX-RS annotations. The runtime will discover and enable these services at startup:
@Path("messages")
@Consumes("application/json")
@Produces("application/json")
public class TestResource {
@Inject
protected MessageStore messageStore;
@GET
@Path("recent")
public List<Message> messages() {
return messageStore.getRecentMessages();
}
}
Base URIs for resource services can be defined using the o.rhizomatic.api.annotations.EndpopintPath
annotation on a module definition:
@EndpointPath("api")
open module module.a {
requires java.ws.rs;
requires io.rhizomatic.api;
}
If multiple modules use the same endpoint path, their resource services will be concatenated under the same path.
Applications may often use messaging systems such as NATS and Kafka or streaming libraries for communications. Instead of providing abstractions on their native APIs, Rhizomatic encourages writing injectable services that encapsulate their use. For example:
@Service
public class Producer {
Connection natsConnection;
@Init
public void init() {
// initialize and create a NATS connection
natsConnection = Nats.connect();
}
public TestChannel() {
}
public void send(String message) {
natsConnection.publish("test", message.getBytes());
}
}
Encapsulation centralizes the complexity of configuring communications systems and allows them to be substituted during testing and other alternative deployment scenarios.
Applications are organized as a set of Java modules. When developing an application, simply organize your code as Java modules.
Application modules are configured into a system and deployed. Unlike legacy runtimes that deploy archives to a server or are deployed as an "uber" jar, Rhizomatic systems are assembled from a layout. A layout can be a set of modules located in a Docker container or an IDE project on a filesystem.
A boot module is responsible for configuring a system from a layout. The boot module provides a Java module service that implements io.rhizomatic.api.SystemDefinition
:
module bootstrap.dev {
requires io.rhizomatic.api;
provides SystemDefinition with DevSystemDefinition;
}
The SystemDefinition
implementation returns a set of RzLayer
definitions that describes the JPMS layers and contained modules (including their location) to run.
Different boot modules can be loaded based on the runtime environment, e.g. "production" or "development". The Rhizomatic API contains a number of DSL classes for dynamically
defining layers and modules. For example, a layer can be deployed by defining a set of module inclusions from a filesystem directory such as an IDE project root.
A system image can be created using the Rhizomatic Gradle assembly plugin. The plugin will create an image from the required Rhizomatic libraries, application modules, and transitive third-party dependencies:
apply plugin: 'io.rhizomatic.assembly'
def rzVersion = '0.3.5'
rhizomaticAssembly {
bootstrapModule = "bootstrap"
bootstrapName = "bootstrap"
appGroup = "io.massiv.sample"
}
dependencies {
implementation group: 'io.rhizomatic', name: 'rhizomatic-kernel', version: rzVersion
implementation group: 'io.rhizomatic', name: 'rhizomatic-inject', version: rzVersion
implementation group: 'io.rhizomatic', name: 'rhizomatic-web', version: rzVersion
implementation project(":app-module")
implementation project(":bootstrap")
}
Using the plugin, different runtime images can be created using various module combinations.
A Rhizomatic system may also deploy one or more web applications. Web applications and their content locations are defined by the boot module SystemDefinition
.
Note web applications do not need to be packaged as WAR files since many web applications do not make use of Java-based UI frameworks. For example, an Angular or React application can invoke REST resource services provided by another module.
Rhizomatic can be embedded in a test fixture. In a Gradle build file, set the test dependencies to include the required Rhizomatic dependencies (for example, if REST resources are not needed, do not include the Rhizomatic web extension):
testImplementation group: 'io.rhizomatic', name: 'rhizomatic-kernel', version: rzVersion
testImplementation group: 'io.rhizomatic', name: 'rhizomatic-inject', version: rzVersion
testImplementation group: 'io.rhizomatic', name: 'rhizomatic-web', version: rzVersion
In a test fixture, configure and start the system, passing in services, mocks, and suppporting classes:
public class SomeTest{
@Test
public void testHello() {
InjectionModule.install();
WebModule.install();
var builder = Rhizomatic.Builder.newInstance();
var rhizomatic = builder.moduleMode(false).silent(true).services(HelloImpl.class, TestResource.class).build();
// ....
rhizomatic.start();
rhizomatic.shutdown();
}
}
Rhizomatic does not provide a logging implementation. Instead, it uses a monitoring interface, io.rhizomatic.api.Monitor
, that can be implemented by a module:
module bootstrap.dev {
requires io.rhizomatic.api;
provides Monitor with CustomMonitor;
}
To enable the monitor implementation, the module must be loaded as a library (not an application module) since monitor messages are emitted during application boot.
To build from source and publish locally, run:
./gradlew clean test publishToMavenLocal