Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. SPDX-License-Identifier: 0BSD
This is an example of how a Java application would use the Java Binding library to connect to and exercise a DAML model running on a ledger. Since there are three levels of interface available, this example builds a similar application with all three levels.
The application is a simple PingPong application, which consists of:
- a DAML model with two contract templates,
PingandPong - two parties,
AliceandBob
The logic of the application is the following:
- The application injects a contract of type
PingforAlice. Alicesees this contract and exercises the consuming choiceRespondPongto create a contract of typePongforBob.Bobsees this contract and exercises the consuming choiceRespondPingto create a contract of typePingforAlice.- Points 1 and 2 are repeated until the maximum number of contracts defined in the DAML is reached.
To set a project up:
If you do not have it already, install the DAML SDK by running:
curl -sSL https://get.digitalasset.com/install/install.sh | sh -s 3.4.9
Launch an OTLP server
- docker run --rm -d
- -p 127.0.0.1:4317:4317 -p 127.0.0.1:4318:4318 -p 127.0.0.1:55679:55679 --name otlp otel/opentelemetry-collector-contrib:0.141.0
- Use the start script for starting a ledger & the java application:
./start.sh
- Check if the OTLP server has received the traces from Canton
docker logs otlp 2>&1 | grep -E '^Span|(ID|Name|Kind|time|Status w+)s+:'
Verify that the spans reported by the start.sh step appear in the log dump above.
The code for this example is in the package examples.pingpong.codegen.
The entry point for the Java code is the main class PingPongMain. Look at this class to see:
- how to connect to and interact with the DAML Ledger via the Java Binding library
- how to use the gRPC layer to build an automation for both parties.
- how to streamline interactions with the ledger types by using auto generated data layer.
The main function:
- creates an instance of a
ManagedChannelconnecting to an existing ledger - fetches the ledgerID and packageId from the ledger
- creates
Identifiersfor the Ping and Pong templates - creates and starts instances of PingPongProcessor that contain the logic of the automation
- injects the initial contracts to start the process
The core of the application is the method PingPongProcessor.runIndefinitely().
This method retrieves a gRPC streaming endpoint using the GetTransactionsRequest request, and then creates a `RxJava <The Underlying Library: RxJava_>`_ StreamObserver, providing implementations of the onNext, onError and onComplete observer methods. RxJava arranges that these methods receive stream events asynchronously.
The method onNext is the main driver, extracting the transaction list from each GetTransactionResponse, and passing in to processTransaction() for processing. This method, and the method processTransaction() implements the application logic.
processTransaction() extracts all creation events from the the transaction and passes them to processEvent(). This produces a list of commands to be sent to the ledger to further the workflow, and these are packages up in a Commands request and sent to the ledger.
processEvent() takes a transaction event and turns it into a stream of commands to be sent back to the ledger. To do this, it examines the event for the correct package and template (it's a create of a Ping or Pong template) and then looks at the receiving part to decide if this processor should respond. If so, an exercise command for the correct choice is created and returned in a Stream.
In all other cases, an empty Stream is returned, indication no action is required.
The application prints statements similar to these:
Bob is exercising RespondPong on #1:0 in workflow Ping-Alice-1 at count 0
Alice is exercising RespondPing on #344:1 in workflow Ping-Alice-7 at count 9
The first line shows that:
Bobis exercising theRespondPongchoice on the contract with ID#1:0for the workflowPing-Alice-1.- Count
0means that this is the first choice after the initialPingcontract. - The workflow ID
Ping-Alice-1conveys that this is the workflow triggered by the second initialPingcontract that was created byAlice.
The second line is analogous to the first one.
The codegen variant of the client application is similar to its grpc counterpart. Both are written in
a traditional imperative style. What sets them apart is the usage of the generated data layer in the former.
This layer simplifies construction of the ledger api calls and the analysis of the return values.
PingPongMain.createInitialContractscreates a strongly typed instance of a Ping contract and then embeds it in an equally strongly typedCommandsSubmission. Then, it uses the built intoProtomethods to convert the request into a wire-readyprotobufstructure.PingPongProcessor.runIndefinitelycreates a per party inclusive filter by invoking a series of class constructors. Contrast this with the intricate process of defining a filter in the analogous method in thegrpcvariant of the application.PingPongProcessor.processEventstarts off by extracting common data fields from thegrpcversion of the received events, to be later used for logging purposes. Events are then converted to the corresponding data layer format and passed to the individual template handlers.PingPongProcessor.processPingPongcreates a strongly typed representation of the daml contracts by means of the daml contract companions. A strongly typed instance can be used to create a command representing a desired choice exercise.PingPongProcessor.processTransactionis responsible for creating a ledger request enveloping the choice exercises and submitting it to the ledger.