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:
module;In src/delivery_chain/entities.rell, define an enum for the three states an order can be in: created, dispatched, and delivered.
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:
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:
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:
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:
import messages. { topic.*, msg };import lib.icmf.receiver.{ receive_icmf_message };Then, in functions.rell, define a receive_icmf_message extension:
@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.
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:
@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.
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
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.ymlFinal version of the chromia.yml
chromia.ymldefinitions: - &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"