A simple & light-weight simulation of Redis, which is part of a company assignment. Deployed at https://spring-bud-betta-veil.cyclic.app/.
This README file is meant to explain the components of the system and how to extend it.
One of the advantages of this design is that the system can be extended with new commands with minimal/trivial modification to the source code.
Here's a class diagram of the system. The main components are shown.
Some components that are not defined in the diagram:
- A
Value
is either aSet
, aCircularQueue
(representing a List) or astring
. - A
LogEntry
contains aforwardCommand
and possibly abackwardCommand
. - A
Result<T>
contains a value of typeT
or an error or a message.
More on the components and their relationships:
- A
Store
is where the key-value mappings reside. It supports basic dictionary operations (get, set, delete). - A
Logger
is where any commands that cause a state change of the system reside, within a log entry along with a rollback command which on executed, would rollback the effect of the former. It supports pushing/poppingLogEntry
and taking/popping a checkpoint. - A
StoreMediator
is the object that owns aStore
and aLogger
. It is the object where all commands are accepted into and passed the context of itsStore
. It also supports snapshots and setting expiration time on keys. - Each command is represented by a
Command
. EachCommand
can be executed given aMediator
as the context. It can also generate a rollback command of itself giving the context. - A
CommandFactory
is an object that creates aCommand
by parsing a raw string. - A
Parser
maintains a mapping from a command name to aCommandFactory
in order to parse any raw string.
- The key-value mappings are stored using the Javascript built-in
Map
, which supports sublinear dictionary operations. - A list value at a key is internally stored as a
CircularQueue
, which supports constant-time random access, constant-time popping and amortized constant-time pushing at either head of the queue. - A set value at a key is stored using the Javascript built-in
Set
, which supports sublinear dictionary operations. - The log is stored as a Javascript built-in
Array
to support constant-time stack operations.
Each time a command that mutates the store is executed, it should be logged into an in-memory log. Such commands should implement the getRollbackCommand(mediator)
method and the system will automatically log the rollback command too.
Effectively, a chain of commands is logged. Each time you execute a SAVE
command, a checkpoint is taken and the log is persisted.
This design's aim is to allow for adding more commands with ease.
More commands can be added by:
- Create your
Command
subclass in a file stored in./src/lib/commands/commands_imp/
. - Create your
CommandFactory
subclass in a file stored in./src/lib/commands/factories/
that's capable of parsing a raw string to return yourCommand
subclass. An utility function namedextractToken
stored in./src/lib/utils/extractToken.ts
can be used to easen the tokenizing step. - Add an entry corresponding to your command in
./src/lib/commandMapping.ts
.
Commands can interfere with each other in an unexpected way. For example, if a mutation command is to be added, the command should manually clear the timeout on the key(s) it's about to modify.
GET key
: Return a string value atkey
. Ifkey
does not exist or the value that resides there is not a string, return an error.SET key value
: Set the string value atkey
tovalue
. If the value that resides atkey
is not a string, return an error.
LLEN key
: Return the length of the list atkey
. Ifkey
does not exist or the value that resides there is not a list, return an error.RPUSH key value1 [value2...]
: Append one or more values to the list, create list if not exists, return length of list after operation. If a value exists but is not a list, return an errorr.LPOP key
: Remove and return the first item of the list. Ifkey
does not exist or the value that resides there is not a list, return an error.RPOP key
: Remove and return the last item of the list. Ifkey
does not exist or the value that resides there is not a list, return an error.LRANGE key start stop
: Return a range of element from the list (zero-based, inclusive ofstart
andstop
),start
andstop
are non-negative integers. Ifkey
does not exist or the value that resides there is not a list, return an error.
SADD key value1 [value2...]
: Add values to set stored at key. If a set doesn't exist atkey
, create one first. If a set exists but is not a set, return an error.SREM key value1 [value2...]
: Remove values from set. Ifkey
does not exist or the value that resides there is not a set, return an error.SMEMBERS key
: Return array of all members of set. Ifkey
does not exist or the value that resides there is not a set, return an error.SINTER [key1] [key2] [key3] ...
: Set intersection among all set stored in specified keys. Return array of members of the result set. Ifkey
does not exist or the value that resides there is not a set, return an error.
KEYS
: List all available keys.DEL key
: Delete the value stored atkey
. Ifkey
does not exist, return an error.EXPIRE key seconds
: Set a timeout onkey
,seconds
is a positive integer (by default a key has no expiration). Return the number ofseconds
if the timeout is set. Ifkey
does not exist, return an error.TTL key
: Query the timeout of akey
. Ifkey
does not exist, return an error.
SAVE
: Save current state in a snapshot. If the current state is already saved, this command has no effect.RESTORE
: Restore from the last snapshot.