From 48d313902189323043cdd60784f0ff0c7cfeef95 Mon Sep 17 00:00:00 2001 From: Giselle van Dongen Date: Wed, 7 Feb 2024 12:51:18 +0100 Subject: [PATCH] Remove Kafka between food ordering UI and order workflow (#98) * Remove Kafka event handler as source of order workflow and clean up for conference presentation * Fix readme --- typescript/food-ordering/README.md | 6 +- .../food-ordering/app/Dockerfile-delivery | 21 +++ .../app/{Dockerfile => Dockerfile-order} | 2 +- typescript/food-ordering/app/package.json | 7 +- .../src/{restate-app => delivery-app}/app.ts | 6 +- .../clients/kafka_publisher.ts | 1 - .../app/src/delivery-app/delivery_manager.ts | 91 ++++++++++++ .../delivery-app/driver_delivery_matcher.ts | 63 ++++++++ .../src/delivery-app/driver_digital_twin.ts | 88 +++++++++++ .../external/driver_mobile_app_sim.ts | 103 +++++++++++++ .../external/driver_mobile_app_sim_utils.ts | 0 .../types/types.ts | 2 + .../utils/geo.ts | 0 .../utils/utils.ts | 7 - .../food-ordering/app/src/order-app/app.ts | 22 +++ .../clients/payment_client.ts | 0 .../clients/restaurant_client.ts | 0 .../app/src/order-app/order_status_service.ts | 46 ++++++ .../app/src/order-app/order_workflow.ts | 68 +++++++++ .../app/src/order-app/types/types.ts | 78 ++++++++++ .../app/src/restate-app/delivery_manager.ts | 126 ---------------- .../restate-app/driver_delivery_matcher.ts | 80 ---------- .../src/restate-app/driver_digital_twin.ts | 140 ------------------ .../external/driver_mobile_app_sim.ts | 119 --------------- .../src/restate-app/order_status_service.ts | 64 -------- .../app/src/restate-app/order_workflow.ts | 84 ----------- typescript/food-ordering/demo_overview.png | Bin 68094 -> 52488 bytes typescript/food-ordering/docker-compose.yml | 41 +++-- .../webui/src/components/Cart/Cart.tsx | 16 +- 29 files changed, 612 insertions(+), 669 deletions(-) create mode 100644 typescript/food-ordering/app/Dockerfile-delivery rename typescript/food-ordering/app/{Dockerfile => Dockerfile-order} (85%) rename typescript/food-ordering/app/src/{restate-app => delivery-app}/app.ts (79%) rename typescript/food-ordering/app/src/{restate-app => delivery-app}/clients/kafka_publisher.ts (99%) create mode 100644 typescript/food-ordering/app/src/delivery-app/delivery_manager.ts create mode 100644 typescript/food-ordering/app/src/delivery-app/driver_delivery_matcher.ts create mode 100644 typescript/food-ordering/app/src/delivery-app/driver_digital_twin.ts create mode 100644 typescript/food-ordering/app/src/delivery-app/external/driver_mobile_app_sim.ts rename typescript/food-ordering/app/src/{restate-app => delivery-app}/external/driver_mobile_app_sim_utils.ts (100%) rename typescript/food-ordering/app/src/{restate-app => delivery-app}/types/types.ts (94%) rename typescript/food-ordering/app/src/{restate-app => delivery-app}/utils/geo.ts (100%) rename typescript/food-ordering/app/src/{restate-app => delivery-app}/utils/utils.ts (72%) create mode 100644 typescript/food-ordering/app/src/order-app/app.ts rename typescript/food-ordering/app/src/{restate-app => order-app}/clients/payment_client.ts (100%) rename typescript/food-ordering/app/src/{restate-app => order-app}/clients/restaurant_client.ts (100%) create mode 100644 typescript/food-ordering/app/src/order-app/order_status_service.ts create mode 100644 typescript/food-ordering/app/src/order-app/order_workflow.ts create mode 100644 typescript/food-ordering/app/src/order-app/types/types.ts delete mode 100644 typescript/food-ordering/app/src/restate-app/delivery_manager.ts delete mode 100644 typescript/food-ordering/app/src/restate-app/driver_delivery_matcher.ts delete mode 100644 typescript/food-ordering/app/src/restate-app/driver_digital_twin.ts delete mode 100644 typescript/food-ordering/app/src/restate-app/external/driver_mobile_app_sim.ts delete mode 100644 typescript/food-ordering/app/src/restate-app/order_status_service.ts delete mode 100644 typescript/food-ordering/app/src/restate-app/order_workflow.ts diff --git a/typescript/food-ordering/README.md b/typescript/food-ordering/README.md index 62f83bee..ceaccd9f 100644 --- a/typescript/food-ordering/README.md +++ b/typescript/food-ordering/README.md @@ -76,7 +76,7 @@ watch -n 1 'psql -h localhost -p 9071 -c "select service, method, service_key_ut ## Exploring the demo ### The order workflow -You can find the implementation of each of the services under `app/src/restate-app/`. +You can find the implementation of each of the services under `app/src/order-app/`. The flow of an incoming order is as follows: 1. When the customer places an order via the web UI (localhost:3000), an order event is published to Kafka. 2. Restate subscribes to the order topic and triggers the order workflow for each incoming event. This subscription is set up by executing two curl commands, as done in the Docker compose file (`docker-compose.yaml`) by the `runtimesetup` container. @@ -86,10 +86,10 @@ The flow of an incoming order is as follows: 3. The order workflow then triggers the payment by calling a third-party payment provider (implemented as a stub in this example). To do this, the order workflow first generates an idempotency token via a side effect, and then uses this to call the payment provider. The payment provider can deduplicate retries via the idempotency key. 4. The workflow then sets the order status to `SCHEDULED` and sets a timer to continue processing after the delivery delay has passed. For example, if a customer ordered food for later in the day, the order will be scheduled for preparation at the requested time. If any failures occur during the sleep, Restate makes sure that the workflow will still wake up on time. 5. Once the timer fires, the order workflow creates an awakeable and sends a request to the restaurant point-of-sales system to start the preparation. This is done via an HTTP request from within a side effect. The status of the order is set to `IN_PREPARATION`. The restaurant will use the awakeable callback to signal when the prepration is done. Once this happens, the order workflow will continue and set the order status to `SCHEDULING_DELIVERY`. - 6. Finally, the order workflow calls the delivery manager (`delivery_manager.ts`) to schedule the delivery of the order (see description below). It does this by using an awakeable, that the delivery manager will use to signal when the delivery is done. Once the delivery is done, the order workflow will set the order status to `DELIVERED`. + 6. Finally, the order workflow calls the delivery manager of the delivery app (`app/src/delivery-app/delivery_manager.ts`) to schedule the delivery of the order (see description below). It does this by using an awakeable, that the delivery manager will use to signal when the delivery is done. Once the delivery is done, the order workflow will set the order status to `DELIVERED`. ### The delivery workflow -To get the order delivered a set of services work together. The delivery manager (`start` method in `delivery_manager.ts`) implements the delivery workflow. It tracks the delivery status, by storing it in Restate's state store, and then requests a driver to do the delivery. To do that, it requests a driver from the driver-delivery matcher. The driver-delivery matcher tracks available drivers and pending deliveries for each region, and matches drivers to deliveries. +To get the order delivered a set of services work together. Have a look at the code of the delivery app under `src/app/delivery-app`. The delivery manager (`start` method in `delivery_manager.ts`) implements the delivery workflow. It tracks the delivery status, by storing it in Restate's state store, and then requests a driver to do the delivery. To do that, it requests a driver from the driver-delivery matcher. The driver-delivery matcher tracks available drivers and pending deliveries for each region, and matches drivers to deliveries. Once a driver has been found, the delivery manager assigns the delivery to the driver and sets the order status to `WAITING_FOR_DRIVER`. The delivery has started now. The delivery manager relies for the rest of the delivery updates on the driver digital twin. The driver's digital twin (`driver_digital_twin.ts`) is the digital representation of a driver in the field. Each driver has a mobile app on his phone (here simulated by `external/driver_mobile_app_sim.ts`) which continuously sends updates to the digital twin of the driver: diff --git a/typescript/food-ordering/app/Dockerfile-delivery b/typescript/food-ordering/app/Dockerfile-delivery new file mode 100644 index 00000000..bc256ec6 --- /dev/null +++ b/typescript/food-ordering/app/Dockerfile-delivery @@ -0,0 +1,21 @@ +FROM node:18-alpine + +# dumb-init helps handling SIGTERM and SIGINT correctly +RUN apk add dumb-init + +WORKDIR /usr/src/app + +# copy package.json and package-lock.json separately to cache dependencies +COPY package*.json . +RUN npm install + +COPY --chown=node:node .. . + +RUN npm run build + +RUN npm prune --production +ENV NODE_ENV production + +EXPOSE 9081 +USER node +ENTRYPOINT ["dumb-init", "node", "./dist/delivery-app/app.js"] diff --git a/typescript/food-ordering/app/Dockerfile b/typescript/food-ordering/app/Dockerfile-order similarity index 85% rename from typescript/food-ordering/app/Dockerfile rename to typescript/food-ordering/app/Dockerfile-order index f783d5b4..9b95f42b 100644 --- a/typescript/food-ordering/app/Dockerfile +++ b/typescript/food-ordering/app/Dockerfile-order @@ -18,4 +18,4 @@ ENV NODE_ENV production EXPOSE 9080 USER node -ENTRYPOINT ["dumb-init", "node", "./dist/restate-app/app.js"] +ENTRYPOINT ["dumb-init", "node", "./dist/order-app/app.js"] diff --git a/typescript/food-ordering/app/package.json b/typescript/food-ordering/app/package.json index 6b936379..2ce50df4 100644 --- a/typescript/food-ordering/app/package.json +++ b/typescript/food-ordering/app/package.json @@ -7,11 +7,12 @@ "scripts": { "build": "tsc --noEmitOnError", "prebundle": "rm -rf dist", - "bundle": "esbuild src/restate-app/app.ts --bundle --minify --sourcemap --platform=node --target=es2020 --outfile=dist/index.js", + "bundle": "esbuild src/delivery-app/app.ts --bundle --minify --sourcemap --platform=node --target=es2020 --outfile=dist/index.js", "postbundle": "cd dist && zip -r index.zip index.js*", - "app": "node ./dist/app/app.js", + "order-app": "node ./dist/app/order-app.js", + "delivery-app": "node ./dist/app/delivery-app.js", "restaurant-app": "node ./dist/restaurant/server.js", - "app-dev": "ts-node-dev --watch src --respawn --transpile-only src/restate-app/app.ts" + "app-dev": "RESTATE_DEBUG_LOGGING=JOURNAL ts-node-dev --watch src --respawn --transpile-only src/order-app/app.ts" }, "dependencies": { "@types/node": "^20.6.3", diff --git a/typescript/food-ordering/app/src/restate-app/app.ts b/typescript/food-ordering/app/src/delivery-app/app.ts similarity index 79% rename from typescript/food-ordering/app/src/restate-app/app.ts rename to typescript/food-ordering/app/src/delivery-app/app.ts index 8546796d..93019e0f 100644 --- a/typescript/food-ordering/app/src/restate-app/app.ts +++ b/typescript/food-ordering/app/src/delivery-app/app.ts @@ -13,18 +13,14 @@ import * as restate from "@restatedev/restate-sdk"; import * as driverDigitalTwin from "./driver_digital_twin"; import * as driverDeliveryMatcher from "./driver_delivery_matcher"; import * as deliveryManager from "./delivery_manager"; -import * as orderWorkflow from "./order_workflow"; -import * as orderstatus from "./order_status_service"; import * as driverMobileAppSimulator from "./external/driver_mobile_app_sim"; if (require.main === module) { restate .createServer() - .bindKeyedRouter(orderWorkflow.service.path, orderWorkflow.router) - .bindKeyedRouter(orderstatus.service.path, orderstatus.router) .bindKeyedRouter(driverDigitalTwin.service.path, driverDigitalTwin.router) .bindKeyedRouter(driverDeliveryMatcher.service.path, driverDeliveryMatcher.router) .bindKeyedRouter(deliveryManager.service.path, deliveryManager.router) .bindKeyedRouter(driverMobileAppSimulator.service.path, driverMobileAppSimulator.router) - .listen(9080); + .listen(9081); } diff --git a/typescript/food-ordering/app/src/restate-app/clients/kafka_publisher.ts b/typescript/food-ordering/app/src/delivery-app/clients/kafka_publisher.ts similarity index 99% rename from typescript/food-ordering/app/src/restate-app/clients/kafka_publisher.ts rename to typescript/food-ordering/app/src/delivery-app/clients/kafka_publisher.ts index 83ccec84..55c7f1fc 100644 --- a/typescript/food-ordering/app/src/restate-app/clients/kafka_publisher.ts +++ b/typescript/food-ordering/app/src/delivery-app/clients/kafka_publisher.ts @@ -17,7 +17,6 @@ const KAFKA_CONFIG: KafkaConfig = { brokers: [KAFKA_BOOTSTRAP_SERVERS] }; const KAFKA_TOPIC = "driver-updates"; export class Kafka_publisher { - private readonly kafka = new Kafka(KAFKA_CONFIG); private readonly producer = this.kafka.producer(); private connected = false; diff --git a/typescript/food-ordering/app/src/delivery-app/delivery_manager.ts b/typescript/food-ordering/app/src/delivery-app/delivery_manager.ts new file mode 100644 index 00000000..adc153b4 --- /dev/null +++ b/typescript/food-ordering/app/src/delivery-app/delivery_manager.ts @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2024 - Restate Software, Inc., Restate GmbH + * + * This file is part of the Restate examples, + * which is released under the MIT license. + * + * You can find a copy of the license in the file LICENSE + * in the root directory of this repository or package or at + * https://github.com/restatedev/examples/ + */ + +import * as restate from "@restatedev/restate-sdk"; +import * as driverDigitalTwin from "./driver_digital_twin"; +import * as driverDeliveryMatcher from "./driver_delivery_matcher"; +import * as geo from "./utils/geo"; +import {DEMO_REGION, Location, DeliveryInformation, OrderAndPromise, Status} from "./types/types"; +import * as orderstatus from "../order-app/order_status_service"; + +/** + * Manages the delivery of the order to the customer. Keyed by the order ID (similar to the + * OrderService and OrderStatusService). + */ +export const service: restate.ServiceApi = { path: "delivery-manager" }; + +const DELIVERY_INFO = "delivery-info"; + +export const router = restate.keyedRouter({ + // Called by the OrderService when a new order has been prepared and needs to be delivered. + start: async (ctx: restate.RpcContext, deliveryId: string, { order, promise }: OrderAndPromise) => { + const [restaurantLocation, customerLocation] = await ctx.sideEffect(async () => [geo.randomLocation(), geo.randomLocation()]); + + // Store the delivery information in Restate's state store + const deliveryInfo: DeliveryInformation = { + orderId: order.id, + orderPromise: promise, + restaurantId: order.restaurantId, + restaurantLocation, + customerLocation, + orderPickedUp: false + } + ctx.set(DELIVERY_INFO, deliveryInfo); + + // Acquire a driver + const driverPromise = ctx.awakeable(); + ctx.send(driverDeliveryMatcher.service).requestDriverForDelivery(DEMO_REGION, { promiseId: driverPromise.id }); + // Wait until the driver pool service has located a driver + // This awakeable gets resolved either immediately when there is a pending delivery + // or later, when a new delivery comes in. + const driverId = await driverPromise.promise; + + // Assign the driver to the job + await ctx.rpc(driverDigitalTwin.service).assignDeliveryJob(driverId, { + deliveryId, + restaurantId: order.restaurantId, + restaurantLocation: deliveryInfo.restaurantLocation, + customerLocation: deliveryInfo.customerLocation + }); + + ctx.send(orderstatus.service).setStatus(order.id, Status.WAITING_FOR_DRIVER); + }, + + // called by the DriverService.NotifyDeliveryPickup when the driver has arrived at the restaurant. + notifyDeliveryPickup: async (ctx: restate.RpcContext, _deliveryId: string) => { + const delivery = (await ctx.get(DELIVERY_INFO))!; + delivery.orderPickedUp = true; + ctx.set(DELIVERY_INFO, delivery); + + ctx.send(orderstatus.service).setStatus(delivery.orderId, Status.IN_DELIVERY); + }, + + // Called by the DriverService.NotifyDeliveryDelivered when the driver has delivered the order to the customer. + notifyDeliveryDelivered: async (ctx: restate.RpcContext, _deliveryId: string) => { + const delivery = (await ctx.get(DELIVERY_INFO))!; + ctx.clear(DELIVERY_INFO); + + // Notify the OrderService that the delivery has been completed + ctx.resolveAwakeable(delivery.orderPromise, null); + }, + + // Called by DriverDigitalTwin.HandleDriverLocationUpdateEvent() when the driver moved to new location. + handleDriverLocationUpdate: async (ctx: restate.RpcContext, _deliveryId: string, location: Location) => { + const delivery = (await ctx.get(DELIVERY_INFO))!; + + // Parse the new location, and calculate the ETA of the delivery to the customer + const eta = delivery.orderPickedUp + ? geo.calculateEtaMillis(location, delivery.customerLocation) + : geo.calculateEtaMillis(location, delivery.restaurantLocation) + + geo.calculateEtaMillis(delivery.restaurantLocation, delivery.customerLocation); + ctx.send(orderstatus.service).setETA(delivery.orderId, eta); + }, +}); diff --git a/typescript/food-ordering/app/src/delivery-app/driver_delivery_matcher.ts b/typescript/food-ordering/app/src/delivery-app/driver_delivery_matcher.ts new file mode 100644 index 00000000..22a622e3 --- /dev/null +++ b/typescript/food-ordering/app/src/delivery-app/driver_delivery_matcher.ts @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2024 - Restate Software, Inc., Restate GmbH + * + * This file is part of the Restate examples, + * which is released under the MIT license. + * + * You can find a copy of the license in the file LICENSE + * in the root directory of this repository or package or at + * https://github.com/restatedev/examples/ + */ + +import * as restate from "@restatedev/restate-sdk"; +import { PendingDelivery } from "./types/types"; + +/** + * Links available drivers to delivery requests Keyed by the region. Each region has a pool of + * available drivers and orders waiting for a driver. This service is responsible for tracking and + * matching the two. + */ +export const service : restate.ServiceApi = { path: "driver-delivery-matcher" }; + +const PENDING_DELIVERIES = "pending-deliveries"; +const AVAILABLE_DRIVERS = "available-drivers"; + +export const router = restate.keyedRouter({ + setDriverAvailable: async (ctx: restate.RpcContext, _region: string, driverId: string) => { + // if we have deliveries already waiting, assign those + const pendingDeliveries = await ctx.get(PENDING_DELIVERIES); + if (pendingDeliveries && pendingDeliveries.length > 0) { + const nextDelivery = pendingDeliveries.shift()!; + ctx.set(PENDING_DELIVERIES, pendingDeliveries); + + // Notify that delivery is ongoing + ctx.resolveAwakeable(nextDelivery.promiseId, driverId); + return; + } + + // otherwise remember driver as available + const availableDrivers = (await ctx.get(AVAILABLE_DRIVERS)) ?? []; + availableDrivers.push(driverId); + ctx.set(AVAILABLE_DRIVERS, availableDrivers); + }, + + // Called when a new delivery gets scheduled. + requestDriverForDelivery: async (ctx: restate.RpcContext, _region: string, request: PendingDelivery) => { + // if a driver is available, assign the delivery right away + const availableDrivers = (await ctx.get(AVAILABLE_DRIVERS)); + if (availableDrivers && availableDrivers.length > 0) { + // Remove driver from the pool + const nextAvailableDriver = availableDrivers.shift()!; + ctx.set(AVAILABLE_DRIVERS, availableDrivers); + + // Notify that delivery is ongoing + ctx.resolveAwakeable(request.promiseId, nextAvailableDriver); + return; + } + + // otherwise store the delivery request until a new driver becomes available + const pendingDeliveries = (await ctx.get(PENDING_DELIVERIES)) ?? []; + pendingDeliveries.push(request); + ctx.set(PENDING_DELIVERIES, pendingDeliveries); + }, +}) \ No newline at end of file diff --git a/typescript/food-ordering/app/src/delivery-app/driver_digital_twin.ts b/typescript/food-ordering/app/src/delivery-app/driver_digital_twin.ts new file mode 100644 index 00000000..6bde4acf --- /dev/null +++ b/typescript/food-ordering/app/src/delivery-app/driver_digital_twin.ts @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2024 - Restate Software, Inc., Restate GmbH + * + * This file is part of the Restate examples, + * which is released under the MIT license. + * + * You can find a copy of the license in the file LICENSE + * in the root directory of this repository or package or at + * https://github.com/restatedev/examples/ + */ + +import * as restate from "@restatedev/restate-sdk"; +import * as driverDeliveryMatcher from "./driver_delivery_matcher"; +import * as deliveryManager from "./delivery_manager"; +import {DeliveryRequest, DriverStatus, Location} from "./types/types"; + +/** + * Digital twin for the driver. Represents a driver and his status, assigned delivery, and location. + * Keyed by driver ID. The actual driver would have an application (mocked by Driver Mobile App Simulator) that + * calls this service. + */ +export const service: restate.ServiceApi = {path: "driver-digital-twin"}; + +const DRIVER_STATUS = "driver-status"; +const ASSIGNED_DELIVERY = "assigned-delivery"; +const DRIVER_LOCATION = "driver-location"; + +export const router = restate.keyedRouter({ + // Called by driver's mobile app at start of workday or end of delivery + setDriverAvailable: async (ctx: restate.RpcContext, driverId: string, region: string) => { + await checkIfDriverInExpectedState(DriverStatus.IDLE, ctx); + + ctx.set(DRIVER_STATUS, DriverStatus.WAITING_FOR_WORK); + ctx.send(driverDeliveryMatcher.service).setDriverAvailable(region, driverId); + }, + + // Gets polled by the driver's mobile app at regular intervals to check for assignments. + getAssignedDelivery: async (ctx: restate.RpcContext) => ctx.get(ASSIGNED_DELIVERY), + + // Gets called by the delivery manager when this driver was assigned to do the delivery. + assignDeliveryJob: async (ctx: restate.RpcContext, _driverId: string, deliveryRequest: DeliveryRequest) => { + await checkIfDriverInExpectedState(DriverStatus.WAITING_FOR_WORK, ctx); + + ctx.set(DRIVER_STATUS, DriverStatus.DELIVERING); + ctx.set(ASSIGNED_DELIVERY, deliveryRequest); + + const currentLocation = await ctx.get(DRIVER_LOCATION); + if (currentLocation) { + ctx.send(deliveryManager.service).handleDriverLocationUpdate(deliveryRequest.deliveryId, currentLocation); + } + }, + + // Called by driver's mobile app at pickup from the restaurant. + notifyDeliveryPickUp: async (ctx: restate.RpcContext, _driverId: string) => { + await checkIfDriverInExpectedState(DriverStatus.DELIVERING, ctx); + const assignedDelivery = (await ctx.get(ASSIGNED_DELIVERY))!; + ctx.send(deliveryManager.service).notifyDeliveryPickup(assignedDelivery.deliveryId); + }, + + // Called by driver's mobile app after delivery success. + notifyDeliveryDelivered: async (ctx: restate.RpcContext, _driverId: string) => { + await checkIfDriverInExpectedState(DriverStatus.DELIVERING, ctx); + + const assignedDelivery = (await ctx.get(ASSIGNED_DELIVERY))!; + ctx.clear(ASSIGNED_DELIVERY); + ctx.send(deliveryManager.service).notifyDeliveryDelivered(assignedDelivery.deliveryId); + ctx.set(DRIVER_STATUS, DriverStatus.IDLE); + }, + + // Called by the driver's mobile app when he has moved to a new location. + handleDriverLocationUpdateEvent: restate.keyedEventHandler(async (ctx: restate.RpcContext, event: restate.Event) => { + const location = event.json(); + ctx.set(DRIVER_LOCATION, location); + const assignedDelivery = await ctx.get(ASSIGNED_DELIVERY); + if (assignedDelivery) { + ctx.send(deliveryManager.service).handleDriverLocationUpdate(assignedDelivery.deliveryId, location); + } + }) +}) + + +async function checkIfDriverInExpectedState(expectedStatus: DriverStatus, ctx: restate.RpcContext): Promise { + const currentStatus = (await ctx.get(DRIVER_STATUS)) ?? DriverStatus.IDLE; + + if (currentStatus !== expectedStatus) { + throw new restate.TerminalError(`Driver status wrong. Expected ${expectedStatus} but was ${currentStatus}`); + } +} \ No newline at end of file diff --git a/typescript/food-ordering/app/src/delivery-app/external/driver_mobile_app_sim.ts b/typescript/food-ordering/app/src/delivery-app/external/driver_mobile_app_sim.ts new file mode 100644 index 00000000..97c23a5f --- /dev/null +++ b/typescript/food-ordering/app/src/delivery-app/external/driver_mobile_app_sim.ts @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2024 - Restate Software, Inc., Restate GmbH + * + * This file is part of the Restate Examples for the Node.js/TypeScript SDK, + * which is released under the MIT license. + * + * You can find a copy of the license in the file LICENSE + * in the root directory of this repository or package or at + * https://github.com/restatedev/examples/blob/main/LICENSE + */ + +import * as restate from "@restatedev/restate-sdk"; +import * as driverDigitalTwin from "../driver_digital_twin"; +import * as geo from "../utils/geo"; +import {DEMO_REGION, Location, DeliveryState} from "../types/types"; +import { getPublisher } from "../clients/kafka_publisher"; +import {updateLocation} from "./driver_mobile_app_sim_utils"; + +/** + * !!!SHOULD BE AN EXTERNAL APP ON THE DRIVER's PHONE!!! Simulated driver with application that + * interacts with the food ordering app. This is not really part of the food ordering application. + * This would actually be a mobile app that drivers use to accept delivery requests, and to set + * themselves as available. + * + * For simplicity, we implemented this with Restate. + */ +export const service: restate.ServiceApi = { path : "driver-mobile-app" }; + +const kafkaPublisher = getPublisher(); + +const ASSIGNED_DELIVERY = "assigned-delivery"; +const CURRENT_LOCATION = "current-location"; + +const POLL_INTERVAL = 1000; +const MOVE_INTERVAL = 1000; +const PAUSE_BETWEEN_DELIVERIES = 2000; + +export const router = restate.keyedRouter({ + startDriver: async (ctx: restate.RpcContext, driverId: string) => { + // check if we exist already + if (await ctx.get(CURRENT_LOCATION) !== null) { + return; + } + + console.log(`Driver ${driverId} starting up`); + + const location = await ctx.sideEffect(async () => geo.randomLocation()); + ctx.set(CURRENT_LOCATION, location); + await kafkaPublisher.send(driverId, location); + + ctx.send(driverDigitalTwin.service).setDriverAvailable(driverId, DEMO_REGION); + ctx.send(service).pollForWork(driverId); + }, + + pollForWork: async (ctx: restate.RpcContext, driverId: string) => { + const optionalAssignedDelivery = await ctx.rpc(driverDigitalTwin.service).getAssignedDelivery(driverId); + if (optionalAssignedDelivery === null || optionalAssignedDelivery === undefined) { + ctx.sendDelayed(service, POLL_INTERVAL).pollForWork(driverId); + return; + } + + const delivery: DeliveryState = { + currentDelivery: optionalAssignedDelivery, + orderPickedUp: false + } + ctx.set(ASSIGNED_DELIVERY, delivery); + ctx.sendDelayed(service, MOVE_INTERVAL).move(driverId); +}, + + move: async (ctx: restate.RpcContext, driverId: string) => { + const currentLocation = (await ctx.get(CURRENT_LOCATION))!; + const assignedDelivery = (await ctx.get(ASSIGNED_DELIVERY))!; + + const nextTarget = assignedDelivery.orderPickedUp + ? assignedDelivery.currentDelivery.customerLocation + : assignedDelivery.currentDelivery.restaurantLocation; + + const { newLocation, arrived } = updateLocation(currentLocation, nextTarget); + + ctx.set(CURRENT_LOCATION, newLocation); + await kafkaPublisher.send(driverId, currentLocation); + + if (arrived) { + if (assignedDelivery.orderPickedUp) { + // fully done + ctx.clear(ASSIGNED_DELIVERY); + + await ctx.rpc(driverDigitalTwin.service).notifyDeliveryDelivered(driverId); + await ctx.sleep(PAUSE_BETWEEN_DELIVERIES); + ctx.send(driverDigitalTwin.service).setDriverAvailable(driverId, DEMO_REGION); + ctx.send(service).pollForWork(driverId); + return; + } + + assignedDelivery.orderPickedUp = true; + ctx.set(ASSIGNED_DELIVERY, assignedDelivery); + + await ctx.rpc(driverDigitalTwin.service).notifyDeliveryPickUp(driverId); + } + + ctx.sendDelayed(service, MOVE_INTERVAL).move(driverId); + } +}) diff --git a/typescript/food-ordering/app/src/restate-app/external/driver_mobile_app_sim_utils.ts b/typescript/food-ordering/app/src/delivery-app/external/driver_mobile_app_sim_utils.ts similarity index 100% rename from typescript/food-ordering/app/src/restate-app/external/driver_mobile_app_sim_utils.ts rename to typescript/food-ordering/app/src/delivery-app/external/driver_mobile_app_sim_utils.ts diff --git a/typescript/food-ordering/app/src/restate-app/types/types.ts b/typescript/food-ordering/app/src/delivery-app/types/types.ts similarity index 94% rename from typescript/food-ordering/app/src/restate-app/types/types.ts rename to typescript/food-ordering/app/src/delivery-app/types/types.ts index d1440dbb..e62170d4 100644 --- a/typescript/food-ordering/app/src/restate-app/types/types.ts +++ b/typescript/food-ordering/app/src/delivery-app/types/types.ts @@ -8,6 +8,8 @@ * in the root directory of this repository or package or at * https://github.com/restatedev/examples/ */ +import * as restate from "@restatedev/restate-sdk"; +import {router} from "../../order-app/order_status_service"; export type Product = { productId: string; diff --git a/typescript/food-ordering/app/src/restate-app/utils/geo.ts b/typescript/food-ordering/app/src/delivery-app/utils/geo.ts similarity index 100% rename from typescript/food-ordering/app/src/restate-app/utils/geo.ts rename to typescript/food-ordering/app/src/delivery-app/utils/geo.ts diff --git a/typescript/food-ordering/app/src/restate-app/utils/utils.ts b/typescript/food-ordering/app/src/delivery-app/utils/utils.ts similarity index 72% rename from typescript/food-ordering/app/src/restate-app/utils/utils.ts rename to typescript/food-ordering/app/src/delivery-app/utils/utils.ts index 859c3667..5c4db8b8 100644 --- a/typescript/food-ordering/app/src/restate-app/utils/utils.ts +++ b/typescript/food-ordering/app/src/delivery-app/utils/utils.ts @@ -17,10 +17,3 @@ export function fail(id: string, msg: string) { throw new Error(errorMsg); } -export const completed = async (ctx: restate.RpcContext): Promise => { - if (await ctx.get("completed") === true) { - return true - } - ctx.set("completed", true) - return false -} diff --git a/typescript/food-ordering/app/src/order-app/app.ts b/typescript/food-ordering/app/src/order-app/app.ts new file mode 100644 index 00000000..f33920e8 --- /dev/null +++ b/typescript/food-ordering/app/src/order-app/app.ts @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2024 - Restate Software, Inc., Restate GmbH + * + * This file is part of the Restate examples, + * which is released under the MIT license. + * + * You can find a copy of the license in the file LICENSE + * in the root directory of this repository or package or at + * https://github.com/restatedev/examples/ + */ + +import * as restate from "@restatedev/restate-sdk"; +import * as orderWorkflow from "./order_workflow"; +import * as orderstatus from "./order_status_service"; + +if (require.main === module) { + restate + .createServer() + .bindKeyedRouter(orderWorkflow.service.path, orderWorkflow.router) + .bindKeyedRouter(orderstatus.service.path, orderstatus.router) + .listen(9080); +} diff --git a/typescript/food-ordering/app/src/restate-app/clients/payment_client.ts b/typescript/food-ordering/app/src/order-app/clients/payment_client.ts similarity index 100% rename from typescript/food-ordering/app/src/restate-app/clients/payment_client.ts rename to typescript/food-ordering/app/src/order-app/clients/payment_client.ts diff --git a/typescript/food-ordering/app/src/restate-app/clients/restaurant_client.ts b/typescript/food-ordering/app/src/order-app/clients/restaurant_client.ts similarity index 100% rename from typescript/food-ordering/app/src/restate-app/clients/restaurant_client.ts rename to typescript/food-ordering/app/src/order-app/clients/restaurant_client.ts diff --git a/typescript/food-ordering/app/src/order-app/order_status_service.ts b/typescript/food-ordering/app/src/order-app/order_status_service.ts new file mode 100644 index 00000000..562fb535 --- /dev/null +++ b/typescript/food-ordering/app/src/order-app/order_status_service.ts @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024 - Restate Software, Inc., Restate GmbH + * + * This file is part of the Restate examples, + * which is released under the MIT license. + * + * You can find a copy of the license in the file LICENSE + * in the root directory of this repository or package or at + * https://github.com/restatedev/examples/ + */ + +import { OrderStatus, Status } from "./types/types"; +import * as restate from "@restatedev/restate-sdk"; + +const ORDER_STATUS = "order_status"; + +export const service: restate.ServiceApi = { path: "order-status" }; + +export const router = restate.keyedRouter({ + /** Gets called by the webUI frontend to display the status of an order. */ + get: async (ctx: restate.RpcContext, _orderId: string) => + ctx.get(ORDER_STATUS), + + setStatus: async (ctx: restate.RpcContext, orderId: string, status: Status ) => { + console.info(`[${orderId}] Order is ${status}`); + const currentStatus = await ctx.get(ORDER_STATUS); + ctx.set(ORDER_STATUS, { ...currentStatus, ...{ status } }); + }, + + setETA: async (ctx: restate.RpcContext, orderId: string, eta: number) => { + const currentStatus = await ctx.get(ORDER_STATUS); + ctx.set(ORDER_STATUS, { + eta: eta, + status: eta === 0 ? Status.DELIVERED : currentStatus?.status, + }); + }, + + eventHandler: restate.keyedEventHandler(async (ctx: restate.RpcContext, event: restate.Event) => { + const eta = +event.body().toString(); + const currentStatus = await ctx.get(ORDER_STATUS); + ctx.set(ORDER_STATUS, { + eta: eta, + status: eta === 0 ? Status.DELIVERED : currentStatus?.status, + }); + }), +}); \ No newline at end of file diff --git a/typescript/food-ordering/app/src/order-app/order_workflow.ts b/typescript/food-ordering/app/src/order-app/order_workflow.ts new file mode 100644 index 00000000..240ff9c0 --- /dev/null +++ b/typescript/food-ordering/app/src/order-app/order_workflow.ts @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2024 - Restate Software, Inc., Restate GmbH + * + * This file is part of the Restate examples, + * which is released under the MIT license. + * + * You can find a copy of the license in the file LICENSE + * in the root directory of this repository or package or at + * https://github.com/restatedev/examples/ + */ + +import * as restate from "@restatedev/restate-sdk"; +import * as deliveryManager from "../delivery-app/delivery_manager"; +import {Order, Status} from "./types/types"; +import * as orderstatus from "./order_status_service"; +import {getPaymentClient} from "./clients/payment_client"; +import {getRestaurantClient} from "./clients/restaurant_client"; + +/** + * Order processing workflow Gets called for each Kafka event that is published to the order topic. + * The event contains the order ID and the raw JSON order. The workflow handles the payment, asks + * the restaurant to start the preparation, and triggers the delivery. + */ +export const service: restate.ServiceApi = { path: "order-workflow" }; + +const restaurant = getRestaurantClient(); +const paymentClnt = getPaymentClient(); + +export const router = restate.keyedRouter({ + create: async ( ctx: restate.RpcContext, orderId: string, order: Order) => { + const { id, totalCost, deliveryDelay } = order; + + // 1. Set status + ctx.send(orderstatus.service).setStatus(id, Status.CREATED); + + // 2. Handle payment + const token = ctx.rand.uuidv4(); + const paid = await ctx.sideEffect(() => paymentClnt.charge(id, token, totalCost)); + + if (!paid) { + ctx.send(orderstatus.service).setStatus(id, Status.REJECTED); + return; + } + + // 3. Schedule preparation + ctx.send(orderstatus.service).setStatus(id, Status.SCHEDULED); + await ctx.sleep(deliveryDelay); + + // 4. Trigger preparation + const preparationPromise = ctx.awakeable(); + await ctx.sideEffect(() => restaurant.prepare(id, preparationPromise.id)); + ctx.send(orderstatus.service).setStatus(id, Status.IN_PREPARATION); + + await preparationPromise.promise; + ctx.send(orderstatus.service).setStatus(id, Status.SCHEDULING_DELIVERY); + + // 5. Find a driver and start delivery + await deliver(ctx, order); + ctx.send(orderstatus.service).setStatus(id, Status.DELIVERED); + } +}); + +async function deliver(ctx: restate.RpcContext, order: Order) { + const deliveryId = ctx.rand.uuidv4(); + const deliveryPromise = ctx.awakeable(); + await ctx.rpc(deliveryManager.service).start(deliveryId, {order, promise: deliveryPromise.id}) + await deliveryPromise.promise; +} diff --git a/typescript/food-ordering/app/src/order-app/types/types.ts b/typescript/food-ordering/app/src/order-app/types/types.ts new file mode 100644 index 00000000..51123b74 --- /dev/null +++ b/typescript/food-ordering/app/src/order-app/types/types.ts @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2024 - Restate Software, Inc., Restate GmbH + * + * This file is part of the Restate examples, + * which is released under the MIT license. + * + * You can find a copy of the license in the file LICENSE + * in the root directory of this repository or package or at + * https://github.com/restatedev/examples/ + */ + +import * as restate from "@restatedev/restate-sdk"; + +export type Product = { + productId: string; + description: string; + quantity: number; +}; + +export type Order = { + id: string, + restaurantId: string; + products: Product[]; + totalCost: number; + deliveryDelay: number; +}; + +export enum Status { + NEW = "NEW", + CREATED = "CREATED", + SCHEDULED = "SCHEDULED", + IN_PREPARATION = "IN_PREPARATION", + SCHEDULING_DELIVERY = "SCHEDULING_DELIVERY", + WAITING_FOR_DRIVER = "WAITING_FOR_DRIVER", + IN_DELIVERY = "IN_DELIVERY", + DELIVERED = "DELIVERED", + REJECTED = "REJECTED", + CANCELLED = "CANCELLED", +} + +export type OrderStatus = { + status?: Status; + eta?: number; +} + +export type DeliveryRequest = { + deliveryId: string, + restaurantId: string, + restaurantLocation: Location, + customerLocation: Location +} + +export type Location = { + long: number, + lat: number, +} + +export type LocationTimestamp = { + long: number, + lat: number, + timestamp: number +} + +export const DEMO_REGION = "San Jose (CA)"; + +export type OrderAndPromise = { + order: Order, + promise: string +} + +export enum DriverStatus { + IDLE = "IDLE", + WAITING_FOR_WORK = "WAITING_FOR_WORK", + DELIVERING = "DELIVERING" +} + + + diff --git a/typescript/food-ordering/app/src/restate-app/delivery_manager.ts b/typescript/food-ordering/app/src/restate-app/delivery_manager.ts deleted file mode 100644 index 1a1f6777..00000000 --- a/typescript/food-ordering/app/src/restate-app/delivery_manager.ts +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (c) 2024 - Restate Software, Inc., Restate GmbH - * - * This file is part of the Restate examples, - * which is released under the MIT license. - * - * You can find a copy of the license in the file LICENSE - * in the root directory of this repository or package or at - * https://github.com/restatedev/examples/ - */ - -import * as restate from "@restatedev/restate-sdk"; -import * as driverDigitalTwin from "./driver_digital_twin"; -import * as driverDeliveryMatcher from "./driver_delivery_matcher"; -import * as geo from "./utils/geo"; -import {DEMO_REGION, Location, DeliveryInformation, OrderAndPromise, Status} from "./types/types"; -import * as orderstatus from "./order_status_service"; - -/** - * Manages the delivery of the order to the customer. Keyed by the order ID (similar to the - * OrderService and OrderStatusService). - */ - -// State key to store all relevant information about the delivery. -const DELIVERY_INFO = "delivery-info"; - -/** - * Finds a driver, assigns the delivery job to the driver, and updates the status of the order. - * Gets called by the OrderService when a new order has been prepared and needs to be delivered. - */ -async function start(ctx: restate.RpcContext, deliveryId: string, { order, promise }: OrderAndPromise) { - - // Temporary placeholder: random location - const [restaurantLocation, customerLocation] = await ctx.sideEffect(async () => [geo.randomLocation(), geo.randomLocation()]); - - // Store the delivery information in Restate's state store - const deliveryInfo: DeliveryInformation = { - orderId: order.id, - orderPromise: promise, - restaurantId: order.restaurantId, - restaurantLocation, - customerLocation, - orderPickedUp: false - } - ctx.set(DELIVERY_INFO, deliveryInfo); - - // Acquire a driver - const driverPromise = ctx.awakeable(); - ctx.send(driverDeliveryMatcher.service).requestDriverForDelivery(DEMO_REGION, { promiseId: driverPromise.id }); - // Wait until the driver pool service has located a driver - // This awakeable gets resolved either immediately when there is a pending delivery - // or later, when a new delivery comes in. - const driverId = await driverPromise.promise; - - // Assign the driver to the job - await ctx - .rpc(driverDigitalTwin.service) - .assignDeliveryJob(driverId, { - deliveryId, - restaurantId: order.restaurantId, - restaurantLocation: deliveryInfo.restaurantLocation, - customerLocation: deliveryInfo.customerLocation - }); - - // Update the status of the order to "waiting for the driver" - ctx.send(orderstatus.service).setStatus(order.id, Status.WAITING_FOR_DRIVER); -} - -/** - * Notifies that the delivery was picked up. Gets called by the DriverService.NotifyDeliveryPickup - * when the driver has arrived at the restaurant. - */ -async function notifyDeliveryPickup(ctx: restate.RpcContext, _deliveryId: string) { - // Retrieve the delivery information for this delivery - const delivery = (await ctx.get(DELIVERY_INFO))!; - // Update the status of the delivery to "picked up" - delivery.orderPickedUp = true; - ctx.set(DELIVERY_INFO, delivery); - - // Update the status of the order to "in delivery" - ctx.send(orderstatus.service).setStatus(delivery.orderId, Status.IN_DELIVERY); -} - -/** - * Notifies that the order was delivered. Gets called by the DriverService.NotifyDeliveryDelivered - * when the driver has delivered the order to the customer. - */ -async function notifyDeliveryDelivered(ctx: restate.RpcContext, _deliveryId: string) { - // Retrieve the delivery information for this delivery - const delivery = (await ctx.get(DELIVERY_INFO))!; - - // Order has been delivered, so state can be cleared - ctx.clear(DELIVERY_INFO); - - // Notify the OrderService that the delivery has been completed - ctx.resolveAwakeable(delivery.orderPromise, null); -} - -/** - * Updates the location of the order. Gets called by - * DriverService.HandleDriverLocationUpdateEvent() (digital twin of the driver) when the driver - * has moved to a new location. - */ -async function handleDriverLocationUpdate(ctx: restate.RpcContext, _deliveryId: string, location: Location) { - // Retrieve the delivery information for this delivery - const delivery = (await ctx.get(DELIVERY_INFO))!; - - // Parse the new location, and calculate the ETA of the delivery to the customer - const eta = delivery.orderPickedUp - ? geo.calculateEtaMillis(location, delivery.customerLocation) - : geo.calculateEtaMillis(location, delivery.restaurantLocation) - + geo.calculateEtaMillis(delivery.restaurantLocation, delivery.customerLocation); - - // Update the ETA of the order - ctx.send(orderstatus.service).setETA(delivery.orderId, eta); -} - -export const router = restate.keyedRouter({ - start, - notifyDeliveryPickup, - notifyDeliveryDelivered, - handleDriverLocationUpdate, -}); - -export type api = typeof router; -export const service: restate.ServiceApi = { path: "delivery-manager" }; diff --git a/typescript/food-ordering/app/src/restate-app/driver_delivery_matcher.ts b/typescript/food-ordering/app/src/restate-app/driver_delivery_matcher.ts deleted file mode 100644 index 9278f411..00000000 --- a/typescript/food-ordering/app/src/restate-app/driver_delivery_matcher.ts +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (c) 2024 - Restate Software, Inc., Restate GmbH - * - * This file is part of the Restate examples, - * which is released under the MIT license. - * - * You can find a copy of the license in the file LICENSE - * in the root directory of this repository or package or at - * https://github.com/restatedev/examples/ - */ - -import * as restate from "@restatedev/restate-sdk"; -import { PendingDelivery } from "./types/types"; - -/** - * Links available drivers to delivery requests Keyed by the region. Each region has a pool of - * available drivers and orders waiting for a driver. This service is responsible for tracking and - * matching the two. - */ - -// Deliveries that are waiting for a driver to become available -const PENDING_DELIVERIES = "pending-deliveries"; -// Drivers that are waiting for new delivery requests -const AVAILABLE_DRIVERS = "available-drivers"; - -/** - * Gets called when a new driver becomes available. Links the driver to the next delivery waiting - * in line. If no pending deliveries, driver is added to the available driver pool - */ -async function setDriverAvailable(ctx: restate.RpcContext, _region: string, driverId: string): Promise { - // if we have deliveries already waiting, assign those - const pendingDeliveries = await ctx.get(PENDING_DELIVERIES); - - // If there is a pending delivery, assign it to the driver - if (pendingDeliveries && pendingDeliveries.length > 0) { - // Update the queue in state. Delivery was removed. - const nextDelivery = pendingDeliveries.shift()!; - ctx.set(PENDING_DELIVERIES, pendingDeliveries); - // Notify that delivery is ongoing - ctx.resolveAwakeable(nextDelivery.promiseId, driverId); - return; - } - - // otherwise remember driver as available - const availableDrivers = (await ctx.get(AVAILABLE_DRIVERS)) ?? []; - availableDrivers.push(driverId); - ctx.set(AVAILABLE_DRIVERS, availableDrivers); -} - -/** - * Gets called when a new delivery gets scheduled. Links the delivery to the next driver - * available. If no available drivers, the delivery is added to the pending deliveries queue - */ -async function requestDriverForDelivery(ctx: restate.RpcContext, _region: string, request: PendingDelivery): Promise { - const availableDrivers = (await ctx.get(AVAILABLE_DRIVERS)); - - // if a driver is available, assign the delivery right away - if (availableDrivers && availableDrivers.length > 0) { - // Remove driver from the pool - const nextAvailableDriver = availableDrivers.shift()!; - ctx.set(AVAILABLE_DRIVERS, availableDrivers); - - // Notify that delivery is ongoing - ctx.resolveAwakeable(request.promiseId, nextAvailableDriver); - return; - } - - // otherwise store the delivery request until a new driver becomes available - const pendingDeliveries = (await ctx.get(PENDING_DELIVERIES)) ?? []; - pendingDeliveries.push(request); - ctx.set(PENDING_DELIVERIES, pendingDeliveries); -} - -export const router = restate.keyedRouter({ - setDriverAvailable, - requestDriverForDelivery, -}) - -export type api = typeof router; -export const service : restate.ServiceApi = { path: "driver-delivery-matcher" }; \ No newline at end of file diff --git a/typescript/food-ordering/app/src/restate-app/driver_digital_twin.ts b/typescript/food-ordering/app/src/restate-app/driver_digital_twin.ts deleted file mode 100644 index b3b72e51..00000000 --- a/typescript/food-ordering/app/src/restate-app/driver_digital_twin.ts +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (c) 2024 - Restate Software, Inc., Restate GmbH - * - * This file is part of the Restate examples, - * which is released under the MIT license. - * - * You can find a copy of the license in the file LICENSE - * in the root directory of this repository or package or at - * https://github.com/restatedev/examples/ - */ - -import * as restate from "@restatedev/restate-sdk"; -import * as driverDeliveryMatcher from "./driver_delivery_matcher"; -import * as deliveryManager from "./delivery_manager"; -import {DeliveryRequest, DriverStatus, Location} from "./types/types"; - -/** - * Digital twin for the driver. Represents a driver and his status, assigned delivery, and location. - * Keyed by driver ID. The actual driver would have an application (mocked by Driver Mobile App Simulator) that - * calls this service. - */ - -// Current status of the driver: idle, waiting for work, or delivering -const DRIVER_STATUS = "driver-status"; -// Only set if the driver is currently doing a delivery -const ASSIGNED_DELIVERY = "assigned-delivery"; -// Current location of the driver -const DRIVER_LOCATION = "driver-location"; - -/** - * When the driver starts his work day or finishes a delivery, his application (Driver mobile app simulator) - * calls this method. - */ -async function setDriverAvailable(ctx: restate.RpcContext, driverId: string, region: string): Promise { - await expectStatus(DriverStatus.IDLE, ctx); - - ctx.set(DRIVER_STATUS, DriverStatus.WAITING_FOR_WORK); - ctx.send(driverDeliveryMatcher.service).setDriverAvailable(region, driverId); -} - -/** - * Gets called by the delivery manager when this driver was assigned to do the delivery. Updates - * the status of the digital driver twin, and notifies the delivery service of its current - * location. - */ -async function assignDeliveryJob(ctx: restate.RpcContext, _driverId: string, deliveryRequest: DeliveryRequest): Promise { - await expectStatus(DriverStatus.WAITING_FOR_WORK, ctx); - - // Update the status and assigned delivery information of the driver - ctx.set(DRIVER_STATUS, DriverStatus.DELIVERING); - ctx.set(ASSIGNED_DELIVERY, deliveryRequest); - - // Notify current location to the delivery service - const currentLocation = await ctx.get(DRIVER_LOCATION); - if (currentLocation) { - ctx.send(deliveryManager.service).handleDriverLocationUpdate(deliveryRequest.deliveryId, currentLocation); - } -} - -/** - * Gets called by the driver's mobile app when he has picked up the delivery from the restaurant. - */ -async function notifyDeliveryPickUp(ctx: restate.RpcContext, _driverId: string): Promise { - await expectStatus(DriverStatus.DELIVERING, ctx); - - // Retrieve the ongoing delivery and update its status - const assignedDelivery = (await ctx.get(ASSIGNED_DELIVERY))!; - - // Update the status of the delivery in the delivery manager - ctx.send(deliveryManager.service).notifyDeliveryPickup(assignedDelivery.deliveryId); -} - -/** - * Gets called by the driver's mobile app when he has delivered the order to the customer. - */ -async function notifyDeliveryDelivered(ctx: restate.RpcContext, _driverId: string): Promise { - await expectStatus(DriverStatus.DELIVERING, ctx); - - // Retrieve the ongoing delivery - const assignedDelivery = (await ctx.get(ASSIGNED_DELIVERY))!; - // Clean up the state - ctx.clear(ASSIGNED_DELIVERY); - - // Notify the delivery service that the delivery was delivered - ctx.send(deliveryManager.service).notifyDeliveryDelivered(assignedDelivery.deliveryId); - - // Update the status of the driver to idle - ctx.set(DRIVER_STATUS, DriverStatus.IDLE); -} - -/** Gets called by the driver's mobile app when he has moved to a new location. */ -async function handleDriverLocationUpdateEvent(ctx: restate.RpcContext, event: restate.Event) { - const location = event.json(); - ctx.set(DRIVER_LOCATION, location); - const assignedDelivery = await ctx.get(ASSIGNED_DELIVERY); - if (assignedDelivery) { - ctx.send(deliveryManager.service).handleDriverLocationUpdate(assignedDelivery.deliveryId, location); - } -} - -/** - * Returns null if no delivery was assigned, or the delivery information if a delivery was - * assigned. Gets polled by the driver's mobile app at regular intervals to check if a delivery - * got assigned to him. - */ -function getAssignedDelivery(ctx: restate.RpcContext): Promise { - return ctx.get(ASSIGNED_DELIVERY); -} - -export const router = restate.keyedRouter({ - // external API - setDriverAvailable, - notifyDeliveryPickUp, - notifyDeliveryDelivered, - getAssignedDelivery, - assignDeliveryJob, - // event handler - handleDriverLocationUpdateEvent: restate.keyedEventHandler(handleDriverLocationUpdateEvent) - -}) - -export type api = typeof router; -export const service: restate.ServiceApi = { path: "driver-digital-twin" }; - - -// -------------------------------------------------------- -// utils -// -------------------------------------------------------- - -// Utility function to check if the driver is in the expected state -// If the driver is in a different state, a terminal exception is thrown that stops any retries -// from taking place. -// Is only called from inside the driver service. -async function expectStatus(expectedStatus: DriverStatus, ctx: restate.RpcContext): Promise { - const currentStatus = (await ctx.get(DRIVER_STATUS)) ?? DriverStatus.IDLE; - - if (currentStatus !== expectedStatus) { - throw new restate.TerminalError(`Driver status wrong. Expected ${expectedStatus} but was ${currentStatus}`); - } -} \ No newline at end of file diff --git a/typescript/food-ordering/app/src/restate-app/external/driver_mobile_app_sim.ts b/typescript/food-ordering/app/src/restate-app/external/driver_mobile_app_sim.ts deleted file mode 100644 index 4c73bb5c..00000000 --- a/typescript/food-ordering/app/src/restate-app/external/driver_mobile_app_sim.ts +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (c) 2024 - Restate Software, Inc., Restate GmbH - * - * This file is part of the Restate Examples for the Node.js/TypeScript SDK, - * which is released under the MIT license. - * - * You can find a copy of the license in the file LICENSE - * in the root directory of this repository or package or at - * https://github.com/restatedev/examples/blob/main/LICENSE - */ - -import * as restate from "@restatedev/restate-sdk"; -import * as driverDigitalTwin from "../driver_digital_twin"; -import * as geo from "../utils/geo"; -import {DEMO_REGION, Location, DeliveryState} from "../types/types"; -import { getPublisher } from "../clients/kafka_publisher"; -import {updateLocation} from "./driver_mobile_app_sim_utils"; - -/** - * !!!SHOULD BE AN EXTERNAL APP ON THE DRIVER's PHONE!!! Simulated driver with application that - * interacts with the food ordering app. This is not really part of the food ordering application. - * This would actually be a mobile app that drivers use to accept delivery requests, and to set - * themselves as available. - * - * For simplicity, we implemented this with Restate. - */ - -const kafkaPublisher = getPublisher(); - -const ASSIGNED_DELIVERY = "assigned-delivery"; -const CURRENT_LOCATION = "current-location"; - -const POLL_INTERVAL = 1000; -const MOVE_INTERVAL = 1000; -const PAUSE_BETWEEN_DELIVERIES = 2000; - -/** - * Mimics the driver setting himself to available in the app - */ -async function startDriver(ctx: restate.RpcContext, driverId: string) { - // check if we exist already - if (await ctx.get(CURRENT_LOCATION) !== null) { - return; - } - - console.log(`Driver ${driverId} starting up`); - - const location = await ctx.sideEffect(async () => geo.randomLocation()); - ctx.set(CURRENT_LOCATION, location); - await kafkaPublisher.send(driverId, location); - - ctx.send(driverDigitalTwin.service).setDriverAvailable(driverId, DEMO_REGION); - ctx.send(service).pollForWork(driverId); -} - -/** - * Asks the food ordering app to get a new delivery job. If there is no job, the driver will ask - * again after a short delay. - */ -async function pollForWork(ctx: restate.RpcContext, driverId: string) { - const optionalAssignedDelivery = await ctx.rpc(driverDigitalTwin.service).getAssignedDelivery(driverId); - if (optionalAssignedDelivery === null || optionalAssignedDelivery === undefined) { - ctx.sendDelayed(service, POLL_INTERVAL).pollForWork(driverId); - return; - } - - const delivery: DeliveryState = { - currentDelivery: optionalAssignedDelivery, - orderPickedUp: false - } - ctx.set(ASSIGNED_DELIVERY, delivery); - ctx.sendDelayed(service, MOVE_INTERVAL).move(driverId); -} - -/** - * Periodically lets the food ordering app know the new location - */ -async function move(ctx: restate.RpcContext, driverId: string) { - const currentLocation = (await ctx.get(CURRENT_LOCATION))!; - const assignedDelivery = (await ctx.get(ASSIGNED_DELIVERY))!; - - const nextTarget = assignedDelivery.orderPickedUp - ? assignedDelivery.currentDelivery.customerLocation - : assignedDelivery.currentDelivery.restaurantLocation; - - const { newLocation, arrived } = updateLocation(currentLocation, nextTarget); - - ctx.set(CURRENT_LOCATION, newLocation); - await kafkaPublisher.send(driverId, currentLocation); - - if (arrived) { - if (assignedDelivery.orderPickedUp) { - // fully done - ctx.clear(ASSIGNED_DELIVERY); - - await ctx.rpc(driverDigitalTwin.service).notifyDeliveryDelivered(driverId); - await ctx.sleep(PAUSE_BETWEEN_DELIVERIES); - ctx.send(driverDigitalTwin.service).setDriverAvailable(driverId, DEMO_REGION); - ctx.send(service).pollForWork(driverId); - return; - } - - assignedDelivery.orderPickedUp = true; - ctx.set(ASSIGNED_DELIVERY, assignedDelivery); - - await ctx.rpc(driverDigitalTwin.service).notifyDeliveryPickUp(driverId); - } - - ctx.sendDelayed(service, MOVE_INTERVAL).move(driverId); -} - - -export const router = restate.keyedRouter({ - startDriver, - pollForWork, - move -}) - -export const service: restate.ServiceApi = { path : "driver-mobile-app" }; diff --git a/typescript/food-ordering/app/src/restate-app/order_status_service.ts b/typescript/food-ordering/app/src/restate-app/order_status_service.ts deleted file mode 100644 index 4790c43a..00000000 --- a/typescript/food-ordering/app/src/restate-app/order_status_service.ts +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2024 - Restate Software, Inc., Restate GmbH - * - * This file is part of the Restate examples, - * which is released under the MIT license. - * - * You can find a copy of the license in the file LICENSE - * in the root directory of this repository or package or at - * https://github.com/restatedev/examples/ - */ - -import { OrderStatus, Status } from "./types/types"; -import * as restate from "@restatedev/restate-sdk"; - -const ORDER_STATUS = "order_status"; - -/** Gets called by the webUI frontend to display the status of an order. */ -const get = async (ctx: restate.RpcContext, _orderId: string) => - ctx.get(ORDER_STATUS); - -const setStatus = async ( - ctx: restate.RpcContext, - orderId: string, - status: Status -) => { - console.info(`[${orderId}] Order is ${status}`); - const currentStatus = await ctx.get(ORDER_STATUS); - ctx.set(ORDER_STATUS, { ...currentStatus, ...{ status } }); -}; - -const setETA = async ( - ctx: restate.RpcContext, - orderId: string, - eta: number -) => { - const currentStatus = await ctx.get(ORDER_STATUS); - ctx.set(ORDER_STATUS, { - eta: eta, - status: eta === 0 ? Status.DELIVERED : currentStatus?.status, - }); -}; - -const handleDriverUpdate = async ( - ctx: restate.RpcContext, - event: restate.Event -) => { - const eta = +event.body().toString(); - const currentStatus = await ctx.get(ORDER_STATUS); - ctx.set(ORDER_STATUS, { - eta: eta, - status: eta === 0 ? Status.DELIVERED : currentStatus?.status, - }); -}; - -export const router = restate.keyedRouter({ - get, - setStatus, - setETA, - eventHandler: restate.keyedEventHandler(handleDriverUpdate), -}); -export type orderStatusApi = typeof router; -export const service: restate.ServiceApi = { - path: "order-status", -}; diff --git a/typescript/food-ordering/app/src/restate-app/order_workflow.ts b/typescript/food-ordering/app/src/restate-app/order_workflow.ts deleted file mode 100644 index 37958774..00000000 --- a/typescript/food-ordering/app/src/restate-app/order_workflow.ts +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (c) 2024 - Restate Software, Inc., Restate GmbH - * - * This file is part of the Restate examples, - * which is released under the MIT license. - * - * You can find a copy of the license in the file LICENSE - * in the root directory of this repository or package or at - * https://github.com/restatedev/examples/ - */ - -import * as restate from "@restatedev/restate-sdk"; -import * as deliveryManager from "./delivery_manager"; -import {Order, Status} from "./types/types"; -import * as orderstatus from "./order_status_service"; -import { completed } from "./utils/utils"; -import {getPaymentClient} from "./clients/payment_client"; -import {getRestaurantClient} from "./clients/restaurant_client"; - -/** - * Order processing workflow Gets called for each Kafka event that is published to the order topic. - * The event contains the order ID and the raw JSON order. The workflow handles the payment, asks - * the restaurant to start the preparation, and triggers the delivery. - */ - -const restaurant = getRestaurantClient(); -const paymentClnt = getPaymentClient(); - -const eventHandler = async (ctx: restate.RpcContext, event: restate.Event) => { - const order = JSON.parse(event.json()); - await create(ctx, event.key, order); -}; - -const create = async ( - ctx: restate.RpcContext, - orderId: string, - order: Order -) => { - // Ignore already completed orders - if (await completed(ctx)) { - return - } - - const { id, totalCost, deliveryDelay } = order; - - // 1. Set status - ctx.send(orderstatus.service).setStatus(id, Status.CREATED); - - // 2. Handle payment - const token = ctx.rand.uuidv4(); - const paid = await ctx.sideEffect(() => paymentClnt.charge(id, token, totalCost)); - - if (!paid) { - ctx.send(orderstatus.service).setStatus(id, Status.REJECTED); - return; - } - - // 3. Schedule preparation - ctx.send(orderstatus.service).setStatus(id, Status.SCHEDULED); - await ctx.sleep(deliveryDelay); - - // 4. Trigger preparation - const preparationPromise = ctx.awakeable(); - await ctx.sideEffect(() => restaurant.prepare(id, preparationPromise.id)); - ctx.send(orderstatus.service).setStatus(id, Status.IN_PREPARATION); - - await preparationPromise.promise; - ctx.send(orderstatus.service).setStatus(id, Status.SCHEDULING_DELIVERY); - - // 5. Find a driver and start delivery - const deliveryId = ctx.rand.uuidv4(); - const deliveryPromise = ctx.awakeable(); - await ctx.rpc(deliveryManager.service).start(deliveryId, {order, promise: deliveryPromise.id}) - await deliveryPromise.promise; - ctx.send(orderstatus.service).setStatus(id, Status.DELIVERED); -}; - -export const router = restate.keyedRouter({ - create, - eventHandler: restate.keyedEventHandler(eventHandler), -}); - -export type orderApi = typeof router; -export const service: restate.ServiceApi = { path: "order-workflow" }; \ No newline at end of file diff --git a/typescript/food-ordering/demo_overview.png b/typescript/food-ordering/demo_overview.png index 9ee4f10f514d91c8999272c6c873d7f7ef7f26b3..9162d8c4df35549bdeaa80540042af40635e7c57 100644 GIT binary patch literal 52488 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 literal 68094 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 diff --git a/typescript/food-ordering/docker-compose.yml b/typescript/food-ordering/docker-compose.yml index d21d0009..990666f2 100644 --- a/typescript/food-ordering/docker-compose.yml +++ b/typescript/food-ordering/docker-compose.yml @@ -33,27 +33,12 @@ services: # blocks until kafka is reachable kafka-topics --bootstrap-server broker:29092 --list echo -e 'Creating kafka topics' - kafka-topics --bootstrap-server broker:29092 --create --if-not-exists --topic orders --replication-factor 1 --partitions 1 kafka-topics --bootstrap-server broker:29092 --create --if-not-exists --topic driver-updates --replication-factor 1 --partitions 1 echo -e 'Successfully created the following topics:' kafka-topics --bootstrap-server broker:29092 --list " - rest-proxy: - image: confluentinc/cp-kafka-rest:7.5.0 - ports: - - 8088:8088 - hostname: rest-proxy - container_name: rest-proxy - environment: - KAFKA_REST_HOST_NAME: rest-proxy - KAFKA_REST_LISTENERS: "http://0.0.0.0:8088" - KAFKA_REST_BOOTSTRAP_SERVERS: "broker:29092" - KAFKA_REST_ACCESS_CONTROL_ALLOW_ORIGIN: "*" - KAFKA_REST_ACCESS_CONTROL_ALLOW_METHODS: "OPTIONS,GET,POST,PUT,DELETE" - KAFKA_REST_ACCESS_CONTROL_ALLOW_HEADERS: "origin,content-type,accept,authorization" - jaeger: image: jaegertracing/all-in-one:1.47 ports: @@ -62,10 +47,21 @@ services: environment: - COLLECTOR_OTLP_ENABLED=true - restate_app: - container_name: restate_app + order_app: + container_name: order_app + build: + context: ./app + dockerfile: Dockerfile-order + environment: + - RESTATE_DEBUG_LOGGING=INVOKE + - RESTAURANT_ENDPOINT=http://restaurant_pos:5050 + - KAFKA_BOOTSTRAP_SERVERS=broker:29092 + + delivery_app: + container_name: delivery_app build: context: ./app + dockerfile: Dockerfile-delivery environment: - RESTATE_DEBUG_LOGGING=INVOKE - RESTAURANT_ENDPOINT=http://restaurant_pos:5050 @@ -82,10 +78,10 @@ services: runtime: image: docker.io/restatedev/restate:0.7 depends_on: - - restate_app + - order_app + - delivery_app - restaurant_pos - broker - - rest-proxy - jaeger ports: - "9070:9070" @@ -101,13 +97,14 @@ services: image: alpine depends_on: - runtime - - restate_app + - order_app + - delivery_app restart: "no" entrypoint: ["sh", "-c", "sleep 5 && apk add --no-cache bash jq curl && - curl -X POST 'runtime:9070/deployments' -H 'content-type: application/json' -d '{\"uri\": \"http://restate_app:9080\"}' && + curl -X POST 'runtime:9070/deployments' -H 'content-type: application/json' -d '{\"uri\": \"http://order_app:9080\"}' && + curl -X POST 'runtime:9070/deployments' -H 'content-type: application/json' -d '{\"uri\": \"http://delivery_app:9081\"}' && sleep 3 && - curl -X POST 'runtime:9070/subscriptions' -H 'content-type: application/json' -d '{ \"source\":\"kafka://my-cluster/orders\", \"sink\":\"service://order-workflow/eventHandler\" }' && curl -X POST 'runtime:9070/subscriptions' -H 'content-type: application/json' -d '{ \"source\":\"kafka://my-cluster/driver-updates\", \"sink\":\"service://driver-digital-twin/handleDriverLocationUpdateEvent\" }' && curl -X POST -H 'content-type: application/json' runtime:8080/driver-mobile-app/startDriver -d '{\"key\": \"driver-01\", \"request\": {} }' && curl -X POST -H 'content-type: application/json' runtime:8080/driver-mobile-app/startDriver -d '{\"key\": \"driver-02\", \"request\": {} }' && diff --git a/typescript/food-ordering/webui/src/components/Cart/Cart.tsx b/typescript/food-ordering/webui/src/components/Cart/Cart.tsx index 7226435e..5264d217 100644 --- a/typescript/food-ordering/webui/src/components/Cart/Cart.tsx +++ b/typescript/food-ordering/webui/src/components/Cart/Cart.tsx @@ -8,8 +8,6 @@ import { useOrderStatusContext } from '../../contexts/status-context/OrderStatus import { useState } from 'react'; import Dropdown from '../Dropdown'; -const isKafkaEnabled = process.env.REACT_APP_ENABLE_KAFKA !== 'false'; - const Cart = () => { const { products, @@ -80,10 +78,6 @@ const Cart = () => { }; }; - const kafkaRecord = JSON.stringify({ - key: user!.user_id, - value: JSON.stringify(generateJsonReq()), - }); const request = JSON.stringify({ key: user!.user_id, request: generateJsonReq(), @@ -94,14 +88,8 @@ const Cart = () => { const checkedOutStatus = { checked_out: true }; updateCartDetails({ ...details, ...checkedOutStatus }); - if (isKafkaEnabled) { - console.info('Generating Kafka record'); - console.info(kafkaRecord); - await publishToKafka(kafkaRecord); - } else { - console.info(request); - sendRequestToRestate('order-workflow', 'create', request); - } + console.info(request); + sendRequestToRestate('order-workflow', 'create', request); let done = false; while (!done) {