The KV Store
The snake got me moving, so for the next one I planned a ladder: a key-value store at the bottom, Raft many steps above it.
Maelstrom was the test bench, and the first checkpoint was the hello world of distributed systems: an echo node. Maelstrom spawns my binary, feeds it JSON over stdin, and expects the answer on stdout:
{
"src": "c2",
"dest": "n0",
"body": {
"type": "echo",
"msg_id": 1,
"echo": "Please echo 2"
}
}
{
"src": "n0",
"dest": "c2",
"body": {
"type": "echo_ok",
"in_reply_to": 1,
"echo": "Please echo 2"
}
}
The whole protocol is one struct and one enum, and serde picks the body’s variant off the type field:
#[derive(Serialize, Deserialize, Debug)]
pub struct Message {
#[serde(rename = "src")]
pub source: String,
#[serde(rename = "dest")]
pub destination: String,
#[serde(rename = "body")]
pub body: Body,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "type")]
pub enum Body {
#[serde(rename = "echo")]
EchoRequest { msg_id: u64, echo: Value },
#[serde(rename = "echo_ok")]
EchoResponse { in_reply_to: u64, echo: Value },
}
serde was this project’s VecDeque: most of the time went into learning how it serializes and deserializes, what each attribute in the snippet above actually does. The node itself is a for loop over stdin lines: parse the message, match on the body, print the reply. The second checkpoint was the actual store, a KV CLI on a HashMap with clap on top.
CP2 came out flat: the HashMap living in the domain, the binary doing parse, dispatch, and construction in one file. I rewrote it as a full hexagon knowing it was over-engineering for a hundred-line tool, because I wanted the pattern. One thick port, two driving adapters (the CLI and the Maelstrom protocol), a main of twelve lines that only builds and injects. The behavior did not move: same commands, same greens.
Those twelve lines:
use std::process::ExitCode;
use tokendb::{
adapters::{driven::kv::memory::InMemoryKv, driving::cli::kv::run},
application::kv::KvService,
};
fn main() -> ExitCode {
let mut store = InMemoryKv::new();
let mut service = KvService::new(&mut store);
run(&mut service)
}
The third checkpoint never happened.
I noticed the enthusiasm was not going to survive the serious version of this, and went to study through games instead. So the next project posts will probably be more on the gamedev side.
Discuss on Bluesky →
Comments on Bluesky
No comments yet. Reply on Bluesky and it shows up here.