Skip to main content

Order chain (send message)

The order chain will contain the logic for buying an item, similar to e-commerce. The chain will then automatically send a message to the factory and delivery chains that a new order has been completed.

Configuration

We start by configuring the order chain to use the &sender part that we defined earlier:

blockchains:
order-chain:
module: order_chain
config:
<<: *sender

This should be enough to enable the infrastructure needed for the dapp to emit messages.

Database Model

We want to keep track of which orders have been done and their contents. We can model this by updating the src/order_chain.rell content.

module;

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;
}

operation register_product(id: integer) {
create product ( id );
}

In the code, we model a many-to-many relationship between an order and a product by the product_order entity that contains the quantity ordered from that product. Let's also supply some queries to be able to read the data from a client:

src/order_chain.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);
}

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]);
}

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()));

and a simple way to add a product to our catalog:

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

Sending a message

Now, we are ready to create a customer order and notify other chains this event has happened. We do this by importing the message types and messaging utilities:

src/order_chain.rell
import messages.*;
import lib.icmf.{ send_message };

and defining an operation the customer can call to make the order:

src/order_chain.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);
}

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 takes a struct containing order details as input. It then creates the order and product_order entities before sending the production order and new delivery message. We wrap the calls to send_message(topic: text, body: gtv) to make the code more readable.

Testing

Let's configure a new test module for the order chain by adding the following to blockchains:order-chain part of our chromia.yml.

chromia.yml
blockchains:
order-chain:
...
test:
modules:
- order_chain_test

and create a new file src/order_chain_test.rell.

src/order_chain_test.rell
@test module;

import order_chain.*;
import messages.*;

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())
);
}

In the test, we register a product, create a new customer order, and assert that the events have been created properly. It should now be possible to run the tests and get the following output:

$ chr test
Running tests for chain: order-chain
TEST: order_chain_test:test_make_order
OK: order_chain_test:test_make_order (0,628s)

------------------------------------------------------------
TEST RESULTS:

OK order_chain_test:test_make_order

SUMMARY: 0 FAILED / 1 PASSED / 1 TOTAL (0,628s)