Service lifecycle management and discovery over a
seda-bus.
seda-bus moves envelopes between named channels and walks their routing slips;
service-bus gives you services as the unit of composition — register
them, start/stop/pause them, let them find each other, and watch their health.
Each service becomes one seda-bus channel keyed by its name, with the service as
that channel's consumer.
This is a Python port of the design in
service-bus-java.
from seda_bus import Envelope, make_envelope
from service_bus import ServiceBus, BaseService
class EchoService(BaseService):
name = "echo"
def handle(self, env: Envelope) -> bool:
print(env.content())
return True
with ServiceBus() as bus: # start() on enter, graceful_shutdown() on exit
bus.register_and_start_services(EchoService())
bus.await_running(5.0, "echo")
echo = bus.get_service("echo")
transports = bus.find_running_services(MyProtocolService) # by type
net = bus.find_running_services(lambda s: s.name.startswith("net-")) # by predicate
bus.send(make_envelope("echo", "hello"))
bus.send(make_envelope("echo", "hello"), on_complete=lambda e: print("done", e.id))The envelope, ServiceStatus (ra-common's 19-state enum) and ServiceReport
come from ra-common,
as service-bus-java gets them from ra-common-java. BaseService holds a
ra_common.ServiceCore; make_envelope(to, payload, slip=[...]) addresses a
service by name.
from service_bus import Daemon, ServiceBus
class MyDaemon(Daemon):
def config_name(self) -> str:
return "my.config"
def on_bus_started(self, bus: ServiceBus, config: dict[str, str]) -> None:
bus.register_and_start_services(FooService(), BarService())
bus.await_running(10.0)
if __name__ == "__main__":
MyDaemon().launch()launch loads key=value config (file + argv), runs the hooks, installs
SIGINT/SIGTERM handlers, then blocks until the bus stops.
| area | methods |
|---|---|
| register | register(service), register_and_start_service(s), register_and_start_services(*s) |
| lifecycle | start_service, stop_service, start_all_registered, pause/unpause, restart, shutdown/graceful_shutdown |
| discovery | get_service(name), find_running_services(type_or_predicate), running_services(), is_registered/is_running |
| wait | await_running(timeout, *names) |
| health | get_service_status(name), get_service_statuses(), add_service_status_listener |
| bus | status, add_bus_status_listener |
| control | send an Envelope with headers["command"] (start/stop/pause/...) and headers["service"] |
- Threaded start/stop —
start_servicereturns immediately; the service starts on its own thread. Join withawait_running. - Advisory dependencies —
Service.depends_on()is checked at registration (warns); order yourregister_and_start_services(...)call. - UNSTABLE self-healing — a service that reports
ServiceStatus.UNSTABLE(viareport_unstable()/_update_status) is stopped and restarted. - Dead letters — use the underlying seda-bus:
bus.seda_bus.set_dead_letter_channel(source, dlq).
python3.13 -m venv .venv
.venv/bin/pip install -e ../seda-bus-python -e ".[test]"
.venv/bin/python -m pytest