Transcript
This transcript was autogenerated. To make changes, submit a PR.
Hey everybody, this is Brian Sambodden. Welcome to extending
Redis with Rust. I am a developer advocate with Redis.
You can reach me at bsb@redis.com on Twitter at bsbodin.
And you can also check out my GitHub repos. So Redis
Redis, it's a popular data platform that it's
mostly known as a very powerful and fast cache.
But Redis is much more than that. So today I want to give you a
quick tour of Redis before we look at how to extend Redis
for your own benefit. So Redis stands for remote dictionary
server. In Redis it's
really an in memory first full blown database with
optional persistence out of the box.
Redis provides many data types. In Redis
is classified as a key value database. But the difference
between redis and most key value databases or key value
stores is that the value part in redis can be
anything you can dream of out of the box. You get strings,
bitmaps, bit fields, hashes,
list sets, sorted sets,
geospatial indexes, hyperlog logs, funny name and
streams. But there's more, and those are all
the values that can be addressed as using a key in
Redis. But like I said, there is much more than that.
You can extend Redis by adding new
data types and new commands to manipulate those data types
ourselves as a company we promote seven different
modules that extend redis with new data types and commands.
Those are Redis JSON Redis search, redis,
graph, time series, AI gears and bloom. And they
extend Redis to basically behave like a document database,
a full blown search engine, a graph database,
et cetera. And now for a quick
tour of redis.
So before we get started with our module building, let's take a quick
tour of redis by starting a Redis server.
And I'm running Redis 7.0.4 which is running
on port 63 79 which is the default Redis board.
And on a separate terminal I'm going to start the Redis
CLI which is the command line interface for Redis, and by
default it connects to the localhost 62 79. So one
of the basic data types or the most basic data type that you can keep
in Redis, it's a string, and that is the basic
level key that you have in Redis. So I'm going to
create a key called actor column one
and give it the value covey smolders. Now, in Redis,
it's very typical to create your own namespace for your keys by
using a separator like the column in my case.
So I can retrieve my key back from
Redis, and it gives me the string that I set
copy's molders. And I can also inspect a key
to see what value it's holding. In this case,
it's a string. Let's set another key
here. Actor two. Let's make this one.
Karen Gillen. As you can see, I'm using
Marvel characters in here, and again, I can retrieve the value
of that key.
And if, for example, I try to retrieve a value of a key that doesn't
exist, I get a nil back.
And you can also set, for example,
encoded strings, escape strings. Pretty much
any binary piece of data can be stored in a redis key.
So I can say get actor three.
And you can see my wonderful misspelling
of Benedict Cumberbatch's name.
You can see here that I can also pass.
For example, I can set a key passing
the nx flag that basically means set it if it
doesn't exist. So in our case, that's going to
have no effect on the original set value.
But if I pass the xx flag,
then it will override the value that was there before.
So in Redis, I can list all the keys
by passing the keys command in star.
And that's obviously not recommended in production, since thread is
meant to hold millions or billions of keys.
But it's okay in debugging session like this.
So I can also delete a key,
and I can check if a key exists in
the system.
All right, so that is the most basic data type in redis.
We also have hashes.
In hashes. It's the very typical data
type used for mapping programming language constructs
like structs or classes objects
to redis. So in these example, I'm going to use the hmset
command, which sets multiple values on a hash,
and I'm going to use the actor one key.
So I'm going to override the key that we had in there, and I'm going
to pass a name value pairs for an actor.
And in this case, I'm passing the name Anthony Mackey.
Year of birth, 1978. Place of birth, New Orleans, New Orleans,
USA. All right, so I can now check my keys again,
and I can do an h get all. So give me all the name value
pairs from a hash actor one,
and you can see that I gave my name value pairs
back out.
Hashes are kind of the workhorse of Redis. So, for example,
I can say h
exists,
actor one, and pass
the string character, and it will tell me whether
that hash has been set, that value has
been set in that hash. So, for example, now let's go ahead
and set that individual value. So I'm doing
a hn set actor one, passing the character
key and the falcon value or falcon as the value.
And now if I check again for existence of that flag,
of that field, you can see that I get a one instead of a zero.
And once again, I can do an h get
all for that key, and I get all my values back.
So knowing those two specific
data types, it's all we need for our purposes today.
Okay, so let's talk about the redis modules API. And the
Redis modules API allows you to extend the functionality of redis
by adding new data type or adding new commands.
Until recently, the Redis modules API was only available to
c programmers. But luckily for us, in the
last couple of years, Redis has been developing a Redis modules API
bridge in rust. And why rust?
Because it shares the philosophy of extreme
carefulness in memory management and performance.
Redis modules run in the same memory space as Redis. Therefore, any memory
leak can compromise the health of these redis server and create a
catastrophic condition for the server. So safe memory
management, it's a must. In Redis modules,
there is a rust crate that wraps our
Redis modules API bridge, and you can find that
on crates IO. And we're going to be using that today to build
our demo. We're going to create
a finite state machine as a Redis module that's
going to be in charge of managing state in Redis hashes,
and we're going to name it Redis FSM, and we're going to use the
Redis modules crate that we shown previously.
So these finite state machine, it's an entity that models and
controls behavior for an entity. So we define a set of possible
states, the transitions between those states and the events
that trigger those transitions. And why
are we targeting redis hashes? Because redis hashes are typically
used to map objects from a programming language into redis
if you're using redis for persistence. So our redis
FSM module will store a generic FSM definition that we
would initially create as JSON, and these stored as
a specific redis data type,
the definition will contain the set of states which
type of hashes it applies to by their prefix,
the name of the field that will be used to persist the
state into the hashes and the possible transitions and events.
So imagine this simple state machine. We're going to be using the same
one during the demos, we have three states,
sleeping, running and cleaning. And notice that the events in green
allow you to transition from a state to another. So for
example, from sleeping to running, the run event triggers that transition.
From running back to sleeping, the sleep event triggers a transition
from cleaning to sleeping. The sleep event also triggers a transition,
and from running to cleaning, the clean event triggers that transition.
So here is a bigger picture of the
scheme that we're setting up in here. So we have a definition of a
state machine on the top left corner,
which we're going to represent as JSON. That JSON
is going to be turned into a binary redis
type that's going to be stored by the module in Redis. And we're
going to have two commands on the left,
FSM create to create our finite state machine data
type in redis and a FSM info to read it
back as JSON again. Then we're going to have
two commands to basically do most of the interaction with
the state machine and the hashes that the state
machine applies to. One of them is going to be FSM allowed,
which is going to interrogate a state machine hash completion,
whether a specific event can
be applied to the system and one that actually applies
the event, which would be FSM trigger and these.
There will also be an implied event
transition or initial state setting that
will be done when a new hash,
it's saved in the system into Redis and our module
is going to detect that the state
field is not in there and set the initial state
of the hash. So that way we have the beginning of
our state machine functionality.
So in our case of the example on screen, it would be the
sleeping state. So if a hash is saved to the system and the sleeping
state is not set, or the state field is not set, then we set it
to sleeping. And here's the JSON document
for our sample FSM definition.
All right, so we're going to kick off our demo by creating a new library
using the cargo new command. We're going to pass the lib
flag. I'm going to name my library Redis
FSM for redis finite state machine,
and I'm going to put it in a folder called redis FSM
all in camel case. As you can
see, we have the output of the library being created
and let's CD to that folder and
use these tree command to quickly inspect what we have. So we have a cargo
tomo file and we have a lib rs file.
Let's quickly take a look at those. I'm going to inspect
the cargo tumble and we just have our package declaration for redis
FSM. And let's do the same thing
with the library file.
And in that one we have
a simple function to tdd two unsigned integers.
And we have a simple test and we're going to get rid of that.
But that's a good skeleton to get started.
All right, so with our newly created library loaded in the code editor,
we're going to open the cargo tumble which I already have open in
here, and we're going to add the
dependencies for these redis module toolkit.
We are also going to
set the crate type to cdylib so we can
create a dynamic system library to be loaded in red
FSM module.
First we'll tackle the FSM create command.
I'm going to open the rust code in lib rs.
And in there you can see that I already have the
external create redis module being
used. And we're going to
import from redis module a few structs,
enums and types that we're going to be using down
in the code. And our first skeleton
code is going to be a function called FsN create. And this is going
to be the function that backs up the
FSM create command that we will be creating.
And as you can see, it takes a context. The context
is going to be a redis context. It's going to be passed to
us by the modules API.
And then we get a vector of arguments which are packaged
as redis strings in the Ards
argument. And each one of our module methods
or our module functions returns a redis result.
So inside of this we're going to skip
the first argument. The FST argument is going to always be the command itself.
So we want to ignore that. And we're going
to grab the length of the arguments in here just to do a quick check.
So it's, if the arguments arity
it's greater than what you're expecting, you throw
a redis error these I'm going to
grab the actual argument
that's being passed and I'm going to call that source src.
And I'm doing that by using the intro iter. And then
I'm going to use the next string which is going to return a string
object. Next I'm going
to take that source and I'm going to format it
in a string with the format macro
and I'm going to call that greet. So this is going to be our hello
world example. So we're going to do this in the skeleton
of this function so we can see how things work. And then we'll get
into the more meaty stuff. All right,
so now I'm going to create a response, and my response is going to be
a vector from that greet string.
And then we're going to wrap that whole thing into an okay.
So we're going to be using the redis underscore module
macro, which is going to be the block of code that's going to basically
declare the module to redis.
So inside of that we're going to have the name of our module, which is
going to be FSM for finite state machine,
and these we're going to give it a version of one.
Right now we have no data types, so we have a block for our commands.
And inside of that we're going to add our FSm create
command. So notice that the name in
the string, the first element in here, FSm create,
is going to be the actual command that you type in the redis Cli or
that you send with a redis client, and then it's going to be
backed by our FSM underscore create function
above. All right, so now that we have a
basic hello world implementation inside of our
FSM create command,
let's start the redis server and load
our newly created module. So before we do that,
we need to compile the module. So I'm going to do a cargo
build. And now
we have the module has been compiled.
It will be under the target debug
folder, and you can see there that we have
the lib redis underscore FSm dylib
file. So now to load that intro our
redis server, I'm going to invoke the redis server command
again. And this time I'm going to pass a load module
flag in these
lib redis fsm file.
So target debug lib
redis fsm dylib.
All right, so now we have the redis server running,
and as you can see on the output, we have
loaded the FSm module from the Dylib
file. All right, so now with that in place,
let's launch the redis Cli.
And in the Redis Cli, one of the things we can do to verify to
you that the module has been loaded is to do a module
list. And you can see that we have
only one module loaded in this instance of redis,
mainly the FSM module that we just created.
Right, and the redis cli kind of autocompletes commands.
So if we do FSM,
the only command loaded right now is FSm create and
we can pass a string to it.
And as you can see, we get the hello back.
Notice that the emoji that we
had in the code, it's actually encoded in the string.
So I kind of wanted to show you that Redis
basically stores everything in raw
format. So now we see that we have a working command.
Very simple command, doesn't do anything. Now let's go back
and flesh it out.
So the first thing I'm going to do is bring in
the inserted JSON dependencies because we're
going to be using JSon. And now back
to the code. The first thing we're going to do is add
a state machine struct.
And as you can see here, I have a state machine
that has a name, a prefix,
a field, and a set of states.
To transition from state to state, we need to have the definition
of an event. So I'm going to create a struct that basically represents
an event. And the events will have a name and it
would have a state that they
can transition to and also a collection of
state that they can transition from. So we have an event in the
state machine. We're going to add a vector of
events.
Now that we have our data structures for the state machine,
the next thing we're going to do is
add a few definitions that we're going to use to
basically manage the registration of our
state machine data type. Okay, so let
me show you what we have in here. The first one, it's this redis
FSM type name. That's going to be the name of the type in
redis. And that has to be 123-45-6789
characters. Specifically, it's one of the requirements of a
redis data type that has to be exactly nine characters.
So we're calling ours Redis FSM
the version of our data type. It's version number
one. You can basically have multiple versions. And then
in your code you would basically have to manage backwards compatibility
with older versions of your data types.
If you choose to do so, then the
actual type will be created under these redis
FSM type. And then this is a redis type. You notice here
we're invoking the new function and it
takes the type name and the version and then
a set of methods that
basically initialize or
manage how these data type lifecycle it's
handled by Redis. So now we're
going to make some modifications to our FSM
create. And the first thing that
I'm doing in here is that I've added this line right here
that creates a variable called Redis key,
which it's actually grabbing the context. Notice that I brought these
context variable back into scope,
and that context provides an open
keywritable method. And that method
basically takes a key definition, a redis key,
and it's going to basically give us a slot in
Redis to write something to it. So these, once we
have that, we're going to create
some JSON representation, a certain representation of
our state machine. So notice that we have a JSON document here that
has the main prefix field, the state for
our state machine and definition of events. In this case,
we have that simple state machine that we shown in the slides before,
which has three states sleeping, running and cleaning. And it
has three events,
run, clean and sleep.
And remember, run can transition from sleeping
to running, clean from running to cleaning,
and sleep from either running or cleaning to sleeping.
And notice that I'm using the JSON macro from Certe
to basically create a JSON object.
And then once we have our JSON object,
we're going to serialize, we're going to create
a state machine from that JSON object. So notice in
here I'm using the asserted from the string of
the JSON that we created, and that is going to give
me an instance of our state machine strut.
So next, once we have a state machine object, we're going
to use that redis key to write that value to the redis key,
passing the specific type that we're writing, which is our redis
FSM type, which we declared up here in
that redis type block.
Okay, so we wrote the key to Redis,
and now we're going to get rid of that hello world part of the example,
and we're going to return a boolean
as an integer. True, if we were successful
at writing that redis value to redis,
that key value, the actual serialization of our state machine,
and false otherwise. So that gives us now
the ability to write our custom
data type to redis.
We also need to add the redis type to
the module declaration.
So noticing here that under the data types vector,
I added the redis FSM type.
And now that the FSM create
its write into redis, we actually mark it with the
modifier write, telling us that
this commands writes to the database. And the
three ones at the end in here represent the
index of the first key, the key step,
and the index of the last key. So since we only have one
parameter, the main key, one key, actually we
have one across the board.
Now we will build the FSM info command.
Okay, so now that we have a method that can write our custom data
type to redis, we should probably add a method to read it back.
So for that we're going to implement the FSM intro
method, and the first thing I'm going to do is just
add a skeleton for it. Just like the FSM
create method, our FSM intro again takes a context
instead of redis arguments, these redis strings and
returns a result. We're starting with just a result,
okay, with a null value, and now we're
going to grab the arguments like we did before, and we're grabbing
what's supposed to be the key as the
first argument after the method name.
And these we're going to again create
a key. But this time we're going to open our key
similar to what we did in the FSM create.
So notice in here first I did a context open
key writable, and in the FSM intro,
since I'm only reading, I just did a context open key passing
that key value.
Then I'm going to bring in the guard crate so I
can avoid having to have nested let
in my code. So this is going to allow me to write a little bit
cleaner code in my opinion. So with that
in place, I'm going to create this guard where I'm basically
trying to bring the state machine back from the JSON that's stored
in the database in redis. So noticing here that
I'm doing a guard and then a let.
Okay, so there's going to be the redis response. So I want it to be
a successful response that contains some
object of type call FSM that I'm
going to name FSM. And I get that from reading
the value, casting it to a state machine using my
redis FSM type that we declared previously. And if that fails,
then I'm just going to return an error with a redis error that
contains an error message. These very likely it's these
errors that I couldn't find the key. Now with that
in place, I'm going to use survey
again to basically put the JSON back into
string form and return that JSON wrapped in
a redis value simple string with an
okay around it. So with that in place,
we need to add the command to our declaration like we did before
with the FSm create. So in here I'm going to now add
the FSm info. And again we have the FSm intro
command being backed by
the FSm info function. This one is a read only the
other one was a write. And again, since we only have one key and it's
the first key, we have ones across the board here in the
definition. And now we can go and test this.
Okay, so now that we have our two commands, the FSM create to write
the JSON definition of the state machine to redis
and these FSm info to read it back.
Let's compile our library
again our module, and let's launch the
redis server again, loading our module. All right,
and now again, let's go back to the redis Cli and
let's see what keys we have in the system. Should be empty again.
All right, so now we can do an FSM create.
And in here I'm going to
pass the key is going
to be job FSM, which is kind of what we hard coded
in the JSOn for now. So if I
do that, notice that we get a one back. So that means that something
went right.
And now I'm going to list the keys
again and notice that we have now one key,
job FSM. And if I check the type of that key,
we get our custom redis data type redis fsm.
So now let's see if our redis command
FSm info works correctly so I
can pass the key that we just created and we
get our XMl back.
And the redis cli doesn't preprint the XML.
So you could use Redis insight,
for example, if you wanted to basically see a nicely colorized version
of the JSON definition. So now
we have a data type that implements our finite state machine
and we have a command to create it and one to read it back.
Okay, so now we have two commands, one that writes some
JSON definition of an object and
one that reads the definition in our lingua
franca to store them. It's Json. So now what
I'm going to do is now refactor that FSM create
so we don't hard code the JSON because we really want to have the flexibility
to have multiple state machine
definitions there. So the first thing I'm going to do is
rather than just reading a key, we're going to
take the JSON. So I rename the first argument being read to FSM,
underscore JSON. And then
I added this block in here where I'm taking that
JSoN, that raw JSon that is being passed from the command.
And we're going to use these third JSON library to
read it from a string, the two string of that FSN JSon which
is a Redis string, and that's going to give us our state machine object
back, rehydrated. And once
again, now these key to write this to Redis
is going to be the name property in the JSON. So what I'm doing in
here is I'm taking the FSM instance
that we just created from the JSON and grabbing the name to
basically create our Redis key.
Okay, so now we can test our refactor version of
the FSM create method. So once again, I'm going
to compile our library and I'm going
to launch the Reddit server and
also run the CLI.
And now our FSM create method.
Obviously it's a much more beefy method
because now it takes the JSON payload and obviously
I have it ready here to copy and paste. Notice that the JSON
has to be scaped properly. So all the internal quotes
have to be scaped with the backslash.
And when you have large payloads to
load in the CLI, rather than pasting them into the Cli, you can
actually pull these from an external file. But since this is a quick
and dirty example, I'm just pasting it on the Cli. And if
you have it in a file, you don't have to do all this escaping maneuvering
over here. So I'm going to run my FSM create now with
the JSon being passed from the CLi.
And again we can check once again
that we have our key being created and that
we can read it. So now we have a full run trip
with a custom definition of our state machine.
Okay, so now we're going to implement the actual functionality of our state
machine. And one of the first things that we want to do is
when a new hash matching the
given prefix, it's added to Redis,
we want to intercept that and set the initial state field
given a state machine that matches the prefix of the hash.
So to do that, first I'm going to add
a function called on event. And this on event function
takes a context again, but this time it takes
an event type, which is a notify event and
the actual event as a string and the key as
a string and inside of it. For now we're just going to have
a message that basically prints what the event was
in the key and
the event. So the event type, the key and the actual
event, and we're going to log that to the context with the
log notice login message. So once
we have that in place, we have to register that event. So down in our
redis module registration block,
we add an event handlers vector
that has inside an event
handler of type hash. And we're pointing
that to our on event function. So when a hash
event is detected, we're going to get notified and
our on event function is going to be invoked.
Right. So with that in place, we can now
test the event functionality.
All right, so now let's compile our library
again and
let's launch it.
And let's launch the CLI.
Let's arrow so we can get to that FSM create. So we'll create
our job
FSM finite state machine.
Let's make sure that the key is there again.
Let's make sure that we can read it back.
So it's job FSM. All right,
so now what we're going to do is let's do an HM set.
So we're going to create a key. Sorry. We're going to create a
hash and we're going to use the prefix that
we're listening for in our state
machine. It doesn't really matter right now, but it will
once we actually look for that prefix. But for now,
any hash event should be detected by our event
handler. So in here, I'm going to create this hash called job
one. And let's give it a feel here,
maybe employee. And this is going to be Brian.
Okay, so now pay attention to the
redis server tab. So I'm going to press return in
here and notice that this is spinning because now we just receive
our event. So in here we have received event hash
on key job one via the event H
set. So our event was an h set event.
So that was the command that basically was triggered by the hmset.
So each one of the writes to the hash.
It's an H set event. And in here
I get these key of the
hash that was created and these event type was a hash.
All right,
so now we got a big refactor in our hands. We need to implement
the on event function. So when a
hash event arrives for a hash that has
a key with a prefix matching any of the prefixes
defined on any of the FSMs that we have stored
in the system, we want to then check
that hash. And if these state field it's
empty on that hash, we're going to grab the initial state from the state
machine that matches that prefix for that hash and we're going to set it
up on the hash. So to do that, first we're going to start
by grabbing the prefix for the
hash for which we have received an event.
So I'm going to split the keys by
the column and then I'm going to grab the prefix as the
first segment of that list of key parts,
and I'm going to reappend the column in
there to build my key prefix. So then
if we go back to the FSM create,
we need to basically have a way for,
given a hash prefix, be able to find
the FSM that matches that prefix.
So there's many ways we can actually store that data. So we basically need
a dictionary or a hash or a map between prefixes
and finite state machines. And the
way that I decided to implement it in this demo is to
actually use Redis itself. So I'm going to use a Redis hash to store
a mapping of prefixes to the keys
of our fsms. So we're
going to start here by refactoring this. So we were returning
just a result of setting the value of the FSM
when we wrote it to the database. Now we're just going to put that in
an individual line with one of these guards. And in here's where I'm basically
writing my state machine, my serialized state machine to the system.
And then in between that and the return,
first I'm going to create a constant that basically
tells me the name of the hash where I'm going to keep my mappings,
my mappings between, again, prefixes to
FSM keys. So now I have that
hash name that we're going to use. And down below here,
notice that I'm using the
context to call h set. So this
h set, it's just a command, a redis command. And the
redis context gives us access to those commands.
So I'm going to call the h set command,
and I'm going to write to this redis fsm hash key that we created
here. And I'm going to write the prefix that
is defined in the findingstate machine JSon. And I'm going to
write also the key of my finite state machine
as the value in there. So now with that in place,
I can now make some modifications. So now we
have a set that has those values. So let's go back to our
on event and in here. Notice that now I can
find the correct finite state machine for the key prefix.
So what I'm doing in here is again, I'm using a guard again,
and I'm looking for my fsm
key by calling h get. So we did an h set
to save the mapping upon creation of the state machine.
And now when we get an event for a hash, we're going to use Hkit
to look into that map to see if our event prefix,
our hash prefix, matches any of the prefixes of
our state machines. I'm also assuming that the prefixes
are unique per state machine. So now with that
in place,
I can now create a key name,
which is going to be, I'm using the context and my
findingstate machine key and I am
basically opening that key. So I'm reading the FSm
back and then from the FSM here,
which is basically my instance of definite state machine, which is going to be the
struct that we defined before. So it's
going to be one of these. So that
state machine object, now it's loaded here at
the end of the current implementation of our own event.
So once I have the state machine object,
I can grab the initial state, but I'm only going to grab the initial
state if a call to try to
retrieve the FSm field from the
hash comes back no. So that means if the hash
doesn't have the state field set, I'm going to
initialize it to the initial value that is on
the state machine. So notice in
here one
of the things I need to do is I need to have a way to
retrieve that initial state. So how do I retrieve that initial state?
That's going to be, if you remember our JSon object or
the definition. So let's go back here to our
state machine. This set of states, the first implements
of this vector is going to be our initial state.
So now let's add some logic to our state machine.
So I'm going to add an input block for our state machine in these,
and we're going to create an initial state
field. So our initial state, this being a method,
takes a pointer to self and these we're going
to return an optional string. So that means if there
are any states in here, we're going to return the first one
or none. And with that in place,
we can now go back to our on event
implementation. So back here
on the on event implementation. Now we can call that new method that we
created on the state machine strut.
So we're going to basically use a guard again,
and I'm going to attempt to grab the initial state by calling
the initial state on the FSM object that we retrieved up
here. So once I have that, now I can call
an h set. So I did an h get to find the
correct mapping
of prefix to state machine. Then I instantiated that
state machine. If these field also
was empty, I use another h, get to check if the state was set on
the hash and if it wasn't set, now I can grab the initial state
from the state machine and do another h set on the
hash on the incoming hash on the
field that was defined on the state machine as being these initial
state and grabbing the initial state value from the state machine.
So now with that in place, we should have a fully
functioning step of the state machine that
sets up the initial state on any matching hash.
Okay, so let's build the application again
and let's launch it so we
can test our event handling code.
So back on the CLI,
let's reload our final
state machine, finite state machine JSON,
and let's do all these usual
checking. So let's actually inspect the
hash of mappings. So we'll do an h getall for
redis FSm hash.
And you can see that we have the job prefix that we
set on our JSON specification,
and it maps to the key for
the FSM that we have in the system.
So now let's do
an hm set for
a key matching our prefix. So again
let's do job one and
we're going to say again, employee Brian.
And as you can see, the redis
server detected the event. Notice that we have two events,
one on the redis hash, but that one doesn't match our prefix,
these redis hash that keeps the mapping of prefixes to
fsms. And then for the actual data one, the job
one. So now let's inspect the
values. So we'll do an h get all for
our job one.
And as you can see now we have the initial
fields that we set. Employee Brian, employee Brian.
But we also have the state set to sleeping.
And if you remember in our FSM
definition, our first state is sleeping, which is
our initial state. So our state machine
initial state setting on events works.
Now we can move on to implements the FSM allowed command.
Okay, so now we're going to implements the method that allows
us to check whether an event can be
applied to an object to transition to
a different state. And to do that,
first we're going to create a skeleton for the FSM allowed
method function, sorry. And has the same signature as
the methods before.
And we are also going to
register that command in our Redis module
command declaration. Module declaration. So I have
my FSM allowed. It's going to be backed by that FSM allowed
function that we created above. And again it's going to be a read only
these first key parameter.
It's a parameter in position one. But this one takes two keys.
So the first key would be the key of the FSM
of the final state machine, and then the second key would be
a target hash for which we are checking whether an event can be
applied to.
All right, so now we're
going to add a method to our final state machine struct.
So let's go find the final state machine, the finite state machine.
Here in our impulse block, we're going
to add a method called allowed.
And this one is going to return an event.
And this event is going to be one of our event substructs.
This one right here. The event that basically
denotes an event that can be applied
to the state machine, which will basically transition
from a state in the from vector
to the final state denoted by the two field.
All right, so in our function right
now, we're returning none. So the first thing we're going to do is
load the hash,
the hash that basically we're checking against in
that hash. We're only interested in the state field.
So we're using the
finite state machine field, which you remember it's in
our declaration right here to basically look for
that specific state in that target hash.
And if we find that
state, that's going to be in response. So we're
going to grab the value of that, which is going to be a redis value
simple string, and we're going to put that value in the current state.
And assuming that we can get that value
out, then we're going to
find the event object, the event struct
that it's inside of our final state machine by iterating
over the events and going to find the event with the name
that matches the FSM event value that's being
passed to us. So this is the event that we're checking whether
that hash can transition to, right. So if
we find that event, it's going to be in our event object.
And then if the current state of the
from field. So if the current state
of the hash, it's one of the values in
the event from vector,
then we know that we can transition to the
final state denoted by that event.
So if that's the case,
we can basically return that
event. Otherwise we return none. And now if we
go back to the FSM allowed method.
Here we go.
Now we can refactor this to
extract the keys.
So I'm going to extract the final state machine key as the first key.
Then the next key would be the hash key.
We're also loading our redis key.
So we can read the final state machine value
and these event would
be the next argument. So remember there's only two keys.
So there's the final state machine key, the hash key,
and then the target event. So that's why we only have two
keys denoted in our declaration
down here. So that two means that there's two keys, even though we have three
parameters. The third parameter, it's the event that we're checking against.
All right, so back to the FSM allowed.
Let's finish that implements. So in here I'm going to have that guard again
where I extract the final state machine object by
reimbing it from redis,
giving its key. And then now
that I have the final state machine, I can call the final state machine allowed
method that we just implemented above, passing the context,
since we're going to use that to basically read things
from redis, the hash key and the event. And then
if I get an event back, that means that we're good.
So I don't need the event object in this specific method,
but then I'm going to use it to determine whether to return true or false
right here.
So now let's recompile
the code and
let's restart our server, loading our module and
then back on the CLI.
Let's up arrow here to recreate what we've done
before. We're going to load the FSM
and let's set a hash
again. Let's use these job one with employee Brian.
Again, let's double check that our
initial state has been set on the hash.
You can see that the state is sleeping. So now I'm
going to do an FSM and this is going
to be allowed. Notice that the CLI autocompletes that.
And these first key
we're going to pass here. It's our job,
FSM. And then the second
one is going to be the hash,
which is job one.
And then let's test
one of the events. So one of the events that we have
in our final state machine,
it's sleep. So sleep can transition from
running or cleaning. So let's try that one.
Since we are not on running or cleaning, we're in sleeping,
we return false, because we cannot transition
using sleep. So the sleep event is not allowed
in this specific case.
But running can.
The event run can transition from
sleeping to running. So let's see if run
it's allowed. And as you can see, run it is allowed.
All right, so now we have a way to check whether an event can be
applied to a given hash.
Finally, we can implements the FSM trigger command.
All right, so now we actually are only missing
the command that would allow us to actually transition
a hash from one event
to the other. So we have one to ask whether a transition or
an event can be applied to a hash.
And now we're going to have one that actually triggers that event.
So to do that we're going to create a skeleton method
for our new command. And this is going to be FSM trigger.
We're grabbing basically the same arguments that we were grabbing
with FSM allowed. So this whole block here,
it's identical. We again are grabbing the state machine
and this one is again going to return a boolean.
So to actually do this, we now
need to, as we did before, implements a
method in our final state machine,
finite state machine struct.
So let's find our implementation up
here. Okay, so we have there the allowed method
that we implemented earlier, and now we're
going to create a trigger method. And the trigger method, the implementation,
it's very similar and it actually reuses the
allowed method. So notice in here that again
I am grabbing the key of the hash
that I'm interested in applying an event against,
and then I am going to grab the event.
Only remember that we said allow to return the
event that you need to use for that specific current
event. And I use
the allowed method above to grab that event,
passing the context, the key of the
final state machine, of the hash and
the event. And then I'm going
to now call h set on the target hash,
and I'm going to set the event field
or the state field to the event that they're interested
in. And again, I have this guard right here that
basically it's asking whether the event is allowed by using
the allowed method. And if it's so I then change
the value of that field on the hash.
Then I basically just return to
the caller if I get an okay response.
So now once we have that
trigger implemented in there in the final
state machine, we can go back to our
FSM trigger skeleton and finish the implementation
in here by simply having an allowed variable
that calls the trigger method
in the finite state machine struct.
And I use that to return that boolean
as an integer in my redis value response
redis result. So with that in place,
then all we have left to do is
register our method. So now I'm registering
the trigger method with the redis module
macro commands list. And again, this one,
it's just like FSM created. It's a write method
and it takes the first key, it's the
key in position one, position zero, remember, is just the
command itself. And it has two keys,
the final state machine and the hash.
So now with that in place, we can
test again.
So, okay, now we have completed the basic implementation
of our toit state machine. So let's go ahead and compile the
code again. And I'm going to
launch the redis server.
And then once again, let's get to the CLI
and arrow to find our FSM create.
Let's just test all of our methods. So when we run FSM create,
remember that that creates the job
FSM custom
data type, which is a finite state machine data type. So when
we check the type of it, we get redis fsm,
these redis fsm hash.
Once again, it's a native redis hash
where we are keeping our mappings of mappings
of prefixes or hash prefixes to
finite state machine keys.
So again, we have for the sample that we've been using,
it's job for the prefix, and that maps to
the job fsm key. All right,
remember that we also implements the
info so we can pass the
name of a state machine and that will basically grab
that native data type that's stored in redis and use cert
JSON to convert that to a readable chunk
of JSON. And if
we create, let's create
a hash.
Here we go. We create a hash. And now we do
an h get all on that hash.
We should have our state
set to the correct value, which is the initial state of
the state machine sleeping. And then remember
that we can ask the state machine
whether we can transition to the different using
different events. So we can ask whether we can run
from sleeping. And it says yes.
And what was the other one?
The other one was sleep, and that's a no.
So since we know that we are allowed to
run, I'm going to trigger the
run event on our target
hash. And that responded with a true.
So now if we do an h, get all,
remember we were in these state sleeping, and now we are in the
state running. So there we go. A simple toy
state machine implemented as a Redis module.
And of course, all the source code for today's demo can be found under my
GitHub repositories.
To learn more, visit us at Redis developer, which is
located at developer redis.com. These we have tons
of examples for pretty much every programming language for
interesting things you can do with Redis. And also, if you want
to learn more about Redis, come to Redis University, where there's a variety
of courses that can help you become a Redis expert in no time.