corda/docs/source/api-service-classes.rst
Viktor Kolomeyko 0978500a9a CORDA-2942: Node lifecycle events (#5846)
* CORDA-2942: Port minimal set of changes to make lifecycle events work

... and make codebase compile.

* CORDA-2942: Undo some changes which are not strictly speaking necessary

* CORDA-2942: Make `NodeServicesContext` leaner and delete `extensions-api` module

* CORDA-2942: Reduce even more number of files affected

* CORDA-2942: Integration test fix

* CORDA-2942: Make events `AfterStart` and `BeforeStop` generic w.r.t. `NodeServicesContext`

* CORDA-2942: `NodeLifecycleObserverService` and a set of integration tests.

Public API violations are expected as well as integration tests failing.

* CORDA-2942: Re-work to introduce `ServiceLifecycleObserver`

* CORDA-2942: Explicitly mention a type of exception that may be thrown for some events.

* CORDA-2942: Register `ServiceLifecycleObserver` through `AppServiceHub`

* CORDA-2942: Fix integration test + KDocs update

* CORDA-2942: Detekt and `api-current` update

* CORDA-2942: Improvement to `CordaServiceLifecycleFatalTests`

... or else it has side effects on other tests.

* CORDA-2942: Add an integration test for new API use in Java

Driver test is written in Kotlin, but services definition is written in Java.

Also KDocs improvements.

* CORDA-2942: Documentation and release notes update

* CORDA-2942: First set of changes following review by @mnesbit

* CORDA-2942: Second set of changes following review by @mnesbit

* CORDA-2942: Added multi-threaded test

* CORDA-2942: Fixes

* CORDA-2942: Undo changes to `api-current.txt`

* CORDA-2942: Bare mimimum change to `api-current.txt` for CI gate to pass.

* CORDA-2942: Address review feedback from @rick-r3

* CORDA-2942: Detekt update

* CORDA-2942: Delete `ServiceLifecycleObserverPriority` and replace it with `Int` after discussion with @mnesbit

* CORDA-2942: Introduce more `NodeLifecycleEvent` and switch services to listen for those events

* CORDA-2942: Few more changes after input from @rick-r3

* First stub on integration test
Unfinished - hang on issue and pay

* CORDA-2942: Switch to use out-of-process nodes for the inetgration test

Currently Alice and Notary stuck waiting to hear from each other.

* CORDA-2942: Extra log lines during event distribution

* CORDA-2942: Asynchronously distribute lifecycle events

* CORDA-2942: Await for complete P2P client start-up

Next step: Add vault query to integration test

* CORDA-2942: Asynchronously distribute lifecycle events

Next step: Improve integration test

* CORDA-2942: Fix test broken by recent changes and improve logging

* CORDA-2942: Improvement of the test to be able to monitor actions performed by @CordaService in the remote process

* CORDA-2942: Add node re-start step to the integration test

* CORDA-2942: Remove `CORDAPP_STOPPED` event for now

* CORDA-2942: s/CORDAPP_STARTED/STATE_MACHINE_STARTED/

* CORDA-2942: Inverse the meaning of `priority` as requested by @rick-r3

* CORDA-2942: Register `AppServiceHubImpl` for lifecycle events and put a warning when SMM is not ready.
2020-01-21 13:38:02 +00:00

130 lines
5.2 KiB
ReStructuredText

.. highlight:: kotlin
.. raw:: html
<script type="text/javascript" src="_static/jquery.js"></script>
<script type="text/javascript" src="_static/codesets.js"></script>
API: Service Classes
====================
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.
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:
.. container:: codeset
.. sourcecode:: kotlin
@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
}
.. sourcecode:: java
@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 :ref:`Starting Flows from a Service <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``.
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:
.. container:: codeset
.. sourcecode:: kotlin
val service: MyCordaService = serviceHub.cordaService(MyCordaService::class.java)
.. sourcecode:: java
MyCordaService service = serviceHub.cordaService(MyCordaService.class);
.. warning:: ``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 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 bellow 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.
.. note:: It is possible to avoid deadlock without following these rules depending on the number of flows running within the node. But, if the
number of flows violating these rules reaches the flow worker queue size, then the node will deadlock. It is best practice to
abide by these rules to remove this possibility.