Crayon is an application bootstrap that makes it easy to build modular applications on top of Google Guice. Crayon provides access to logging, module discovery, contributions, configuration, services and more.
Development version: 2.0.0-SNAPSHOT
Crayon is split into several sub-projects, depending on your needs different artifacts need to be imported into your project:
<dependency>
<groupId>se.l4.crayon</groupId>
<artifactId>crayon-app</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>
Application
is the recommended way to start an application built on Crayon.
It will take care of discovery of modules, configure logging and will start any
services defined by modules.
Example of an application using a single module MainModule
:
public class ServerApp {
public static void main(String[] args) {
Application app = Application.withIdentifier("appId")
.add(MainModule.class)
.start();
}
}
Logging is provided via Logback. Application
will
automatically load the file logback.xml
if available and will default to
outputting log messages with INFO
level to the console if no configuration
is available.
Artifact: crayon-module
Module: se.l4.crayon.module
Modules in Crayon are built around dependency injection and are an extensions of Guice modules. With Crayon it is recommended to split a project into several modules, which if you're using Java 9+ may be JPMS modules as well.
For each module in your application you should create a class that extends
CrayonModule
that describes the classes the module makes available, contributes
things such as services and what other modules are required by
this module.
Here's a module that contributes a service that will start during application startup:
public class HttpModule extends CrayonModule {
@Override
public void configure() {
// Indicate that we depend on services
install(new ServicesModule());
}
/**
* This is a contribution method that the crayon-services module will invoke
* during application startup.
*/
@ServiceContribution
public void contributeService(ServiceCollector collector, HttpService service) {
collector.add(service);
}
}
Crayon supports auto-discovery of modules during startup using ServiceLoader.
When using JPMS modules this can be provided via module-info.java
:
provides se.l4.crayon.module.CrayonModule with com.example.HttpModule;
If not using JPMS it can be provided by the file META-INF/services/se.l4.crayon.module.CrayonModule
on the classpath. This file should include the fully qualified class name of
your module:
com.example.HttpModule
Artifact: crayon-contributions
Module: se.l4.crayon.contributions
Contributions is how Crayon wires modules together, contributions are defined as methods in a module class with an annotation describing the type of contribution.
It is possible for these methods to have parameters that are provided via injection:
@ExampleContribution
public void contributeThing(ObjectFromGuice thing) {
...
}
The order at which contributions are invoked can be configured:
@ExampleContribution
@Named("thingA")
public void contributeThingA() {
}
@ExampleContribution
@Before("thingA")
public void contributeThingB() {
// This will run before the contribution named "thingA"
}
The annotations Before
, After
and Order
can be used for ordering.
Custom contributions are bound via annotations. First define a custom binding annotation to be used as the contribution annotation:
@BindingAnnotation
@Target({ ElementType.METHOD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface StorageContribution
{
}
In your module call bindContributions
with your annotation. This will bind an
instance of Contributions
annotated with custom annotation. When desired you
can then call the run
method on the Contributions
instance to run all
the contributions:
public class StorageModule extends CrayonModule {
public void configure() {
bindContributions(StorageContribution.class);
// Other configuration here
}
@Provides
@Singleton
public Storage provideStorage(@StorageContribution Contributions contributions) {
contributions.run();
return new Storage(...);
}
}
Other modules may use your contribution as normal:
public class StorageExtensionModule extends CrayonModule {
@StorageContribution
public void contributeSomeStorageStuff() {
// This method is run when the contributions are triggered by StorageModule
}
}
Artifact: crayon-config
Module: se.l4.crayon.config
When a Crayon application is started it will automatically load configuration files and make these available to modules. This is done via Config provided by L4 commons.
By default the first of ./appId.conf
, /etc/appId/default.conf
or ./default.conf
will be loaded.
It is recommended that modules bind up classes via bindConfig
:
public class ExampleModule extends CrayonModule {
@Override
public void configure() {
bindConfig(ExampleConfig.class)
.withDefault(new ExampleConfig(8080))
.to("example.path.in.config");
}
}
@Use(ReflectionSerializer.class)
public class ExampleConfig {
@Expose
@NotEmpty
private int port;
}
Defaults can also be provided via contributions:
@ConfigContribution
public void contributeConfig(ConfigBuilder builder) {
builder.with("example.path.in.config.port", 8080);
}
Artifact: crayon-services
Module: se.l4.crayon.services
Artifact: crayon-types
Module: se.l4.crayon.types
Artifact: crayon-vibe
Module: se.l4.crayon.vibe
Crayon provides integration with Vibe
for health monitoring. By default applications will bind up some JVM health
metrics under the scope jvm
.
Depending on the module VibeModule
will make an instance of
Vibe
available for use.
public class ExampleModule extends CrayonModule {
@Override
public void configure() {
// Indicate that this module uses Vibe
install(new VibeModule());
// Bind something that uses a Vibe instance
bind(Thing.class).to(ThingImpl.class);
}
}
To make things exported via Vibe useable you will want to bind up one or more
backends depending on your needs. These can be bound via a @VibeBackendContribution
:
@VibeBackendContribution
public void contributeVibeBackend(Vibe.Builder builder) {
builder.addBackend(createdBackend);
}