Chromia
Start Building

05 / 07Advanced

Delivery chain (receive message)

The delivery chain has two responsibilities. It tracks which orders need delivering, and it copes with messages that arrive out of order, for example when a shipment is marked ready before it has been created. In practice this would not occur, but the example handles the case anyway.

In the src/delivery_chain directory, add a module file named module.rell containing this definition:

src/delivery_chain/module.rell
module;

In src/delivery_chain/entities.rell, define an enum for the three states an order can be in: created, dispatched, and delivered.

src/delivery_chain/entities.rell
enum shipping_state {    CREATED,    DISPATCHED,    DELIVERED}entity delivery {    key order_id: integer;    index customer_id: integer;    shipping_address: text;    mutable shipping_state;}// orders that can be dispatched but have not yet been createdentity pending_delivery {    key order_id: integer;}

To mark a shipment as delivered, add a new operation to src/delivery_chain/operations.rell:

src/delivery_chain/operations.rell
operation accept_delivery(order_id: integer) {    require(delivery @ { order_id }.shipping_state == shipping_state.DISPATCHED, "Order must be dispatched before it can be completed");    update delivery @ { order_id } ( shipping_state = shipping_state.DELIVERED);}

To read the current delivery status, add a matching query to src/delivery_chain/queries.rell:

src/delivery_chain/queries.rell
query get_delivery_details(order_id: integer) = delivery @* { order_id } ($.to_struct());query list_deliveries() = delivery @* {} ($.to_struct());

Receiving a message

Before the new blockchain can receive messages, you need to configure it and reference the receiver defined in the Introduction section. In chromia.yml, add the following delivery_chain entry under the blockchains property:

chromia.yml
blockchains:  # ↓↓↓ Add this code snippet ↓↓↓  delivery_chain:    module: delivery_chain    config:      <<: *receiver      icmf:        receiver:          local:            - topic: "L_delivery"              brid: null            - topic: "L_shipment_ready"              brid: null  # ↑↑↑ Add this code snippet ↑↑↑

This configuration subscribes the dapp to the L_delivery and L_shipment_ready topics defined in the dapp code. The brid field identifies the chain that produces the topic. Because the dapp is not yet deployed to a network, set it to null for now.

Two message types need handling, so add these imports to src/delivery_chain/module.rell:

src/delivery_chain/module.rell
import messages. { topic.*, msg };import lib.icmf.receiver.{ receive_icmf_message };

Then, in functions.rell, define a receive_icmf_message extension:

src/delivery_chain/functions.rell
@extend(receive_icmf_message)function (sender: byte_array, topic: text, body: gtv) {    when (topic) {        NEW_DELIVERY -> handle_new_delivery(msg.delivery_details.from_gtv(body));        SHIPMENT_READY -> handle_shipment_ready(msg.shipment_ready.from_gtv(body));        else -> log("Message type %s not handled".format(topic));    }}function handle_new_delivery(msg: msg.delivery_details) {    val state = pending_delivery @? { msg.order_id } (shipping_state.DISPATCHED) ?: shipping_state.CREATED;    create delivery(        order_id = msg.order_id,        customer_id = msg.customer_id,        shipping_address = msg.shipping_address,        shipping_state = state    );    delete pending_delivery @? { msg.order_id };}function handle_shipment_ready(msg: msg.shipment_ready) {    if (not exists(delivery @? { .order_id == msg.order_id })) {        create pending_delivery ( msg.order_id );    } else {        update delivery @ { .order_id == msg.order_id } ( shipping_state = shipping_state.DISPATCHED );    }}

The extension inspects the message topic and branches on it. For a topic it does not handle, it fails the operation, which stops block building because the configuration is wrong. Each of the two supported message types either creates or updates entities so the delivery is recorded with the correct state.

Testing

Testing the delivery chain requires extra configuration in chromia.yml. Under the delivery_chain section, add a test property with the required details.

