One of the main differences between the Corda 5 Developer Preview 1 (C5DP1) and Corda 4.x is C5DP1 has a layered approach with interface-based APIs, rather than the Corda 4 core module, where implementation and the API formed a single block. In this blog, we will discuss the way we write and use CordaService in Corda 5. CordaService provides a way to use interface-based APIs.
In C5DP1, Functional Corda Services replaces ServiceHub. Therefore, if you need to use a particular Functional Corda Services API, you just inject that Service, rather than including a whole core module. For example, the FlowEngine Service contains basic flow operations that used to be in FlowLogic in Corda 4.x.
Here are CordaService rules:
- All interactions with Corda API are via Services.
- A new @CordaInject annotation is used to inject Corda Services at runtime.
- A variable annotated with @CordaInject must be mutable, which is not final in Java and lateinit in Kotlin.
- Services must be injected before the call(), which means they can not be used in the constructor.
- List of all Corda Services.
- CordaServices are singletons.
Custom CordaService in C5
Custom CordaService provides functional reusability, where the same functionality can be used in different flows by encapsulating its implementation into separate Service and calling it on demand.
Custom CordaService has the exact injection mechanism as CordaServices. Here’s a comparison of Custom CordaService in Corda 4.x and in Corda 5:
In Corda 4.x Custom Corda Service has to implement an abstract class SingletonSerizlizeAsToken and has a single constructor argument AppServiceHub:
@CordaService
class CustomService (val services: AppServiceHub): SingletonSerializeAsToken(){
fun fun1(){ //code }
}
In Corda 5 it’s quite different: implementation and takes care of singleton.
interface CustomService : CordaService, CordaFlowInjectable{
fun doStuff1()
}
The following code snippet is the definition and implementation of CordaServices CustomServiceImpl, it uses @CordaInject — to show that Corda Services can be used by other Services in its implementation:
class CustomServiceImpl: CustomService {
@CordaInject
lateinit car vaultStateEventService : VaultStateEventService
override fun doStuff1(){//code}
}
VaultStateEventService is injected after the construction of Custom Service. For the Custom Service to work, you need to:
- Implement the CordaService interface.
- Create a unique interface name for each Service.
If you need your Custom CordaService to be injectable into flow, it must implement CordaFlowInjectable, or implement CordaServiceInjectable to be injectable into other services. If you implement both abstract classes, your Custom CordaService becomes injectable into both flows and services. However, if you only need your custom service to listen for events, you don’t need to implement it.
Using Custom Services
In Corda 4.x usage of Custom Services is:
class Flow1 {
@Suspendable
override fun call(){
val service1=serviceHub.cordaService(CustomService::class.java)
service1.fun1()
}
}
In Corda 5, it’s a bit different. You need to define the immutable field of custom service and annotate with the @CordaInject service function call has been injected and is available for use in the call() method:
class Flow1 {
@CordaInject
lateinit var service1: CustomService
@Suspendable
override fun call(){
service1.fun1()
}
}
Service lifecycle
Services come with lifecycle dependencies.
Services are not available in the constructor; CordaService can be injected after the instance has been created.
CordaService implements ServiceLifecycleObserver, which defines the onEvent() method. If you would like your Service to react to an event, you need to implement logic inside this method. There are two types of events — ServiceStart and StateMachineStarted. When the ServiceStart event is fired, the initialization of the Service can begin. StateMachineStarted is fired when Corda Services are initialized and ready. Therefore, injected Services might only be fully functional after the ServiceStart event has been fired.
Service dependencies create an ordering, which may lead to a circular dependency. This problem occurs when the start of two or more services depend on each other. To eliminate circular dependency, Services injected with @CordaInject are not available until the StateMachineStarted event has been fired. If some services need to be injected before the ServiceStart event is fired, these services need to be annotated with @CordaInjectPreStart. Cyclic CordaInjectPreStart dependency is an error.