Conf42 Rustlang 2022 - Online

Extending Redis with Rust

Video size:

Abstract

In this session, we’ll live-code a Redis Module in Rust that implements a Finite State Machine (FSM), we’ll roughly follow these steps: * Intro to Redis ~ 3 minutes * Intro to Redis Modules ~ 2 minutes * Bootstrap our project ~ 3-5 minutes * Deploy skeleton module to Redis on Docker ~ 3-5 minutes * TDD loop `till completion - Rest of the time! * Creating a new data structure/data type in Redis * Adding commands to manage our FST - one at a time

Summary

  • Welcome to extending Redis with Rust. I am a developer advocate with Redis. You can reach me at bsb@redis. com. And you can also check out my GitHub repos.
  • Redis stands for remote dictionary server. In Redis it's really an in memory first full blown database with optional persistence. Redis provides many data types. You can extend Redis by adding new data types and new commands. We promote seven different modules that extend redis.
  • Let's take a quick tour of redis by starting a Redis server. The most basic data type in Redis is a string. We also have hashes. Pretty much any binary piece of data can be stored in a redis key.
  • Redis has been developing a Redis modules API bridge in rust. Why rust? Because it shares the philosophy of extreme carefulness in memory management and performance. Any memory leak can compromise the health of these redis server. So safe memory management is a must.
  • All right, so we're going to kick off our demo by creating a new library using the cargo new command. I'm going to name my library Redis FSM for redis finite state machine. Let's CD to that folder and use these tree command to quickly inspect what we have.
  • We're going to create a dynamic system library to be loaded in red FSM module. First we'll tackle the FSM create command. Now let's start the redis server and load our newly created module.
  • FSM intro takes a context instead of redis arguments, these redis strings and returns a result. 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.
  • Brian: 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. With that in place, we can now test the event functionality.
  • So back on the CLI, let's reload our final state machine, finite state machine JSON. Let's actually inspect the hash of mappings. Notice that we have two events, one on the redis hash, but that one doesn't match our prefix. And then for the actual data one, the job one.
  • Now we can move on to implements the FSM allowed command. This method allows us to check whether an event can be applied to an object to transition to a different state. We are also going to register that command in our Redis module command declaration.
  • A simple toy state machine implemented as a Redis module. All the source code for today's demo can be found under my GitHub repositories. To learn more about Redis, visit us at Redis developer.

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.
...

Brian Sam-Bodden

Developer Advocate @ Redis

Brian Sam-Bodden's LinkedIn account Brian Sam-Bodden's twitter account



Join the community!

Learn for free, join the best tech learning community for a price of a pumpkin latte.

Annual
Monthly
Newsletter
$ 0 /mo

Event notifications, weekly newsletter

Delayed access to all content

Immediate access to Keynotes & Panels

Community
$ 8.34 /mo

Immediate access to all content

Courses, quizes & certificates

Community chats

Join the community (7 day free trial)