Chromia
Start Building

04 / 07Advanced

Order chain (send message)

The order chain holds the purchase logic, much like a typical e-commerce checkout. Once an order completes, the chain automatically sends a message about it to the factory and delivery chains.

Configuration

First, configure the order chain to use the &sender part you defined earlier. In chromia.yml, add the following under the blockchains property:

chromia.yml
blockchains:  order_chain:    module: order_chain    config:      <<: *sender

With this in place, the dapp has the infrastructure it needs to emit messages.

Database Model

Create a directory named src/order_chain, and inside it add a module.rell file. This file is the module definition and needs exactly this content:

src/order_chain/module.rell
module;

The chain must record which orders were processed and what each one contained. Add a file named entities.rell to src/order_chain with this code:

src/order_chain/entities.rell
entity order {    key tx: byte_array = op_context.transaction.tx_rid;    index customer_id: integer;    address: text;}entity product {    id: integer;}entity product_order {    key order, product;    quantity: integer;}

The product_order entity represents the many-to-many relationship between orders and products, storing how many units of a given product were ordered. To let a client read this data, add some queries to queries.rell:

src/order_chain/queries.rell
query list_orders() {    val ordered_products = product_order @* {} (.order.rowid.to_integer(), (product_id = .product.id, quantity = .quantity));    return group_products_by_id(ordered_products);}query get_order_id(tx: byte_array) = order @? { tx }.rowid;query get_order_details(id: integer)    = product_order @* { .order.rowid == rowid(id) } ( order_id = .order.rowid, product_id = .product.id, quantity = .quantity );query get_order_details_by_tx(tx: byte_array) = get_order_details(require(get_order_id(tx)?.to_integer()));

Next, add a file named functions.rell to src/order_chain to hold these helper functions:

src/order_chain/functions.rell
function group_products_by_id(value: list<(integer, (product_id:integer, quantity:integer))>) {    val result = map<integer, list<(product_id:integer, quantity:integer)>>();    for ((k, v) in value) {        if (k not in result) result[k] = list<(product_id:integer, quantity:integer)>();        result[k].add(v);    }    return result @* {} (order_id = $[0], details = $[1]);}

You also need an easy way to add products to the catalog. Add a file named operations.rell to src/order_chain with the following code:

src/order_chain/operations.rell
operation register_product(id: integer) {    create product ( id );}

Sending a message

You can now create a customer order and tell the other chains about it. Start by importing the message types and messaging utilities into src/order_chain/module.rell:

src/order_chain/module.rell
module;import messages.{ msg, topic };import lib.icmf.{ send_message };

Then define the operation a customer calls to place an order. Add this code to src/order_chain/operations.rell:

src/order_chain/operations.rell
operation make_customer_order(details: msg.order_details) {    val order = create order (        customer_id = details.customer_id,        address = details.address    );    val product_to_ids = product @* { .id in details.products @* { }.id }( $, .id );    val ordered_products = list<struct<product_order>>();    for ((p, id) in product_to_ids) {        ordered_products.add(        struct<product_order>(            order = order,            product = p,            quantity = details.products @ { .id == id }.quantity        )        );    }    create product_order ( ordered_products );    val order_id = order.rowid.to_integer();    send_production_order(order_id, details.products);    send_new_delivery(order_id, details.customer_id, details.address);}

Add the following functions to src/order_chain/functions.rell as well. They send the order to production and start the delivery.

src/order_chain/functions.rell
function send_production_order(order_id: integer, products: list<msg.product>) {    send_message(        topic.PRODUCTION_ORDER,        msg.production_details(order_id, products).to_gtv()    );}function send_new_delivery(order_id: integer, customer_id: integer, shipping_address: text) {    send_message(        topic.NEW_DELIVERY,        msg.delivery_details(order_id, customer_id, shipping_address).to_gtv()    );}

The operation receives a struct with the order details. It creates the order and product_order entities, then sends the production order and the new delivery message. The calls to send_message(topic: text, body: gtv) are wrapped in helper functions for readability.

Testing

To set up a test module for the order chain, add the following to the blockchains:order_chain section of chromia.yml.

chromia.yml
blockchains:  order_chain:    # ↓↓↓ Add this code snippet ↓↓↓    test:      modules:        - test.order_chain_test    # ↑↑↑ Add this code snippet ↑↑↑

Create a file named order_chain_test.rell in src/test and add this code:

src/test/order_chain_test.rell
@test module;import order_chain.{ register_product, make_customer_order, order };import messages.{ msg, topic };function test_make_order() {    val test_order = msg.order_details(0, "MyStreet 12", [msg.product(id = 12, quantity = 110)]);    rell.test.tx().op(register_product(12)).run();    rell.test.tx().op(make_customer_order(test_order)).run();    val order_id = order @ { } ( .rowid.to_integer() );    assert_events(        (        "icmf_message",        (topic = topic.PRODUCTION_ORDER,        body = msg.production_details(order_id, test_order.products).to_gtv())            .to_gtv_pretty()),        ("icmf_message",        (topic = topic.NEW_DELIVERY,        body = msg.delivery_details(order_id, customer_id = test_order.customer_id, test_order.address).to_gtv())            .to_gtv_pretty())    );}

The test registers a product, places a customer order, and checks that the expected events were created. Run the tests, and you should see this output:

bash
chr test
Result
Running tests for chain: order_chainTEST: order_chain_test:test_make_orderOK: order_chain_test:test_make_order (0,628s)------------------------------------------------------------TEST RESULTS:OK order_chain_test:test_make_orderSUMMARY: 0 FAILED / 1 PASSED / 1 TOTAL (0,628s)

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

Project structure

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

Final version of the chromia.yml file

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_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.