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.

Mints/sec, mean of 3 trials, 16-bit difficulty, 10s publish window
Implementation single ten (1 producer) ten-mp (10 producers) ten-mp/single
Rust77.793.6420.95.41x
Java41.3176.9186.84.53x
C++14.268.973.15.16x
Go15.957.056.43.54x
C#12.131.932.52.69x
Python 3.14t (free-threaded)12.045.847.73.98x
Python 3.13 (GIL)11.54.54.30.37x
TypeScript6.25.316.12.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.