This example application implements an order processing middleware which sits between third-party food ordering providers and restaurants. Food ordering providers interact with the application by publishing events to Kafka. For each event, Restate triggers the order workflow. The order workflow interacts with the restaurants' external point of sales system to request the preparation of the orders. It also interacts with the delivery services to get the order delivered to the customer once preparation is done.
-
Via the CLI:
restate example kotlin-food-ordering && cd kotlin-food-ordering
-
Via git clone:
git clone [email protected]:restatedev/examples.git cd examples/end-to-end-applications/kotlin/food-ordering
-
Via
wget
:wget https://github.com/restatedev/examples/releases/latest/download/kotlin-food-ordering.zip && unzip kotlin-food-ordering.zip -d kotlin-food-ordering && rm kotlin-food-ordering.zip
Build the docker containers:
cd app
./gradlew build jibDockerBuild
Launch the Docker compose setup:
docker compose up
WebUI is running at http://localhost:3000
Jaeger is running at http://localhost:16686
When you are making changes to the code, and you want to trigger a build of the Docker images:
docker compose build --no-cache
Clean up after bringing setup down:
docker compose rm
Restate has a psql interface to query the state of the system.
If you buy some products via the webUI, you can see how the order workflow is executed by querying the state of the order status service:
watch -n 1 'psql -h localhost -p 9071 -c "select component, component_key, key, value_utf8 from state s where s.component='"'"'OrderStatusService'"'"';"'
Or have a look at the state of all the services, except for the driver simulator:
watch -n 1 'psql -h localhost -p 9071 -c "select component, component_key, key, value_utf8 from state s where s.component not in ('"'"'DriverMobileAppSimulator'"'"');"'
Or you can check the state of the ongoing invocations via:
watch -n 1 'psql -h localhost -p 9071 -c "select component, handler, component_key, id, status, invoked_by_component, invoked_by_id from sys_invocation_status;"'
You can find the implementation of each of the services under app/restate-app/src/main/kotlin/dev/restate/sdk/examples/
.
The flow of an incoming order is as follows:
- When the customer places an order via the web UI (localhost:3000), an order event is published to Kafka.
- 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 theruntimesetup
container. - The order workflow is implemented in
OrderWorkflow.kt
and consists of the following steps:- When the order workflow is triggered, it first parses the raw Kafka event and extracts the order details.
- It then calls the
OrderStatusService
to create a new order in the system. TheOrderStatusService
is a virtual object which tracks the status of each order by storing it in Restate's key-value store. - 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 a stable idempotency token provided by the SDK, and then uses this to call the payment provider. The payment provider can deduplicate retries via the idempotency key.
- 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. - 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 wrapped in
ctx.run
to log the result in Restate. The status of the order is set toIN_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 toSCHEDULING_DELIVERY
. - Finally, the order workflow calls the delivery manager (
DeliveryManager.kt
) 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 toDELIVERED
.
To get the order delivered a set of services work together. The delivery manager (start
handler in DeliveryManager.kt
) 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 DriverDeliveryMatcher. The DriverDeliveryMatcher 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 (DriverDigitalTwin.kt
) is the digital representation of a driver in the field. Each driver has a mobile app on his phone (here simulated by external/DriverMobileAppSimulator.kt
) which continuously sends updates to the digital twin of the driver:
- The driver can notify when they start working: have a look at
DriverMobileAppSimulator/StartDriver
which callsDriverDigitalTwin/SetDriverAvailable
. - The mobile app also polls the digital twin to check if a new delivery was assigned to the driver. Have a look at
DriverMobileAppSimulator/PollForWork
which regularly callsDriverDigitalTwin/GetAssignedDelivery
. - During delivery, the mobile app sends regular location updates over Kafka to the digital twin of the driver. Have a look at the handler
DriverDigitalTwin/HandleDriverLocationUpdateEvent
. - Once the driver has arrived at the restaurant, the driver's mobile app notifies its digital twin (by calling
DriverDigitalTwin/NotifyDeliveryPickup
). The digital twin then notifies the delivery manager that the driver has picked up the delivery (by callingDeliveryManager/NotifyDeliveryPickup
). - Finally, the driver arrives at the customer and the driver's mobile app notifies its digital twin (by calling
DriverDigitalTwin/NotifyDeliveryDelivered
). The digital twin then notifies the delivery manager that the driver has picked up the delivery (by callingDeliveryManager/NotifyDeliveryDelivered
). - The delivery manager then sets the order status to
DELIVERED
. And the order workflow gets completed, by resolving the awakeable.
The implementation of the web app is based on the MIT Licensed repository here: https://github.com/jeffersonRibeiro/react-shopping-cart.
Upgrade the Restate SDK dependencies for the app
.
Then run the example via Docker compose.
The Docker Compose setup uses the latest Restate runtime version. Test run the example via Docker compose.