Layrry
A Runner and API for Layered Java Applications
Install / Use
/learn @moditect/LayrryREADME
= Layrry - A Launcher and API for Modularized Java Applications :linkattrs: :project-owner: moditect :project-name: layrry :project-group: org.moditect.layrry :project-version: 1.0.0.Final
image:https://github.com/{project-owner}/{project-name}/workflows/Build/badge.svg["Build Status", link="https://github.com/{project-owner}/{project-name}/actions"] image:https://img.shields.io/maven-central/v/{project-group}/{project-name}-core.svg[Download, link="https://search.maven.org/#search|ga|1|{project-group}"]
Latest version: {project-version}
http://andresalmiray.com/layrry-1-0-0-alpha1-has-been-released/[1.0.0.Alpha1 announcement]
Layrry is a launcher and Java API for executing modularized Java applications.
It allows to assemble modularized applications based on Maven artifact coordinates of the (modular) JARs to include. Layrry utilizes the Java Module System's notion of link:https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/ModuleLayer.html[module layers], allowing multiple versions of one module to be used within an application at the same time, as well as dynamically adding and removing modules at application runtime.
The module graph is built either declaratively (using YAML or TOML descriptors) or programmatically (using a fluent API).
Learn more about Layrry in these blog posts:
- link:https://www.morling.dev/blog/introducing-layrry-runner-and-api-for-modularized-java-applications/[Introducing Layrry: A Launcher and API for Modularized Java Applications]
- link:https://www.morling.dev/blog/plugin-architectures-with-layrry-and-the-java-module-system/[Plug-in Architectures With Layrry and the Java Module System]
- link:https://www.morling.dev/blog/class-unloading-in-layered-java-applications/[Class Unloading in Layered Java Applications]
- link:http://andresalmiray.com/building-a-layered-modular-java-application-watch-out-for-these/[Building a layered modular Java application? Watch out for these!]
Layrry also was presented at different (online) conferences and meet-ups, for example at vJUG (https://speakerdeck.com/gunnarmorling/plug-in-architectures-with-layrry-and-the-java-module-system-vjug[slides], https://www.youtube.com/watch?v=iJyys_LgG-U[recording]).
== Why Layrry?
The Java Module System doesn't define any means of mapping between modules (e.g. com.acme.crm) and JARs providing such module
(e.g. acme-crm-1.0.0.Final.jar) or retrieving modules from remote repositories using unique identifiers
(e.g. com.acme:acme-crm:1.0.0.Final). Instead, it's the responsibility of the user to obtain all required JARs of a modularized
application and provide them via --module-path.
Furthermore, the module system doesn't define any means of module versioning; i.e. it's the responsibility of the user to
obtain all modules in the right version. Using the --module-path option, it's not possible, though, to assemble an
application that uses multiple versions of one and the same module. This may be desirable for transitive dependencies of
an application, which might be required in different versions by two separate direct dependencies.
This is where Layrry comes in: utilizing the notion of module layers, it provides a declarative approach as well as an API for assembling modularized applications, organized in module layers. The JARs to be included are described using Maven GAV (group id, artifact id, version) coordinates, solving the issue of retrieving all required JARs in the right version.
Module layers allow to use different versions of one and the same module in different layers of an application (as long as they are not exposed in a conflicting way on module API boundaries).
Module layers and thus Layrry also allow application extensions to be added and removed dynamically at runtime. Here's link:https://github.com/moditect/layrry-examples/tree/master/modular-tiles[an example] for using the Layrry plug-in API to dynamically modify a JavaFX application:
image:images/javafx-layrry.gif[JavaFX app based on Layrry]
== Using the Layrry Launcher
The Layrry Launcher is a CLI tool which takes a configuration of a layered application and executes it. It's used like so:
[source] [subs="attributes"]
layrry-launcher-{project-version}-all.jar --layers-config <path/to/layers.yml> [program arguments]
E.g. like so:
[source] [subs="attributes"]
layrry-launcher-{project-version}-all.jar --layers-config hello-world.yml Alice Bob
The application layers configuration file is a YAML file which the following structure:
[source,yaml]
layers: <name 1>: modules: - "G:A:V" - "G:A:V" - ... <name 2>: parents: - "<name 1>" modules: - ... - ... <name 3>: parents: - "<name 2" directory: "relative/path/to/directory/of/layer/directories
main: module: <main module> class: <main class>
Each layer comprises:
- A unique name
- The list of parent layers
- The list of contained modules given via Maven GAV coordinates OR
- A directory which contains one or more sub-directories, each of which represent one layer made up of the modular JARs within that sub-directory; the directory path is resolved relatively to the location of the layrry.yml file. Alternatively the directory may be an absolute path however be very careful as this may cause a non portable configuration.
As an example, consider the following application whose modules foo and bar depend on two different versions of the greeter module:
image:images/example.png[Layrry Example]
Running this application wouldn't be possible with the default module path, which only allows for one version of a given module. Here is how the application can be executed via Layrry, organizing all the modules in multiple layers:
[source,yaml]
layers: log: modules: - "org.apache.logging.log4j:log4j-api:2.20.0" - "org.apache.logging.log4j:log4j-core:2.20.0" - "com.example:logconfig:1.0.0" foo: parents: - "log" modules: - "com.example:greeter:1.0.0" - "com.example:foo:1.0.0" bar: parents: - "log" modules: - "com.example:greeter:2.0.0" - "com.example:bar:1.0.0" app: parents: - "foo" - "bar" modules: - "com.example:app:1.0.0" main: module: com.example.app class: com.example.app.App
Alternatively you may use TOML instead of YAML
[source,toml]
[layers.log] modules = [ "org.apache.logging.log4j:log4j-api:2.20.0", "org.apache.logging.log4j:log4j-core:2.20.0", "com.example.it:it-logconfig:1.0.0"] [layers.foo] parents = ["log"] modules = [ "com.example.it:it-greeter:1.0.0", "com.example.it:it-foo:1.0.0"] [layers.bar] parents = ["log"] modules = [ "com.example.it:it-greeter:2.0.0", "com.example.it:it-bar:1.0.0"] [layers.app] parents = ["foo", "bar"] modules = ["com.example.it:it-app:1.0.0"] [main] module = "com.example.app" class = "com.example.app.App"
Be sure to use .toml as file extension to let Layrry know which format should be parsed.
You can find the complete example in the tests of the Layrry project.
The Layrry Launcher accepts the following arguments:
- --basedir: The base directory from which plugin directories will be resolved. Layrry will use the parent directory of the layers config file if this value is not set.
- --layers-config: Path to the layers config file. The file must use any of the supported config formats. REQUIRED.
- --properties: Path to additional properties in Java
.propertiesformat. These properties will be used to replace value placeholders found in the layers config file. OPTIONAL.
== Using JBang
link:https://github.com/jbangdev/jbang[JBang] can launch self contained Java sources, JShell scripts, JARs. jbang has a feature that allows you to try out Layrry without having to install or build Layrry yourself. You only need a JDK (11+ is preferred) and jbang installed. Once you do, you may invoke the previous example with
[source]
jbang layrry@moditect --layers-config layers.yml
JBang will resolve and download the appropriate Layrry bootstrap binary, then Layrry resolves the modules described in the input configuration file, finally the application is launched.
== Dynamic Plug-Ins
Layrry also supports the dynamic addition and removal of plug-ins at runtime. For that, simply add or remove plug-in
sub-directories to the directory of a layer configuration. Layrry watches the given plug-ins directory and will add or
remove the corresponding module layer to/from the application in case a new plug-in is added or removed. The core of an
application can react to added or removed module layers. In order to do so, the module org.moditect.layrry:layrry-platform
must be added to the application core layer and an implementation of the PluginLifecycleListener interface must be
created and registered as service:
[source]
public interface PluginLifecycleListener { void pluginAdded(PluginDescriptor plugin);
void pluginRemoved(PluginDescriptor plugin);
}
Typically, an application will retrieve application-specific services from newly added module layers:
[source,java]
@Override public void pluginAdded(PluginDescriptor plugin) { ServiceLoader<MyService> services = ServiceLoader.load( plugin.getModuleLayer(), MyService.class);
services.forEach(service -> {
// only process services declared by the added layer itself, but not
// from ancestor layers
if (service.getClass().getModule().getLayer() == layer) {
// process service ...
}
});
}
To avoid class-loader leaks, it's vital that all references to plug-in contributed classes are released upon pluginRemoved().
Note that classes typically will not instantly be unloaded, but only upon the next full GC (when using G1).
You can find a complete example for the usage of dynamic plug-ins in the vertx-example
