Using services in a flow
Service classes are long-lived instances that can trigger or be triggered by flows from within a node. A Service class is limited to a single instance per node. During startup, the node handles the creation of the service. If there is problem when instantiating service the node will report in the log what the problem was and terminate.
Services allow related, reusable, functions to be separated into their own class where their functionality is grouped together. These functions can then be called from other services or flows.
Creating a Service
To define a Service class:
- Add the
CordaService
annotation - Add a constructor with a single parameter of
AppServiceHub
- Extend
SingletonSerializeAsToken
Below is an empty implementation of a Service class:
@CordaService
class MyCordaService(private val serviceHub: AppServiceHub) : SingletonSerializeAsToken() {
init {
// Custom code ran at service creation
// Optional: Express interest in receiving lifecycle events
services.register { processEvent(it) }
}
private fun processEvent(event: ServiceLifecycleEvent) {
// Lifecycle event handling code including full use of serviceHub
when (event) {
STATE_MACHINE_STARTED -> {
services.vaultService.queryBy(...)
services.startFlow(...)
}
else -> {
// Process other types of events
}
}
}
// public api of service
}
@CordaService
public class MyCordaService extends SingletonSerializeAsToken {
private final AppServiceHub serviceHub;
public MyCordaService(AppServiceHub serviceHub) {
this.serviceHub = serviceHub;
// Custom code ran at service creation
// Optional: Express interest in receiving lifecycle events
serviceHub.register(SERVICE_PRIORITY_NORMAL, this::processEvent);
}
private void processEvent(ServiceLifecycleEvent event) {
switch (event) {
case STATE_MACHINE_STARTED:
serviceHub.getVaultService().queryBy(...)
serviceHub.startFlow(...)
break;
default:
// Process other types of events
break;
}
}
// public api of service
}
The AppServiceHub
provides the ServiceHub
functionality to the Service class, with the extra ability to start flows. Starting flows
from AppServiceHub
is explained further in Starting Flows from a Service.
The AppServiceHub
also provides access to database
which will enable the Service class to perform DB transactions from the threads
managed by the Service.
Also the AppServiceHub
provides ability for CordaService
to subscribe for lifecycle events of the node, such that it will get notified
about node finishing initialisation and when the node is shutting down such that CordaService
will be able to perform clean-up of some
critical resources. For more details please have refer to KDocs for ServiceLifecycleObserver
.
Service Lifecycle Events
A Corda node will notify services when significant events occur via service lifecycle events. Upon initialization, a service can register a function to receive the events and act in whatever way is required. Handler functions do not need to handle every single type of event, merely the events that the service is interested in.
The Corda node issues events to all registered handler functions. Where multiple event handlers are registered, there is no guarantee for the order in which an event is dispatched to them.
The following service lifecycle events are issued by the node.
ServiceLifecycleEvent.BEFORE_STATE_MACHINE_START: The node is starting up and the State Machine Manager is about to start. The node issues this event synchronously to each service, meaning that the State Machine Manager will not be started until all handler functions have returned from processing this event. This can be useful if a CorDapp does not want flows to be processed until it is ready, perhaps after completing some lengthy startup processing of its own, in which case the event handler can block on this event until the CorDapp is ready.
ServiceLifecycleEvent.STATE_MACHINE_STARTED: The node is starting up and the State Machine Manager has started. The node issues this event asynchronously to each service, meaning that the node startup sequence will proceed regardless of whether handler functions have finished processing this event.
Retrieving a Service
A Service class can be retrieved by calling ServiceHub.cordaService
which returns the single instance of the class passed into the function:
val service: MyCordaService = serviceHub.cordaService(MyCordaService::class.java)
MyCordaService service = serviceHub.cordaService(MyCordaService.class);
ServiceHub.cordaService
should not be called during initialisation of a flow and should instead be called in line where
needed or set after the flow’s call
function has been triggered.Starting Flows from a Service
Starting flows via a service can lead to deadlock within the node’s flow worker queue, which will prevent new flows from starting. To avoid this, the rules below should be followed:
- When called from a running flow, the service must invoke the new flow from another thread. The existing flow cannot await the execution of the new flow.
- When
ServiceHub.trackBy
is placed inside the service, flows started inside the observable must be placed onto another thread. - Flows started by other means, do not require any special treatment.
Was this page helpful?
Thanks for your feedback!
Chat with us
Chat with us on our #docs channel on slack. You can also join a lot of other slack channels there and have access to 1-on-1 communication with members of the R3 team and the online community.
Propose documentation improvements directly
Help us to improve the docs by contributing directly. It's simple - just fork this repository and raise a PR of your own - R3's Technical Writers will review it and apply the relevant suggestions.
We're sorry this page wasn't helpful. Let us know how we can make it better!
Chat with us
Chat with us on our #docs channel on slack. You can also join a lot of other slack channels there and have access to 1-on-1 communication with members of the R3 team and the online community.
Create an issue
Create a new GitHub issue in this repository - submit technical feedback, draw attention to a potential documentation bug, or share ideas for improvement and general feedback.
Propose documentation improvements directly
Help us to improve the docs by contributing directly. It's simple - just fork this repository and raise a PR of your own - R3's Technical Writers will review it and apply the relevant suggestions.