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:
blockchains: order_chain: module: order_chain config: <<: *senderWith 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:
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:
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:
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:
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:
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:
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:
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.
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.
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:
@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:
chr testRunning 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
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 filedefinitions: - &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"