-
Notifications
You must be signed in to change notification settings - Fork 5
Architecture
Mnemonix is, at its core, a traditional but very rich GenServer client/server interface.
The Mnemonix Client API functions convert any conceivable key/value store operation into a Mnemonix.Store.Server
call. The server dispatches this instruction to its underlying store implementation and responds with the result; depending on the format of this response the client can either return successfully, return while issuing a warning, or raise an exception.
Forcing every client command to go through a synchronous GenServer call, even when not strictly necessary, ensures that every operation upon a store issued through the Mnemonix API is functionally atomic.
The Mnemonix Client API is actually implemented in a series of discrete modules called 'features'. Each feature groups together conceptually related functions. We perform this grouping for a couple of reasons:
- It provides a place to document general usage of each feature-set in its module docs
- It allows for drilling down into function documentation for a specific set of operations rather than having to exclusively peruse the rather wide client API
- It allows for building custom client APIs that only expose some of the full breadth of Mnemonix by cherrypicking certain feature-sets
The core value proposition of Mnemonix is to allow you to use the Map API on any key/value store. This is where these map-parity functions go.
To ensure we keep this promise, we actively test that (with a few exceptions) we are offering an identical API.
Atomically incrementing and decrementing integer values is a common use-case for key/value stores, and many offer a dedicated API for these operations, so Mnemonix does as well.
Another common desire is to instrument entries within a key/value store with a ttl (time-to-live), after which they remove themselves from the store. This is especially useful for stores being used as expiring caches. Mnemonix supports setting store-wide ttls as well as per-entry ones for any kind of store.
While not always advisable, it is often desirable to enumerate over all entries within a key/value store. Operations that do so, including ones that would otherwise belong in the Map feature, are contained here.
Not every store supports enumeration; unless configured to do so the store server will instruct the client to raise a Mnemonix.Features.Enumerable.Exception
at the call site of the enumerable feature function. The store can be tested at runtime for enumeration support via Mnemonix.enumerable?/1
.
All instructions sent to the server take the form of a tuple representing the feature function being invoked, with all arguments (minus the leading store server parameter itself). So Mnemonix.put_and_expire(store, key, value, ttl)
becomes GenServer.call(store, {:put_and_expire, key, value, ttl})
.
Calls to the server that represent protocol implementation function invocations use a two-tuple of the protocol name and implementation function in lieu of a feature function name, such as GenServer.call(store, {{Access, :get}, key, default})
.
The server interprets this as a call to the underlying store implementation. It performs the requested operation and updates the server's internal state if necessary. It then re-interprets the result of the operation with an instruction to the client to either return, warn and return, or raise. These replies take one of the formats:
-
success:
:ok
The operation was successful and provided no useful return value. Normally the client handles this by returning the store reference itself, but sometimes returns
:ok
instead. -
success and return:
{:ok, value}
The operation was successful and the value is returned in the client.
-
warn:
{:warn, message}
The operation was successful but should emit a warning. The operation produced no useful return value.
-
warn:
{:warn, message, value}
The operation was successful but should emit a warning. The client should return the provided value.
-
error:
{:raise, exception_module, args}
The operation failed. The client should raise an exception of the given type using the provided args.