embedded_cluster3
Datastore, driven directly through the in-process request
injector. A test scaffold for hook implementations.
Source:
crates/dynomite/examples/embedded_cluster3.rs --
run with cargo run -p dynomite --example embedded_cluster3
What it demonstrates
Three things at once: building more than one node in a process, supplying
your own Datastore hook, and driving requests without a socket via
inject_request.
The custom datastore is a shared in-memory map that all three nodes write through:
#![allow(unused)] fn main() { #[derive(Default, Clone)] struct SharedKv { inner: Arc<Mutex<std::collections::HashMap<u64, MsgType>>>, } impl Datastore for SharedKv { fn protocol(&self) -> Protocol { Protocol::Custom } fn dispatch(&self, req: Msg) -> BoxFuture<'_, Result<Msg, DatastoreError>> { let inner = self.inner.clone(); Box::pin(async move { let mut g = inner.lock(); if matches!(req.ty(), MsgType::ReqRedisSet) { g.insert(req.id(), MsgType::RspRedisStatus); } let stored = g.get(&req.id()).copied(); drop(g); let mut rsp = Msg::new(req.id(), stored.unwrap_or(MsgType::RspRedisStatus), false); rsp.set_parent_id(req.id()); Ok(rsp) }) } } }
Each node is built with a distinct rack and token so the three of them tile the ring, then a write is injected at node 0 and reads at nodes 1 and 2:
#![allow(unused)] fn main() { let mut w = Msg::new(7, MsgType::ReqRedisSet, true); w.set_parent_id(0); let _ = n0.inject_request(w).await?; for (label, h) in [("n1", &n1), ("n2", &n2)] { let req = Msg::new(7, MsgType::ReqRedisGet, true); let rsp = h.inject_request(req).await?; eprintln!("{label}: rsp ty={:?} parent={}", rsp.ty(), rsp.parent_id()); } }
Design decisions and trade-offs
- Shared in-memory
Datastore - One
SharedKvbehind all three nodes lets the example show routing and response flow without three real backends. It is a test double, not a replication story -- the nodes are not actually replicating to independent stores. inject_requestover sockets- Requests go straight into the engine, skipping the client listener. That makes the example deterministic and fast and is exactly how you would unit-test a hook. Real clients still connect over the socket.
- Gossip disabled, seeds empty
- The topology is wired statically by giving each node its rack and token directly. This keeps the example reproducible; a live cluster would enable gossip and share seeds.
multi_threadruntime- Three nodes with concurrent injected traffic want real parallelism, unlike the single-node examples.
A truly independent three-node cluster runs three processes with three backends and real gossip -- that is what the integration and conformance suites do. This example collapses it into one process on purpose: it exists to exercise the hook surface under multi-node routing, where a process boundary would add noise without teaching anything new.
Because the three nodes share one in-memory map and use
inject_request, do not read this example as a template for a
production cluster. For that, see
Your First Cluster,
which runs separate dynomited processes.
When to use this pattern
When you are implementing a Datastore, MetricsSink, or other hook and
want to test it under multi-node routing in a single fast, deterministic
process.
Where to go next
- Hooks and Traits for the full trait surface
the
SharedKvhere implements. - Your First Cluster for a real multi-process cluster.