A port of service-bus-java's
design onto seda-bus-python.
Same model, idiomatic to Python.
seda-bus is a transport: named channels, a shared ThreadPoolExecutor, bounded
queues, the routing-slip engine. It has no notion of a "service".
service-bus adds that notion and the management that comes with it:
- composition — a
Serviceis the unit; each becomes one channel + its consumer; - lifecycle — register, start, stop, pause, restart, per service and for the whole bus;
- discovery — look services up by name, by type, or by predicate;
- health — per-service
ServiceStatustracked, published to listeners, and anUNSTABLEservice stopped and restarted; - remote control — an
Envelopecarryingheaders["command"]acts on a service; - a reusable
Daemonhost.
ServiceBus
_seda SEDABus (created, or supplied)
_registered name -> Service
_running name -> Service
_statuses name -> ServiceStatus
_svc_listeners / _bus_listeners
register(service):
bind a ServiceContext (config + send) and a status observer onto the service
_seda.channel(service.name)
_seda.subscribe(service.name, service.handle)
start_service(name): threading.Thread -> service.start() -> _running[name] = service
send(env): if headers["command"] in CONTROL_COMMANDS -> _process_command;
_seda.publish(env)
"name" is the registration key, the channel name, and the value a routing slip
carries (its DynamicRoutingSlip, keyed by route.service).
| java | here | reason |
|---|---|---|
reflective Class.forName registration |
pass a Service instance |
the instance carries its own name |
| dependency-ordered auto-registration | depends_on() is advisory (warns) |
can't build an unknown dependency without a factory; caller orders register_and_start_services |
AppThread per start/stop |
threading.Thread per start/stop |
same "async, join later" shape; await_running joins |
findRunningServices(Class) |
find_running_services(type_or_predicate) |
Python takes a class or a callable |
ControlCommand enum on commandPath |
headers["command"] + headers["service"] |
ergonomic string headers rather than a typed command path |
PersistDeadLetter file with rotation |
seda.set_dead_letter_channel(source, dlq) |
seda-bus-python already models a dead-letter channel |
BaseService from ra-common-java |
BaseService holds a ra_common.ServiceCore |
ra-common-python's Service uses start(properties) / shutdown(); service-bus keeps its no-arg start() / stop() and delegates status/report to ServiceCore |
ServiceStatus and ServiceReport are ra-common's (the 19-state enum, same as
service-bus-java); the envelope is ra_common.Envelope, and its routing slip
is LIFO and keyed by route.service.
start_service / stop_service run service.start() / .stop() on a daemon
threading.Thread (services may block on network or disk). await_running(timeout, *names) polls _running until the targets appear. On a free-threaded (PEP 703)
build the services' handle work also runs in parallel across the seda-bus pool.
State maps are plain dicts guarded by an RLock on the registration path; reads
(is_running, get_service) are lock-free snapshots.
A BaseService calls _update_status(status), which reaches
ServiceBus._on_service_status(name, status). The bus records it, forwards it to
every service-status listener, and on ServiceStatus.UNSTABLE spawns a thread that
stops and restarts the service.
Daemon has config_name / load_config / before_start / on_bus_started /
on_stopping hooks and a launch(argv) that loads config, runs the hooks, installs
SIGINT/SIGTERM handlers, and blocks on a threading.Event until shutdown().
- No priority between services (that is seda-bus per-stage config).
- No distributed registry — one process, one bus.
- No hot reload — restart re-runs
start()on the same instance.