(2) Tutorial
Build your own "Wolt" with Memphis

Building a distributed message-based food delivery application

With Memphis, we can develop a system that requires asynchronous communication to process various tasks in the backend. More precisely, in this case, we will build a distributed message-based groceries delivery application using Node.js, Express, MongoDB, Memphis, and Kubernetes.
We’ll also be injecting faults in the system to examine the resilience it offers.
Many thanks to Dhanush Kamath for the use case and supporting article.
Image-1 (source: neiljbrown.com)

High-Level Plan

  1. 1.
    Install Minikube using brew package manager
  2. 2.
    Install Memphis over Minikube
  3. 3.
    Clone "Fastmart" GitHub repo
  4. 4.
    Review the code, the different services, and how they interact with each other
  5. 5.
    Deploy "Fastmart" over Kubernetes
  6. 6.
    Order food!

Let's start!

The best groceries delivery platform out there!
The application that we’ll be building in this tutorial is called FastMart. FastMart is a food delivery platform that allows users to order different food and track their delivery in real-time.

1. Install Minikube

For the installation commands, please head here: https://minikube.sigs.k8s.io/docs/start/
minikube is local Kubernetes, focusing on making it easy to learn and develop for Kubernetes.
All you need is Docker (or similarly compatible) container or a Virtual Machine environment, and Kubernetes is a single command away: minikube start
What you’ll need
Output -
1
kubectl get ns
Copied!
Output -
1
NAME STATUS AGE
2
default Active 31h
3
kube-node-lease Active 31h
4
kube-public Active 31h
5
kube-system Active 31h
Copied!

2. Install Memphis

1
helm repo add memphis https://k8s.memphis.dev/charts/ &&
2
helm install memphis memphis/memphis --set connectionToken="memphis" --create-namespace --namespace memphis
Copied!
Let's wait a minute or two, allowing the different components to reach "Running" state
1
kubectl get pods -n memphis
Copied!
Output -
1
NAME READY STATUS RESTARTS AGE
2
k8s-busybox-68867bb9b7-sqdql 0/1 CrashLoopBackOff 4 (68s ago) 3m13s
3
memphis-broker-0 3/3 Running 4 (55s ago) 3m13s
4
memphis-ui-fd54f5bd6-zzqd4 0/1 CrashLoopBackOff 4 (79s ago) 3m13s
5
mongodb-replica-0 1/1 Running 0 3m13s
Copied!
1
NAME READY STATUS RESTARTS AGE
2
k8s-busybox-68867bb9b7-sqdql 0/1 CrashLoopBackOff 4 (76s ago) 3m21s
3
memphis-broker-0 3/3 Running 4 (63s ago) 3m21s
4
memphis-ui-fd54f5bd6-zzqd4 1/1 Running 5 (87s ago) 3m21s
5
mongodb-replica-0 1/1 Running 0 3m21s
Copied!
k8s-busybox can be ignored. It will be fixed in the coming versions

3. Clone the Fastmart repo

1
git clone https://github.com/yanivbh1/FastMart.git
Copied!

4. System Architecture, Code, and Flow

Follow the numbers to understand the flow
FastMart has three main components:
order-service - Exposes REST endpoints that allow clients to fetch the food menu, place an order and track the order in real-time.
A new order will be saved in mongo with the status "Pending" and will be produced (Pushed) into "orders" station
1
GET: /<orderId>
2
Example: curl http://order-service:3000/30
3
4
POST: /<order_details>
5
Example: curl -X POST http://order-service:3000/api/orders -d '{"items":[{"name":"burger","quantity":1}], "email":"[email protected]"}' -H 'Content-Type: application/json'
Copied!
The code responsible for communicating with Memphis will be found on -
./order-service/src/services/mqService.js
1
const memphis = require("memphis-dev");
2
const { logger } = require('./loggerService')
3
const MEMPHIS_HOST = process.env.MEMPHIS_HOST || 'localhost'; // create MQ connection string using environment variable
4
const MEMPHIS_USERNAME = process.env.MEMPHIS_USERNAME;
5
const MEMPHIS_TOKEN = process.env.MEMPHIS_TOKEN;
6
let ordersStation_producer = null;
7
8
const memphisConnect = async () => {
9
try {
10
logger.info(`Memphis - trying to connect`)
11
await memphis.connect({
12
host: MEMPHIS_HOST,
13
username: MEMPHIS_USERNAME,
14
connectionToken: MEMPHIS_TOKEN
15
});
16
logger.info(`Memphis - connection established`)
17
18
ordersStation_producer = await memphis.producer({
19
stationName: "orders",
20
producerName: "order_service",
21
});
22
logger.info(`ordersStation_producer created`)
23
} catch(ex) {
24
logger.log('fatal',`Memphis - ${ex}`);
25
memphis.close();
26
process.exit();
27
}
28
}
29
30
/**
31
* Publish order to station
32
* @param {Object} order - order object containing order details
33
*/
34
const publishOrderToStation = (order) => {
35
ordersStation_producer.produce({message: Buffer.from(JSON.stringify(order))});
36
logger.info(`Memphis - order ${order._id} placed`);
37
}
38
39
/**
40
* An express middleware for injecting queue services into the request object.
41
* @param {Object} req - express request object.
42
* @param {Object} res - express response object.
43
* @param {Function} next - express next() function.
44
*/
45
46
const injectPublishService = (req, res, next) => {
47
// add all exchange operations here
48
const stationServices = {
49
publishOrderToStation: publishOrderToStation
50
}
51
// inject exchangeServices in request object
52
req.stationServices = stationServices;
53
next();
54
}
55
56
module.exports = {
57
injectPublishService: injectPublishService,
58
memphisConnect: memphisConnect,
59
}
60
Copied!
email-service - Responsible for notifying the client of the different stages.
email-service consumer messages from two stations: orders and notifications.
As soon as an order is inserted into the station, the email service notifies the client with an order confirmation.
At the same time listens for new notification requests of other services
resturant-service - Responsible for fulfilling an order.
  1. 1.
    Consume an order
  2. 2.
    Process the order -
    1. 1.
      Change order status at the MongoDB level to "Accepted"
    2. 2.
      Using constant sleep time to mimic the preparation of the food by the restaurant
    3. 3.
      Change order status at the MongoDB level to "Delivered"
    4. 4.
      Sending notification to the client

