Messaging#
When running in emulation mode, ECLYPSE allows services to communicate directly with one another using structured EclypseCommunicationInterface.
Two communication models are currently supported:
an MPI-like interface (
EclypseMPI), designed for low-latency message passinga REST-style interface (
EclypseREST), designed for loosely coupled HTTP interactions
These APIs can be used from within emulated services to exchange information, coordinate behaviour, or simulate service-level protocols.
MPI-style messaging#
The MPI interface provides basic primitives to send and receive messages among services deployed in the same application.
Each Service has access to an mpi attribute (an instance of the internal communication layer), through which it can:
send messages to specific services (
send())broadcast messages to all neighbours (
bcast())receive incoming messages (
recv())
All methods are asynchronous and simulate network cost using the placement and topology of the infrastructure. The send and broadcast requests must be awaited explicitly.
""" ... inside the step() method of service-A """
# Send to one neighbour
await self.mpi.send("service-B", {"msg": "ping"})
# Broadcast to all neighbours
await self.mpi.bcast({"msg": "update"})
# Receive next message
message = await self.mpi.recv()
@exchange() decorator#
In addition to direct calls, the
EclypseMPI interface
provides a decorator
(@exchange())
to define communication behaviour declaratively inside service methods.
You can decorate a method to:
receive a message before it runs (receive=True)
send the return value to a target (send=True)
broadcast the return value to all neighbours (broadcast=True)
Sending and broadcasting are mutually exclusive.
exchange() decorator#from eclypse.remote.service import Service
from eclypse.remote.communication.mpi import exchange
class EchoService(Service):
@exchange(receive=True, send=True)
async def step(self, sender_id, message):
reply = {"msg": f"Echo: {message['msg']}"}
return sender_id, reply
REST-style Messaging#
ECLYPSE also provides a REST-style communication interface for services. This mode models service interaction using HTTP-like semantics and is better suited for stateless, loosely coupled communication patterns.
To use this style, your service must subclass
RESTService.
@register_endpoint() decorator#
You define REST endpoints inside your service by decorating methods with
@register_endpoint().
Each endpoint:
is associated with a URL pattern and an HTTP method (GET, POST, PUT, DELETE)
receives the request data as keyword arguments
must return a tuple: (HTTPStatusCode, response_dict)
Example:
from eclypse.remote.communication.rest.codes import HTTPStatusCode
from eclypse.remote.communication.rest.interface import register_endpoint
from eclypse.remote.service import RESTService
class StoreService(RESTService):
def __init__(self, service_id: str):
super().__init__(service_id)
self.store = {}
@register_endpoint("/data", method="POST")
async def store_data(self, key: str, value: Any):
self.store[key] = value
return HTTPStatusCode.CREATED, {"status": "ok"}
@register_endpoint("/data", method="GET")
def get_data(self, key: str):
value = self.store.get(key, None)
return HTTPStatusCode.OK, {"value": value}
Making Requests#
From within a service, you can send requests using:
""" ... inside the step() method of service-A """
await self.rest.post("StoreService/data", key="item1", value=42)
Each method returns an HTTP request object. When awaited, the request is executed and the object exposes:
status_codebodyresponseroute
For example:
request = await self.rest.post("StoreService/data", key="item1", value=42) print(request.status_code) print(request.body)
—
Endpoint Resolution#
When a REST service starts, it scans all decorated methods using
@register_endpoint()
and registers them dynamically. Endpoint URLs are scoped per service ID: the
final URL is prefixed by the service name.
For example, if a service s1 exposes /data, the full URL is: s1/data.
Routing and delivery are managed by the infrastructure runtime.