In this use case centred around healthcare, we exemplify how developers can compose locally the libraries generated by independent choreographies.
Suppose that a “healthcare service” in a hospital needs to gather sensitive data about vital signs (we call them vitals) from some IoT devices (e.g., smartwatches, heart monitors), and then upload them to the cloud for storage. This is a typical scenario that requires the integration of libraries for participating in choreographies at the local level. We shall carry out the following two steps.
Define a new choreography class, called VitalsStreaming
, that prescribes how data should be streamed from an IoT Device
monitoring the vitals of a patient to a data Gatherer
; this choreography will enforce that the Gatherer
processes only data that is (a) correctly cryptographically signed by the device and (b) pseudonymised.
Implement the healthcare service, a local Java class called HealthCareService
, that combines the Java library compiled from VitalsStreaming
to gather data from the IoT devices with the Java library compiled from our previous DistAuth
example to authenticate with the cloud storage service through a third-party service (this could be, e.g., a national authentication system) and upload the data.
public enum StreamState@R { ON, OFF }
public VitalsStreamingHelper@A {
private static Vitals@A pseudonymise( Vitals@A vitals ) { /*...*/ }
private static Boolean@A checkSignature( Signature@A signature ) { /*...*/ }
}
public class VitalsStreaming@( Device, Gatherer ) {
private SymChannel@( Device, Gatherer )< Object > ch;
private Sensor@Device sensor;
public VitalsStreaming(
SymChannel@( Device, Gatherer )< Object > ch,
Sensor@Device sensor
) {
this.ch = ch;
this.sensor = sensor;
}
public void gather( Consumer@Gatherer< Vitals > consumer ) {
if ( sensor.isOn() ) {
ch.< StreamState >select( StreamState@Device.ON );
VitalsMsg@Gatherer msg = sensor.next()
>> ch::< Vitals >com;
if ( checkSignature( msg.signature() ) ){
msg.content()
>> VitalsStreamingHelper::pseudonymise
>> consumer::accept;
}
gather(consumer);
} else {
ch.< StreamState >select( StreamState@Device.OFF );
}
}
}
Try it yourself: see the source code on .
In class VitalsStreaming
composes a channel between the Device
and the Gatherer
and a Sensor
object located at the Device
(for obtaining the local vital readings).
Method pseudonymises
pseudonymises personal data in Vitals
at the Gatherer
. Likewise, the method checkSignature
is used by the Gatherer
uses to check that a message signature is valid. (We omit the bodies of these two static methods, which are standard local methods.)
The interesting part of this class is the method gather
. The Device
checks whether its sensor is on and informs the Gatherer
of the result with appropriate selections for knowledge of choice. If the sensor is on, then Device
sends its next available reading to Gatherer
. Gatherer
now checks that the message is signed correctly; if so, it pseudonymises
the content of the message and then hands it off to a local consumer function. Notice that Gatherer
does not need to inform Device
of its local choice, since it does not affect the code that Device
needs to run. We then recursively invoke gather to process the next reading.
To conclude the use case, we define the local implementation of the healthcare service. The healthcare service acts as the Gatherer
in the VitalsStreaming
choreography (to gather the data) and as the Client
in the DistAuth choreography (to authenticate with the cloud storage). So we compose the compiled Java classes VitalsStreaming_Gatherer
and DistAuth_Client
, respectively. The code of this local implementation is given below.
import DistAuth_Client; // From the DistAuth choreography
import VitalsStreaming_Gatherer; // From the VitalsStreaming choreography public class
public class HealthCareService {
public static void main() {
TLSChannel_A toIP = HealthIdentityProvider.connect();
TLSChannel_A toStorage = HealthDataStorage.connect();
AuthResult_A authResult = new DistAuth_Client( toIP )
.authenticate( getCredentials() );
authResult.left().ifPresent( token -> {
DeviceRegistry
.parallelStream()
.map( Device::connect )
.map( VitalsStreaming_Gatherer::new )
.forEach( vs -> vs.gather( data ->
toStorage.com( new StorageMesg( token, data ) ) )
);
}
);
}
private static Credentials getCredentials() { /* ... */ }
}
The interesting part is the main
method of the HealthCareService
, which interweaves local computation using the standard Java library and the libraries generated by our compiler. In the method, we use auxiliary methods to connect to the identity provider for authentication (which implements IP
in DistAuth
) and the data storage service (which implements Service
in DistAuth
)—we assume that these services are provided by third parties, e.g., the national health system and some cloud provider.
After the connection, we run our distributed authentication protocol as the Client
, checking check if we successfully received an authentication token by inspecting the optional result. If so we: obtain a parallel stream of Device objects from a local registry of devices; connect to each device in parallel, which maps devices to channels; and use each channel to create a respective instance of VitalsStreaming_Gatherer
(the code compiled by Choral to implement Gatherer
in VitalsStreaming
). Finally, we launch the gather method to engage in the VitalsStreaming
choreography with each device, passing as a consumer function a lambda expression that sends the received data to the cloud storage service (including the authentication token).
Notice that we do not need to worry about pseudonymisation nor signature checking in the local code since all these details are dealt with by the code compiled from VitalsStreaming
.