service-bus · Technical deep dive
Services as the unit of composition, over the same bus, seven times over.
service-bus sits directly on top of 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 own name. Built from one shared design in the same seven languages as seda-bus: Java, Rust,
Python, TypeScript, C++, C#, and Go.
This is reference material, not a pitch — a personal, non-commercial project, same as seda-bus. See the short version on the research page for the framing.
What it is
Register a service; it becomes a channel with a name.
A service implements one method — handle an envelope, return whether it
was processed — and reports a status (starting, running, pausing, stopping, stopped, and
the rest of a roughly 19-state lifecycle shared with the underlying ra-common
port in each language). Registering a service with the bus does two things: it creates a
seda-bus channel named after the service, and it wires the service in as that channel's
consumer. From there, ServiceBus is the discovery surface — look up a
service by name, list what's registered, ask for a health report.
The design traces back to service-bus-java, embedded in the 1M5 project it was
extracted from. Its reflective registration (services are instantiated via
Class.forName(...).getConstructor().newInstance(), so there's no constructor
argument to carry per-instance configuration through) and its BaseService.receive()
always returning true regardless of what the handler did — no ack/nack signal at all from
a document handler — are both original-design limitations. Every other port is a
deliberate departure from at least the second one: a real bool/Task<bool>
return from handle() that the bus actually acts on.
The seven ports
Same design, sitting on each language's own seda-bus port.
Unlike seda-bus, there's no per-language deep-dive page here — the interesting divergence lives one layer down, in each seda-bus implementation, which this layer just sits on top of. What's below is what's specific to the service layer itself in each port.
| Language | Version | What's specific to this port |
|---|---|---|
| Java | 1.5.0 |
The original design — reflective registration, and receive() always acks, both described above. |
| Rust | 0.1.0 |
Two real bugs found and fixed while building the benchmark below against it: a control-command header-parsing call site broken by a seda-bus-rust envelope rewire, and that same rewire leaving the entire test suite, an example, and a doc-comment uncompilable, undetected until then. |
| Python | 0.2.0 |
The same source runs unmodified under both the GIL and free-threaded (PEP 703) CPython builds — the benchmark difference between them, below, is measured on identical code. |
| TypeScript | 0.2.0 |
A real, open gap: ServiceBus.register() always calls seda-bus-ts's subscribe(), which throws for a Worker-thread-backed channel — so the Service abstraction can't yet target real parallelism the way raw seda-bus-ts can. Found, not worked around, while building the benchmark below. |
| C++ | 0.1.0 |
Header-only C++20, same as seda-bus-cpp underneath it. |
| C# | 0.1.0 |
Follows Java's model rather than Rust's — the shared, process-wide .NET ThreadPool. |
| Go | 0.1.0 |
No BaseService — Go's lack of inheritance means there's no template-method class to write; a Service interface plus a ServiceCore struct instead. |
Benchmark snapshot
A real, identical, CPU-bound unit of work: minting a hashcash proof-of-work token.
seda-bus-compare measures the bus in isolation, with a deliberately near-zero-cost
consumer. service-bus-compare
asks a different question: given a real, identical unit of work — minting a
hashcash
proof-of-work token via each language's own ra-common port, at the same fixed
difficulty (16 bits) everywhere — how efficient is each language/runtime, end to end through
its own service-bus? Three configurations: single (one service/channel), ten
(ten independent services/channels, one shared round-robin producer), and ten-mp (the
same ten, but a dedicated producer per channel). Run date 2026-09-14, on a verified-quiet Docker
host, one container at a time — see
the full RESULTS.md
for methodology, every anomaly explained individually, and the real bugs and gaps this benchmark
found in the libraries it was measuring.
| Implementation | single |
ten (1 producer) |
ten-mp (10 producers) |
ten-mp/single |
|---|---|---|---|---|
| Rust | 77.7 | 93.6 | 420.9 | 5.41x |
| Java | 41.3 | 176.9 | 186.8 | 4.53x |
| C++ | 14.2 | 68.9 | 73.1 | 5.16x |
| Go | 15.9 | 57.0 | 56.4 | 3.54x |
| C# | 12.1 | 31.9 | 32.5 | 2.69x |
| Python 3.14t (free-threaded) | 12.0 | 45.8 | 47.7 | 3.98x |
| Python 3.13 (GIL) | 11.5 | 4.5 | 4.3 | 0.37x |
| TypeScript | 6.2 | 5.3 | 16.1 | 2.61x |
Two findings this benchmark settled directly rather than left as a caveat. Rust's flat
ten (93.6, barely above its own single) looked like a real
concurrency limit for a compiled, genuinely multi-threaded language — implausible on its
face. ten-mp isolated the actual cause: the benchmark's own single round-robin
producer, not Rust's real consumer capacity, was the ceiling — with a dedicated producer per
channel, Rust reaches 420.9/sec, the best scaling ratio in the report. Every other language's
ten and ten-mp numbers already agreed closely, ruling the same effect out
for them — except TypeScript, whose flat result had a different cause:
ten-mp originally reused the same in-process event-loop path as single/
ten, because service-bus-ts's Service abstraction can't
target a Worker-thread-backed channel (see the table above). Rewiring ten-mp to drive
seda-bus-ts's real WorkerTransport directly — real OS threads, bypassing
ServiceBus for that one configuration — produced the corrected number above
(2.61x), landing inside the same range every other language shows instead of standing outside it.
Source
Seven repositories, one design document, one benchmark.
Each language's implementation lives in its own repository. The shared design document and the cross-language benchmark are separate repositories that reference all seven.
- service-bus-design — the shared design document
- service-bus-compare — the real-workload benchmark, methodology, and full results
- service-bus-java
- service-bus-rust
- service-bus-python
- service-bus-ts
- service-bus-cpp
- service-bus-cs
- service-bus-go