Skip to main content

Custom components

The Genesis Application Platform allows developers to create modules for business logic.

Injecting dependencies

A key part of writing custom code is injecting the objects your module or process relies on.

Genesis supports some of the key annotations from javax.annotation and javax.inject, defined in Java Platform Enterprise Edition, using Google Guice as the internal dependency injection mechanism. In addition, Genesis provides some of its own annotations.

@Module

All classes that need to be created on microservice start-up must be annotated with @Module. This ensures the instance is created as singleton.

@ProviderOf

This is used to annotate a class that is responsible for acting as Factory for a specified type. Adding the @Singleton annotation ensures only one Factory class is created.

Example:

import com.google.inject.Provider
import com.google.inject.Singleton

@Singleton
@ProviderOf(type = PriceFeed::class)
class PriceFeedProvider : Provider<PriceFeedProvider> {
override fun get(): PriceFeed {
return new PriceFeed()
}
}

@Inject

This is used to annotate a field or constructor to indicate to Genesis that it should inject an object of matching type during the dependency injection stage. These types can be provided with the aforementioned @Module and @ProviderOf annotations. You should use the Java @Inject annotation for best practice.

@Named

This annotation is used to provide Genesis system definition properties as part of the dependency injection mechanism, and should be used alongside @Inject.

@PostConstruct

The Genesis microservice runtime environment will call this only once after initialisation of the object, including all injected beans.

@PreDestroy

The Genesis microservice runtime environment will call this only once, just before Genesis removes the object from the application context on JVM shutdown.

Example

The example below combines an @Module, @Named and @Inject annotation on a constructor and on a field, and it uses @PostConstruct and @PreDestroy.

@Module
class PriceFeed @Inject constructor(@Named("CONNECTION_URL") private val connectionUrl: String) {

@Inject
private lateinit var value: Value

@PostConstruct
fun init() {
// code would go here
}

@PreDestroy
fun cleanUp() {
// code would go here
}
}

Conditional annotations

Conditional on property

You can define a module as conditional, based on system definition properties. In the example below, the AeronDriverModule will only be instantiated if the MqLayer property is set to AERON:

@Module
@ConditionalOnProperty("MqLayer", conditionalValue = "AERON")

Conditional on class

Likewise, modules can be defined to be conditional on classes, so if the selected class has been instantiated (e.g. the previous example shown as AeronDriverModule), then our dependency injection mechanism will instantiate this class as well.

@Module
@ConditionalOnClass(AeronDriverModule::class)

Conditional on missing class

You can also add conditional modules on missing classes. This annotation is very helpful if you want to develop an “extension” mechanism as part of your application.

For example, you could define an interface, and an @Module implementing this interface with a conditional on that interface class being missing. This means that if a class is defined as an @Module that implements the interface, that class will be selected (i.e. this class can be defined in an external jar and added to classpath using framework config). If no class is defined as an @Module to implement that interface, then the @ConditionalOnMissingClass module will be instantiated.

For example, imagine you want to create a “hook” into the Genesis message-handling logic, so it can be extended as desired in other applications. The interface and default class would look like this in Kotlin:

interface MessageTracer{
fun onMessageReceived(msg: Message)
}

@Module
@ConditionalOnMissingClass(MessageTracer::class)
class DefaultMessageTracer : MessageTracer{
override fun onMessageReceived(msg: Message) = println("This is a message $msg")
}

If an application requires custom logic, a new @Module class could be defined in a separate jar file like this:

@Module
class ProductMessageTracer : MessageTracer{
override fun onMessageReceived(msg: Message) = println("Hi $msg")
}

If the package for this class is available in the classpath ( <classpath> section in processes.xml) and is also being scanned (i.e. <package> section in processes.xml), ProductMessageTracer will take precedence when starting the process and DefaultMessageTracer will be ignored. Otherwise, DefaultMessageTracer will be instantiated.

Conditional on property and missing class

Lastly, we have a combination of both “conditional on property” and “conditional on missing class“ annotations. Referring to the previous example, we could use both features at once like this:

@Module
@ConditionalOnPropertyAndMissingClass("MessageTracer", conditionalValue = "DEFAULT", MessageTracer::class)
class DefaultMessageTracer : MessageTracer{
override fun onMessageReceived(msg: Message) = println("This is a message $msg")
}

The process will crash on start up if the system definition value for MessageTracer is set to anything other than DEFAULT and no implementation has been provided for MessageTracer. This annotation can be used to enforce good practice.

Injectable properties from System Definition

Example of genesis-system-definition.kts file

systemDefinition {
global {
item(name = "CONFIG_FILE_NAME", value = "/data/")
// other params omitted for simplicity
}
}

System Definition property being referenced in Java file

@Inject
public RequestReplyDefinitionReader(RxDb db,
@Named("CONFIG_FILE_NAME") String configFileName) throws GenesisConfigurationException {
this(db.getDictionary(), configFileName);
}