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:

  1. 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.

  2. 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 common recv/send patterns.

    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.send directly, specifying the recipient and the message body.

  • recv waits 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.