SockShop#
SockShop is a microservices-based e-commerce application that simulates an online sock store. It is composed of multiple services responsible for catalog browsing, user management, cart updates, payment, shipping, and order handling.
ECLYPSE provides two versions of the application that share the same abstract application model and infrastructure, but differ in the communication interface used by the services:
MPI: this version uses the Message Passing Interface (MPI) for communication between services. MPI is a high-performance communication protocol that is well-suited for distributed computing environments, enabling efficient message passing and synchronization between services.
REST: this version uses Representational State Transfer (REST) for communication between services. RESTful APIs promote simplicity and standardization, making it easier to develop, maintain, and integrate services within the microservices architecture.
Below, we focus on the FrontendService implementation in both variants,
because it highlights the practical differences between MPI and REST
communication in ECLYPSE.
The full code lives in the examples/sock_shop/mpi.py and examples/sock_shop/rest.py directories.
Run either variant from the repository root with:
uv run sock-shop-mpi
uv run sock-shop-rest
Warning
Both interfaces are asynchronous. When you call the low-level request APIs directly, await the returned request object so that route and delivery failures surface immediately.
FrontendService (MPI version)#
1"""The `FrontendService` class.
2
3It serves as the user interface for the SockShop application,
4providing the user-facing components of the store.
5
6- Key Responsibilities:
7 - Displays product catalogs, shopping carts, and order information to users.
8 - Interacts with backend services (e.g., `CatalogService`,
9 `UserService`, `OrderService`) to display real-time data.
10 - Manages user input and interactions such as product searches,
11 cart updates, and order placements.
12"""
13
14from eclypse.remote.communication import mpi
15from eclypse.remote.service import Service
16from eclypse.utils import format_log_kv
17
18
19class FrontendService(Service):
20 """MPI workflow of the Frontend service."""
21
22 def __init__(self, name, store_step: bool = False):
23 """Initialize the FrontendService with a user ID.
24
25 Args:
26 name (str): The name of the service.
27 store_step (bool, optional): Whether to store the results of
28 each step. Defaults to False.
29 """
30 super().__init__(name, store_step=store_step)
31 self.user_id = 12345
32
33 async def step(self):
34 """Example workflow of the `Frontend` service.
35
36 It starts with fetching the catalog, user data, and cart items,
37 then placing an order.
38 """
39 # Send request to CatalogService
40 await self.catalog_request()
41
42 # Receive response from CatalogService
43 catalog_response = await self.mpi.recv()
44
45 self.logger.info(
46 "Received response | "
47 + format_log_kv(source="CatalogService", body=catalog_response)
48 )
49
50 # Send request to UserService
51 user_request = {"request_type": "user_data", "user_id": self.user_id}
52 self.mpi.send("UserService", user_request)
53
54 # Receive response from UserService
55 user_response = await self.mpi.recv()
56 self.logger.info(
57 "Received response | "
58 + format_log_kv(source="UserService", body=user_response)
59 )
60
61 # Send request to CartService
62 cart_request = {"request_type": "cart_data", "user_id": self.user_id}
63 self.mpi.send("CartService", cart_request)
64
65 # Receive response from CartService
66 cart_response = await self.mpi.recv()
67 self.logger.info(
68 "Received response | "
69 + format_log_kv(source="CartService", body=cart_response)
70 )
71
72 products = catalog_response.get("products", [])
73 cart_items = cart_response.get("items", [])
74 order_items = [
75 {
76 "id": item["id"],
77 "amount": next(
78 (
79 product["price"] * item["quantity"]
80 for product in products
81 if product["id"] == item["id"]
82 ),
83 0.0,
84 ),
85 }
86 for item in cart_items
87 ]
88
89 # Send request to OrderService
90 order_request = {
91 "request_type": "order_request",
92 "user_id": self.user_id,
93 "items": order_items,
94 }
95 self.mpi.send("OrderService", order_request)
96
97 # Receive response from OrderService
98 order_response = await self.mpi.recv()
99 self.logger.info(
100 "Received response | "
101 + format_log_kv(source="OrderService", body=order_response)
102 )
103
104 @mpi.exchange(send=True)
105 def catalog_request(self):
106 """Send a request to the CatalogService for product data.
107
108 Returns:
109 str: The recipient service name.
110 dict: The request body.
111 """
112 return "CatalogService", {"request_type": "catalog_data"}
The MPI interface revolves around the methods send, bcast, and
recv. It is useful when you want to model explicit message passing between
services.
We can notice two ways of sending a message:
At lines 64 – 72 we use the
exchange()decorator. This is convenient for commonrecv/sendpatterns.A complete example of such a pattern is the following, where the service receives a message and sends a response to the sender:
@mpi.exchange(receive=True, send=True) def my_request(self, sender_id, body): response = ... # process the body return sender_id, response
At lines 36, 44, and 58 we use
mpi.senddirectly, specifying the recipient and the message body.recvwaits for the next message in the internal queue.
FrontendService (REST version)#
1"""The `FrontendService` class.
2
3It serves as the user interface for the SockShop application,
4providing the user-facing components of the store.
5
6- Key Responsibilities:
7 - Displays product catalogs, shopping carts, and order information to users.
8 - Interacts with backend services (e.g., `CatalogService`,
9 `UserService`, `OrderService`) to display real-time data.
10 - Manages user input and interactions such as product searches,
11 cart updates, and order placements.
12"""
13
14from eclypse.remote.service import Service
15from eclypse.utils import format_log_kv
16
17
18class FrontendService(Service):
19 """Example workflow of the Frontend service."""
20
21 def __init__(self, name, store_step: bool = False):
22 """Initialise the Frontend service with the REST interface."""
23 super().__init__(
24 name,
25 communication_interface="rest",
26 store_step=store_step,
27 )
28 self.user_id = 12345
29
30 async def step(self):
31 """Example workflow of the `Frontend` service.
32
33 It starts with fetching the catalog, user data, and cart items,
34 then placing an order.
35 """
36 catalog_r = await self.rest.get("CatalogService/catalog")
37 user_r = await self.rest.get("UserService/user", user_id=self.user_id)
38 cart_r = await self.rest.get("CartService/cart")
39
40 self.logger.info(
41 "Received response | "
42 + format_log_kv(source="CatalogService", body=catalog_r.body)
43 )
44 self.logger.info(
45 "Received response | "
46 + format_log_kv(source="UserService", body=user_r.body)
47 )
48 self.logger.info(
49 "Received response | "
50 + format_log_kv(source="CartService", body=cart_r.body)
51 )
52
53 products = catalog_r.body.get("products", [])
54 items = cart_r.body.get("items", [])
55
56 order_items = [
57 {
58 "id": item["id"],
59 "amount": next(
60 (
61 product["price"] * item["quantity"]
62 for product in products
63 if product["id"] == item["id"]
64 ),
65 None,
66 ),
67 }
68 for item in items
69 ]
70
71 order_r = await self.rest.post("OrderService/order", items=order_items)
72 self.logger.info(
73 "Received response | "
74 + format_log_kv(source="OrderService", body=order_r.body)
75 )
In this example, the REST interface uses endpoint-oriented methods such as
get and post. This style is a better fit when you want to model
request/response interactions explicitly.