[Back to LOC Studio Guidebook](https://hackmd.io/R8uxDVYvQAGhJtqoTWXy6A) # Use Case: Lakeside Oasis Café :::info We recommend you finish our **[Quick Start](https://hackmd.io/_BXotO9kSQulpZLHuAOjwA?view)** first before continuing this section. ::: In this section, we will demonstrate a slightly more complicated scenario, involving with *two* data processes in which important data will be kept in the format of events. The events can also be used to trace data flow to find out problems in your business processes or to monitor your business activity. Lastly, you will understand how LOC Studio helps you model business logics and capture the data flow in Data Discovery. ## Design Process Imagine a brand-new fast food restaurant, *Lakeside Oasis Café*, has just opened near your office. Unfortunately, it has become way too popular since the opening day. New orders keep flooding in, and the staff can barely handle them. The counter staff have to verbally relay orders, and the kitchen staff might miss producing some items from time to time until it is too late. The manager of the café wants to monitor the orders between the counter and the kitchen with LOC Studio's help. ### Business Process and Logic Analysis This business process can be split into two business processes: 1. Set Order - The counter staff key in the order data and assign food/drink items to a specific order ID. 2. Deliver Order - The kitchen staff will be assigned to each of their responsible food/drink item. ![](https://hackmd.io/_uploads/HkYCMGJo5.png) ### Data Process Analysis Each data process has two generic logics and one aggregator logic: | Data Process | (Generic) Logic #1 | (Generic) Logic #2 | Aggregator Logic | | ---------------------- | ----------------------- | ------------------- | ------------------ | | ++Set Order++ (#A) | ```inputOrder``` | ```setOrderEvent``` | (return status ok) | | ++Deliver Order++ (#B) | ```queryOrderEvent``` | ```deliverOrderEvent``` | (return status ok) | In both of the data processes, one of the generic logics is used to create events: | Label Name (from which data process) | SourceDID | TargetDID | Meta | | -------------------------------------- | ------------------------- | ----------------------- | ------------- | | setOrder: ```[order ID]``` (Logic #2 from #A) | Item: ```[item name]``` | Order: ```[order ID]``` | order details | | ```deliverOrder``` (Logic #2 from #B) | Staff: ```[staff name]``` | Item: ```[item name]``` | order details | The 1^st^ event is essentially the feed to the 2^nd^ data process. ![](https://hackmd.io/_uploads/BkWNjz-o5.png) ## LOC Studio Preparation ### Create User Account If you haven't had any LOC Studio user account, please refer to [Quick Start - Create User Account](/_BXotO9kSQulpZLHuAOjwA?view#Create-User-Account). For more setup details, please see [Reference - User Management](https://hackmd.io/HeIFfxw5SquerGr8mcWtKQ?view#User-Management). ### Create Project/Scenario/Data Process As in the [Quick Start - Create a “Greeting” Data Process](https://hackmd.io/_BXotO9kSQulpZLHuAOjwA?view#Create-a-“Greeting”-Data-Process), you need to create a project and a scenario before creating new data processes. Detailed instructions can be found [HERE](https://hackmd.io/HeIFfxw5SquerGr8mcWtKQ?view#Data-Process-Explorer). ### Order Data Here we provide test data comprised of 5 orders, each of which has an unique ```OrderId``` field. Under the ```OrderItems``` fields you will find all items sold in the café and the quantity ordered by the customer (0 means not ordered). We will use this to test our data processes later. ```json= [ { "OrderId": "201", "OrderItems": { "French Fries": 1, "Hamburger": 1, "Fried Chicken": 1, "Coke": 1, "Black Tea": 1, "Salad": 1, "Ice Cream": 0 } }, { "OrderId": "202", "OrderItems": { "French Fries": 3, "Hamburger": 2, "Fried Chicken": 1, "Coke": 5, "Black Tea": 1, "Salad": 1, "Ice Cream": 0 } }, { "OrderId": "203", "OrderItems": { "French Fries": 2, "Hamburger": 2, "Fried Chicken": 3, "Coke": 1, "Black Tea": 1, "Salad": 1, "Ice Cream": 0 } }, { "OrderId": "204", "OrderItems": { "French Fries": 1, "Hamburger": 0, "Fried Chicken": 4, "Coke": 2, "Black Tea": 1, "Salad": 3, "Ice Cream": 2 } }, { "OrderId": "205", "OrderItems": { "French Fries": 3, "Hamburger": 1, "Fried Chicken": 1, "Coke": 2, "Black Tea": 1, "Salad": 1, "Ice Cream": 0 } } ] ``` ## Create Data Process #A - Set Order The first data process is to address the order data (in the format of JSON payload) and make it available for subscription via events. Since we have completed setting up the project, scenario, and data processes, what you need to do next is to add codes to the corresponding blocks in each data process. ![](https://hackmd.io/_uploads/HkWKaz1j5.png) > The data processes and the logics can be named whatever you like, but we will use the name we have listed above. ### Generic Logic #1 of Data Process #A The first logic of #A is to read and parse the JSON data from the POST request body, and then to make all the food/drink items that need to be prepared into events and send them to the event store. #### <span style="color:green">++**[if OK]**++</span> ```javascript= /** * * Data Process #A - Generic Logic #1 * * The codes in 'run' are executed when no error occurrs in Generic Logic. * */ async function run(ctx) { // a function that transforms byte array to string const UTF8ArrToStr = (aBytes) => { let utf8decoder = new TextDecoder(); return utf8decoder.decode(new Uint8Array(aBytes)); } // read and parse JSON data from the request body const payload = JSON.parse(UTF8ArrToStr(ctx.payload.http.body)); // extract all order items and re-package them into customised event objects // for the next logic to process let orderAsItems = []; payload.forEach(order => { const orderItems = order?.OrderItems; // iterate through property names of an order object (JSON field names) for (let itemName in orderItems) { // skip if the item has incorrect or zero quantity if (!Number.isInteger(orderItems[itemName]) || orderItems[itemName] <= 0) { ctx.agents.logging.error( `Incorrect quantity for item ${itemName} in order ${order.OrderId}` ); continue; } // prepare a new order item object let newOrderItem = { OrderId: order.OrderId, // order id Name: itemName, // item name Quantity: orderItems[itemName] // item quantity }; // push the order item into the array orderAsItems.push(newOrderItem); } }); // write the order item array into session storage await ctx.agents.sessionStorage.putJson("orderAsItems", orderAsItems); } ``` For every item in orders (and if its quantity is not zero), a JSON data will be sent to the session storage, for example: ```json { "OrderId": "200", "Name": "French Fries", "Quantity": 1 } ``` #### <span style="color:red">++**[if Error]**++</span> ```javascript= /** * * The codes in 'handleError' is executed when an error occurrs * in Aggregator Logic, or the CURRENT running Logic just gets an error. * */ async function handleError(ctx, error) { ctx.agents.logging.error(error.message); // log error } ``` The next logic would take over these data by querying the session storage. ### Generic Logic #2 of Data Process #A This logic will get the order items from the session storage and make them into events. #### <span style="color:green">++**[if OK]**++</span> ```javascript= /** * * Data Process #A - Generic Logic #2 * * The codes in 'run' are executed when no error occurrs in Generic Logic. * */ async function run(ctx) { // load order event objects from session storage (prepared by the previous logic) const orderAsItems = await ctx.agents.sessionStorage.get("orderAsItems"); let events = []; orderAsItems.forEach(item => { // create a new event object and add to array // the item name itself is the source, order id is the target let newEvent = { sourceDID: `Item: ${item.Name}`, // event source targetDID: `Order: ${item.OrderId}`, // event target labelName: `setOrder: ${item.OrderId}`, // event label name meta: JSON.stringify(item), // convert item object to JSON string type: 'default', // event group }; events.push(newEvent); }); // send events to event store for all order items await ctx.agents.eventStore.emit(events); } ``` For every order item, the event is supposed to like this: ``` sourceDID: "Item: French Fries" targetDID: "Order: 200" labelName: "setOrder" meta: "{ OrderId: 200, Name: French Fries, Quantity: 1 }" type: "default" ``` Essentially, the order item data is embedded in the ```meta``` field. #### <span style="color:red">++**[if Error]**++</span> ```javascript= /** * * The codes in 'handleError' are executed when an error occurrs * in Aggregator Logic or the CURRENT running Logic just gets an error. * */ async function handleError(ctx, error) { ctx.agents.logging.error(error.message); // log error } ``` ### Aggregator Logic of Data Process #A Our aggregator logic doesn't do much here, except to return an execution status. #### <span style="color:green">++**[if OK]**++</span> ```javascript= /** * * Data Process #A - Aggregator Logic * * The codes in 'run' are executed when no error occurrs in Generic Logic. * */ async function run(ctx) { // signal this data process is executed properly ctx.agents.result.finalize({ status: "ok", taskId: ctx.task.taskId, }); } ``` #### <span style="color:red">++**[if Error]**++</span> ```javascript= /** * * The codes in 'handleError' are executed when an error occurres * in Aggregator Logic or the CURRENT running Logic just gets an error. * */ async function handleError(ctx, error) { ctx.agents.logging.error(error.message); // log error } ``` ## Create Data Process #B - Deliver Order Now let's move to the second data process - the kitchen will process the order items and "assign" them to kitchen staff. ### Generic Logic #1 of Data Process #B The first logic of #B is to read order items out of event store and store them in its session storage. #### <span style="color:green">++**[if OK]**++</span> ```javascript= /** * * Data Process #B - Generic Logic #1 * * The codes in 'run' are executed when no error occurrs in Generic Logic. * */ async function run(ctx) { // event search parameters const searchReq = { queries: [], excludes: [], filters: [ { Wildcard: { field: "label_name", value: "setOrder*" } }], from: 0, size: 1000, sorts: [], }; // search events that match and extract event objects const search = await ctx.agents.eventStore.search(searchReq); const events = search?.events; // read metadata (order items) from the events let orderAsItems = []; events.forEach(orderEvent => { // convert string to JSON object const orderItem = JSON.parse(orderEvent.meta); // save the object in array orderAsItems.push(orderItem); }); // write the order items into session storage await ctx.agents.sessionStorage.putJson("orderAsItems", orderAsItems); } ``` #### <span style="color:red">++**[if Error]**++</span> ```javascript= /** * * The codes in 'handleError' are executed when an error occurrs * in Aggregator Logic or the CURRENT running Logic just gets an error. * */ async function handleError(ctx, error) { ctx.agents.logging.error(error.message); // log error } ``` ### Generic Logic #2 of Data Process #B Now the kitchen has to find the right staff to prepare order items, and then send another event (```deliverOrder```) to mark this new data flow. #### <span style="color:green">++**[if OK]**++</span> ```javascript /** * * Data Process #B - Generic Logic #2 * * The codes in 'run' are executed when no error occurrs in Generic Logic. * */ async function run(ctx) { // employees and what order items they should be responsible for const kitchen_staffs = { John: ["French Fries", "Fried Chicken", "Chicken Nugget"], Ann: ["Hamburger", "Ice Cream"], Emily: ["Coke", "Diet Coke", "Sprite", "Lemonade", "Black Tea", "Coffee"] }; // function to search each order item that is responsible by which kitchen staff to prepare const searchStaff = (itemName) => { for (let staff in kitchen_staffs) { for (let responsibility of kitchen_staffs[staff]) { if (responsibility === itemName) return staff; } } return null; } // load order items from session storage const orderAsItems = await ctx.agents.sessionStorage.get("orderAsItems"); // iterate through items let events = []; orderAsItems.forEach(item => { // get the staff who is responsible for this item const staff = searchStaff(item.Name); if (staff) { // if a staff is found, create an event let newEvent = { sourceDID: `Staff: ${staff}`, // event source targetDID: `Item: ${item.Name}`, // event target labelName: "EventDeliverOrder", // event label name meta: JSON.stringify(item), // convert item object to JSON string type: 'default', // event group }; events.push(newEvent); } else { // no staff found, output an error message ctx.agents.logging.error( `No staff found for item ${item.Name} from order ${item.OrderId}` ); } }); // send events to event store for all order items await ctx.agents.eventStore.emit(events); } ``` Here we use a hard-coded object ```kitchen_staffs``` as a look-up table (but you can replace it with rules imported from a database or API). The function ```searchStaff()``` is for looking up the staff who should be responsible for which specific item. The staff name will then become the new event's source. #### <span style="color:red">++**[if Error]**++</span> ```javascript= /** * * The codes in 'handleError' are executed when an error occurrs * in Aggregator Logic or the CURRENT running Logic just gets an error. * */ async function handleError(ctx, error) { ctx.agents.logging.error(error.message); // log error } ``` ### Aggregator Logic of Data Process #B #### <span style="color:green">++**[if OK]**++</span> ```javascript= /** * * Data Process #B - Aggregator Logic * * The codes in 'run' are executed when no error occurs in Generic Logic. * */ async function run(ctx) { // signal this data process is executed properly ctx.agents.result.finalize({ status: "ok", taskId: ctx.task.taskId, }); } ``` #### <span style="color:red">++**[if Error]**++</span> ```javascript= /** * * The codes in 'handleError' are executed when an error occurs * in Aggregator Logic or the CURRENT running Logic just gets an error. * */ async function handleError(ctx, error) { ctx.agents.logging.error(error.message); // log error } ``` ## Deploy Data Processes Right-click the data processes and select **Deploy Data Process**. You should see the message from LOC Studio that your processes are successfully deployed. ![](https://hackmd.io/_uploads/rkgUbXks9.png) :::info Please note that - Deploying a data process is equivalent to storing it in the backend of LOC Studio. - When a data process is deployed, this data process in the menu will be marked with a green solid circle. ::: ## Create API Route Now deploy an API route as well so that we can invoke the data processes through it. Here we choose ```/lakeside/order``` as the route and remember to set it for **POST** requests. Then add the two data processes in sequence (```setOrder``` first and then ```deliverOrder```) - this will be the sequence of how the API route invoke data processes, so please check twice! Click **Create API Route** when you are done. ![](https://hackmd.io/_uploads/SJLSfmJo9.png) ## Invoke Data Processes Now we can trigger our two data processes with the API route (in our example it's ```https://api.loc-xxx/fastfood/order```) with the test JSON payload. Here's a screenshot of Insomnia: ![](https://hackmd.io/_uploads/Sku8LFvIq.png) LOC returns status 200 (OK) as well as an ```executionid```, which we can use to look up the events generated during this execution. :::info Please note that if there is an error message in the response, the reasons could be - the URL is being requested for too long, so you need to re-send the request on Postman/Insomnia again. - the data process execution time is longer than your previous setup, so please change the setting. ([Reminder in Data Process](https://hackmd.io/HeIFfxw5SquerGr8mcWtKQ?view#Data-Process)) - the code snippets in your (generic or aggregator) logics might be erroneous, so please revise them and re-deployed. Here are some of the common error messages you might encounter only for your reference. ::: ## View Events in Data Discovery Now go back to LOC Studio's Data Discovery window. You should see new events appear in the list: ![](https://hackmd.io/_uploads/ByVYPmyj5.png) ### Filter by Execution ID Remember the ```executionId``` field we've got from the request response? Click **Add filter**, select **Execution ID** and enter the value: ![](https://hackmd.io/_uploads/Bk80P71s5.png) :::info Please note that only when there are events written in the logics, can they be searched here. ::: Now if you click the **Event Lineage Graph**, you can now view all the related nodes and events of this particular execution (you may have to drag the nodes around yourself): ![](https://hackmd.io/_uploads/B1-LdXJi5.png) :::info Notice that there is no kitchen staff connecting to **Item: Salad**, meaning that the kitchen staff listed in logic #2 of data process #B needs to be updated. Fortunately, you can now monitor this business activity and discover missing data flows sooner with LOC Studio's assistance. ::: ### Filter by Source DID/Target DID You can also filter nodes/events with specific Source DID or Target DID. For example, below is the result if we add **Staff: John** as the Source DID we want to search: ![](https://hackmd.io/_uploads/r14sd7yi5.png) Now we see the **Staff: John** related events (**deliverOrder**) and what he is responsible for. Clicking on any of the event to see what's in the event fields: ![](https://hackmd.io/_uploads/B18EYQysc.png) Note that the meta field does have a JSON string, which is the payload sent along with the event. You might also want to play around with this use case yourself or even to add more scenes to this scenario, such as how the counter staff to calculate the total amount of each order or how to replace the kitchen staff when one is on leave. It is a fun use case to make you better understand what LOC Studio brings to you; as for more use cases with different domain know-how, they are coming soon. Stay tuned! --- <text style="font-size:17pt">**LOC Studio Guidebook Catalogue**</text> <text style="font-size:13pt"> [Introduction](https://hackmd.io/PtsDJXYvReqsoGo_jR_4KA?view)</text> <text style="font-size:13pt"> [Concept](https://hackmd.io/mwTxDdBjSuiKrAmainQZAA?view)</text> <text style="font-size:13pt"> [Quick Start](https://hackmd.io/_BXotO9kSQulpZLHuAOjwA?view)</text> <text style="font-size:13pt"> [Use Case: Lakeside Oasis Café](https://hackmd.io/JvP9MTvgSmGEubrizwSWVQ?view)</text> <text style="font-size:13pt"> [Reference](https://hackmd.io/HeIFfxw5SquerGr8mcWtKQ?view)</text> ###### tags: `LOC Studio`