5. Deploy "Fastmart" over Kubernetes

Fastmart repo tree -
Fastmart's files tree
To deploy Fastmart namespace and different services,
please run kubectl apply -f k8s-deployment.yaml
1
kubectl get pods -n fastmart
Copied!
Output -
1
NAME READY STATUS RESTARTS AGE
2
email-service-5ddb9b58d6-bq2xd 0/1 CrashLoopBackOff 3 103s
3
fastmart-ui-5c9bc497bd-kn4lk 1/1 Running 0 11m
4
orders-service-5b689b66-4h8t9 0/1 CrashLoopBackOff 7 11m
5
resturant-service-6d97cf6fdc-c9mvs 0/1 Completed 4 103s
Copied!
Let's understand why Fastmart services cant start
1
kubectl logs email-service-5ddb9b58d6-bq2xd -n fastmart
Copied!
Output -
2
> node ./index.js
3
4
17-05-2022 07:10:09 PM - info: Sleeping for 300ms before connecting to Memphis.
5
17-05-2022 07:10:09 PM - info: Memphis - trying to connect
6
17-05-2022 07:10:09 PM - info: email-service started
7
17-05-2022 07:10:09 PM - fatal: Memphis - User is not exist
Copied!
It appears that the services try to connect to "Memphis" with the user "fastmart" which does not exist and we require to create it.
The simplest way to add a new user would be through the UI, but let's do it via CLI.
Please install Memphis CLI via here.
1
$ mem
2
Usage: index <command> [options]
3
4
Options:
5
-V, --version output the version number
6
-h, --help display help for command
7
8
Commands:
9
connect Connection to Memphis
10
factory Factories usage commands
11
station Stations usage commands
12
user Users usage commands
13
producer Producers usage commands
14
consumer Consumer usage commands
15
init Creates an example project for working with Memphis
16
help display help for command
17
18
Factory is the place to bind stations that have some close business logic
19
Factory Commands:
20
ls List of factories
21
create Create new factory
22
edit Edit factory name and/or description
23
del Delete a factory
24
25
Station is Memphis' queue/topic/channel/subject
26
Station Commands:
27
ls List of stations
28
create Create new station
29
info Specific station's info
30
del Delete a station
31
32
Manage users and permissions
33
User Commands:
34
ls List of users
35
add Add new user
36
del Delete user
37
38
Producer is the entity who can send messages into stations
39
Producer Commands:
40
ls List of Producers
41
42
Consumer is the entity who can consume messages from stations
43
Consumer Commands:
44
ls List of Consumers
Copied!
To connect the CLI with Memphis control plane we need -
  • root password
1
kubectl get secret memphis-creds -n memphis -o jsonpath="{.data.ROOT_PASSWORD}" | base64 --decode
2
3
OqEO9AbncKFF93r9Qd5V
Copied!
  • memphis control-plane url
1
kubectl port-forward service/memphis-cluster 7766:7766 6666:6666 5555:5555 --namespace memphis > /dev/null &
Copied!
Now, connect the CLI
1
mem connect --user root --password bpdASQlhwWNzFt4JwLQo --server localhost:5555
Copied!
Add the user "fastmart"
1
mem user add -u fastmart --type application
Copied!
Or via the UI
Soon after we will create the user,
the pods will restart automatically and reconnect with Memphis.

6. Order food!

To expose the orders-service through localhost, run
1
kubectl port-forward service/orders 9001:80 --namespace fastmart > /dev/null &
Copied!
Get the menu
1
curl localhost:9001/api/menu
2
3
Output -
4
{"items":[{"name":"burger","price":50},{"name":"fries","price":20},{"name":"coke","price":10}]}
Copied!
Make an order
1
curl -X POST localhost:9001/api/orders -d '{"items":[{"name":"burger","quantity":1}], "email":"[email protected]"}' -H 'Content-Type: application/json'
Copied!
An email should arrive shortly at the email address specified above.
Thanks!