One flat namespace gets crowded. Step 3 groups keys
into named buckets (a map of maps) and adds a
protocol so the store can be exported to a simple
text format without KV knowing every value type.
1
defmoduleKVdodefstructbuckets:%{}defput(%KV{}=kv,bucket,key,value)do%{kv|buckets:Map.update(kv.buckets,bucket,%{key=>value},&Map.put(&1,key,value))}enddefget(%KV{buckets:b},bucket,key),do:b|>Map.get(bucket,%{})|>Map.get(key)defbucket_names(%KV{buckets:b}),do:b|>Map.keys()|>Enum.sort()# Bulk loading is a reduce: fold every row into the# store, starting from an empty one.defload(rows),do:Enum.reduce(rows,%KV{},fn{b,k,v},kv->put(kv,b,k,v)end)end
A protocol is an interface dispatched on the data
type. Anyone can implement it for their own types,
later, without touching KV.
2
defprotocolKV.Codecdo@doc"Encodes a value as a single line of text."defencode(value)enddefimplKV.Codec,for:BitStringdodefencode(s),do:inspect(s)enddefimplKV.Codec,for:Integerdodefencode(n),do:Integer.to_string(n)enddefimplKV.Codec,for:Listdodefencode(list),do:"["<>Enum.map_join(list,",",&KV.Codec.encode/1)<>"]"endrows=[{"users","ada","admin"},{"users","grace","editor"},{"stats","visits",42},{"stats","top",["/","/docs"]}]kv=KV.load(rows)IO.inspect(KV.bucket_names(kv),label:"buckets")IO.inspect(KV.get(kv,"users","grace"),label:"users/grace")IO.inspect(KV.get(kv,"nope","x"),label:"missing bucket")