chromia.yml
blockchains:  delivery_chain:    # ↓↓↓ Add this code snippet ↓↓↓    test:      modules:        - test.delivery_chain_test    # ↑↑↑ Add this code snippet ↑↑↑

Next, write a test that emits events through the ICMF test utilities and checks that the expected shipment orders and states are created. Create delivery_chain_test.rell in src/test with the following code:

src/test/delivery_chain_test.rell
@test module;import delivery_chain.{ delivery, accept_delivery, shipping_state };import messages.{ msg, topic };import lib.icmf.test.{ test_icmf_message };function test_make_delivery() {    rell.test.tx().op(            test_icmf_message(                x"",                topic.NEW_DELIVERY,                msg.delivery_details(                    order_id = 1,                    customer_id = 10,                    shipping_address = "MyStreet 101"                )                    .to_gtv()            )        )        .run();    assert_equals(delivery @ { .order_id == 1 }.shipping_state, shipping_state.CREATED);    rell.test.tx().op(accept_delivery(1)).run_must_fail("must be dispatched before");    rell.test.tx().op(            test_icmf_message(                x"",                topic.SHIPMENT_READY,                msg.shipment_ready(order_id = 1)                    .to_gtv()            )        )        .run();    assert_equals(delivery @ { .order_id == 1 }.shipping_state, shipping_state.DISPATCHED);    rell.test.tx().op(accept_delivery(1)).run();}

Run chr test and confirm the results.

bash
Running tests for chain: delivery_chainTEST: order_chain_test:test_make_deliveryOK: order_chain_test:test_make_order (0,837s)------------------------------------------------------------TEST RESULTS:OK delivery_chain_test:delivery_make_orderSUMMARY: 0 FAILED / 1 PASSED / 1 TOTAL (0,837s)

For reference, here are the project structure and the full chromia.yml after these changes. Use them to check your work at this point in the course:

Project structure

text
order-system-example/├── build/├── src/│   ├── delivery_chain/│   │   ├── entities.rell│   │   ├── functions.rell│   │   ├── module.rell│   │   ├── operations.rell│   │   └── queries.rell│   ├── lib/│   ├── order_chain/│   │   ├── entities.rell│   │   ├── functions.rell│   │   ├── module.rell│   │   ├── operations.rell│   │   └── queries.rell│   ├── test/│   │   ├── delivery_chain_test.rell│   │   └── order_chain_test.rell│   └── messages.rell ├── .gitignore└── chromia.yml

Final version of the chromia.yml

chromia.yml
definitions:  - &sender # Configuration for a chain that sends messages    gtx:      modules:        - "net.postchain.d1.icmf.IcmfSenderGTXModule"  - &receiver # Base configuration for a chain that receives messages    gtx:      modules:        - "net.postchain.d1.icmf.IcmfReceiverGTXModule"    sync_ext:      - "net.postchain.d1.icmf.IcmfReceiverSynchronizationInfrastructureExtension"  - &sender_receiver # Base configuration for a chain that will both send and receive messages    gtx:      modules:        - "net.postchain.d1.icmf.IcmfSenderGTXModule"        - "net.postchain.d1.icmf.IcmfReceiverGTXModule"    sync_ext:      - "net.postchain.d1.icmf.IcmfReceiverSynchronizationInfrastructureExtension"blockchains:  order_chain:    module: order_chain    config:      <<: *sender    test:      modules:        - test.order_chain_test  delivery_chain:    module: delivery_chain    config:      <<: *receiver      icmf:        receiver:          local:            - topic: "L_delivery"              brid: null            - topic: "L_shipment_ready"              brid: null    test:      modules:        - test.delivery_chain_testcompile:  rellVersion: 0.14.9database:  schema: schema_order_system_examplelibs:  icmf:    registry: https://gitlab.com/chromaway/core/directory-chain    path: src/lib/icmf    tagOrBranch: 1.90.1    rid: x"B56524EE85B679371EAB1A069B03BE6A3E94F36993DEA900747BA5BAE8B99122"

Written for this site from Chromia’s public course. Code samples are the course’s own.