 I showed a little bit of code. I created a telegram bot that we were using to track who was going to be attending some events. Our soccer group here at ThoughtWorks uses it to see who's coming every week. So the functionality of the, I'll just go over this very quickly again to show people that may not have been here. You can start a roll call in a chat group. Hopefully the bot's listening. So it just acknowledges that, and then you can declare who is in, records me as coming. Christians responded. So Christians coming as well. And then I might say I'm out because I'm injured, for example. So it's kind of useful. The original version of this was done, I think the original one was done with Phoenix, and then I refactored it to get rid of Phoenix because it didn't need Phoenix at all. I re-wrote it, but it was using relational database. It was using a Postgres database to persist all the data, and that's fine, and that's still the production version, but I was thinking about it, and I wanted something that was, I wanted a project to just play with Elixir, and I decided to keep iterating over this. There was a few problems I wanted to address. First of all, I felt the first version was really just a very normal app. It took requests, processed them, stored some stuff in the database, and responded. It wasn't very OTP. So it was not doing much in the way of managing processes, et cetera, and I just wanted to find a challenge to use some of the cool things that Elixir provides. So I came up with a plan. This is a little bit of an unprepared talk, so I don't have a diagram or anything, but firstly, I wanted to avoid having to register a new SSL certificate for every bot that I created. So I thought I could have one system that received web requests, and then delegated it off to other systems to actually handle it. So I could have, what I've called it, is the bot hub. So there's one master system that takes incoming requests and then it can farm it out to other applications. So these are actually running in separate Erlang VMs at the moment. So we're actually getting communication across Erlang VMs. And within the VM that is actually handling the requests, I've got a couple of supervisors going on, and I've got individual processes for each chat group. So one of the ideas with a process in Elixir is that it just, it maintains a state that it passes in every time you send a message to the process. It will have the state that the process last responded the last time it handled a message. So this implementation that I've just demonstrated here, it no longer has a relational database that is storing the data. Everything is just an in-memory state. So if currently this version, if this, the process for this particular chat group dies, it will lose its state. I am hoping to do another version where I will start persisting it to disk, I think, just for each chat group when it receives a message or something similar. I'm looking into DETS. So this version is using ETS tables, but there is a disk version of ETS tables. ETS is Erlang term storage. So I'll jump into some of the code. If you haven't looked at OTP, it's probably going to be a bit hard to follow some of this stuff. I'll try and explain it as best I can. So I'm not going to bother showing you the bot hub. I will just focus on an individual bot that lives inside the hub. The first thing that this application does is it spawns a worker process. That's the process that will receive messages from the bot hub. And those messages have already been deemed applicable to this particular bot. So it won't receive messages from any of the other bots. It will also have a chat group supervisor. The chat group supervisor is responsible for starting processes for each individual chat group that it receives a message for. So I'll show you the worker. Basically when this process first starts up, it connects to the hub. To do that, I'm using an Erlang library here. This is actually sort of built into Erlang. There's no library involved here where I can register the name of this process globally. And when it means globally, it's not just within the single VM, it's across VMs. So I've already joined another VM here. So connect to hub actually gets the node name of my bot hub VM and will connect to that node. So then I register this process with that node so the bot hub knows where to send messages. When I receive an actual telegram message, I'll receive it into this process first. At the moment this process is responsible for passing the JSON of that message into a structure. And then I basically pass it off to an individual chat group process that's going to manage all the messages for that chat group. We can at this point jump in and have a look at the chat group. So there's a chat group, this is another gen server. When you call handle message on the chat group module, it looks for the chat group process which may or may not exist yet. If it's the first message, it won't exist. I can just jump down to the code down below and say, okay, it looks up where is the chat group with that ID. This is using local process registration. So all of these processes are only known within this Erlang VM. And if it doesn't find one, it creates a new chat group process and returns the PID for that process. Otherwise, it returns the other PID. Really simple. Actually handling the message once it's found the process. So this is where we're sending the message to the process itself. And this is where it is handled. I have a message handler which I kind of showed the two meetups ago. This has been refactored to remove all of the persistence stuff. And I really like this now because it's very, very clean and simple. We're using a lot of pattern matching in here to match all the different methods, sorry, commands that the bot supports. And it's really pretty simple. Go back to chat group. So the actual chat group process is pretty simple. It actually handles the message. If the response to that is OK, it saves the state in ETS. So here, the reason it's doing this at the moment is if this process dies, it will still reload the state of the process. My bigger issue here is if the whole VM crashes, then I lose the state. But as long as the VM doesn't go down, well, actually that's not quite true. If my supervisor doesn't go down, but my supervisor is very, very simple. So all it does is spawn trial process. It's very, very unlikely that that will go down. But if the whole VM goes down, then I will lose state. So yeah, loading and storing state in these ETS tables. The interfaces for this is really simple. And this is all built in to Elixir. Well, I mean, this is really an Erlang library. You can look up. This is the name of the ETS instance. And I'm looking things up based on the process name for my chat group. What you can store in there is really like a tuple. So you can put whatever you like. It uses the first item as your lookup key. So in my case, the first item when I'm saving state is my process name. And then I actually store my state. My state refers to a struct I've defined that stores the information about the chat group and all the responses that it's received. I don't want to ramble on too much about this stuff because I've sort of jumped all over the place and probably confused everyone so far. If anyone has any questions, I'd love to answer them. And if anyone's got any suggestions on how I can make it better, that's good too. One question, so the ETS you use is just memory mode, right? It doesn't save text. How do you configure it to save information to the ETS? I've only tried it once. And I've actually just had to stash it to this and sort of finish it. But basically it is as simple as renaming all of the ETS atoms to D ETS. And it's a different module that is totally compatible with all the same functions. But rather than storing memory, it will actually write it to disk. The only difference, give me one second, let's just do it here. So you can see I've got DTS, DTS, D ETS, sorry. This, so when you first create it, too small. Hang on, I don't know. I don't think I can in this app. Hold on, let's do, why not? Where was that? That was in chat group supervisor, hold on. So this is the line that we were trying to read. This is the only one that really had to change after I made the switch, because I had to provide a file name for where it was actually going to store it. So I'm not sure if that's the way forward. I was going to try this out. From what I've read, it's significantly slower. You can make it faster for ETS and set it to have like a sort of running disk. I think there's a mode for it so that it actually stores everything in memory. And whenever you write, it keeps a copy in memory as well. Yeah, exactly. So when you read, it's actually very fast. So the little bit of research I've done so far was pointing me towards the Amnesia library. I don't know if it's a library. I think that's part of Erlang as well, I think. Which basically seems to just be a layer on top of ETS and DTS. And I think that. It's a lot more complex. Yeah. So I haven't tried that yet. I'm curious to hear that try. The other thing that I think is worth mentioning, the reason that I'm OK with it being in memory at the moment is because I was playing around with eDeliver, which allows you to do deployments without restarting the VM. So the zero downtime deployments and upgrades. I have that configured in this project. And I have it also configured through SNAP CI. So essentially every commit will build and update binary and push that to the server. And at the moment, unfortunately, it's not automatically running the upgrade script. But I think I'm pretty close to getting that functional. I'm probably going to get myself in trouble eventually because I'll change. It gets complex when you're trying to upgrade a running process, right? But if you if you if you if you if you change the state of the process in an incompatible way, the whole thing is going to crash. You can write upgrades, groups, etc. But at the moment, I'm kind of just assuming a happy path. Is there a lot of people who doesn't know that Erlang allows live code upgrades? Yeah. It supports all the blocks. Shut up. Shut up. Yeah. Yeah. It's a very cool feature of Erlang and Alexa. And if you're doing deployments, definitely look into eDeliver. I can talk about this for a few minutes. How long do we have? I'll try a little bit. It's the five minutes. Just give me a really quick overview. There is a library called eDeliver. It's sort of based on top of EXRM, which is a release management library as well. So EXRM actually builds the binary and eDeliver is kind of like Capistrano is in Rails. It's just got a bunch of scripts that help you actually deploy the binaries that EXRM creates. The deployment process is as easy as these few commands. So mix, eDeliver, build, or release. At the moment, I'm on a particular branch. And then an important thing to know here is that when you build the release, you have to build it on the same platform that you're going to deploy it to because it contains the actual Erlang runtime, I think. So if you built it on a Mac and then tried to deploy it to a Linux server, it would not work. eDeliver makes that easy. In a config file, you can provide your build host. So in my case, my build host is the exact same thing as my production host. So it actually builds it on the production box, delivers it back to me as a binary, and then I deploy it back to the production box. In a real setup, you would not be doing that. You'd have a proper build box and deploy to a separate machine. Yeah, no, I think there's a lot of people that are talking about that at the moment, but that doesn't exist in... It's because it's actually embedding the compiled version of Erlang, essentially, in your release, so you get a single release model. And it's already been compiled specifically to that platform. You must know that for XRM, when you release it, the binary itself contains everything. It's standalone, including the whole BNVN and everything. Yeah, that's why when it compiles on the platform itself, it's very platform-specific. How are you dealing with some secret keys when deploying? Where do you keep them? I have this problem in my app. Yeah, I'm still kind of working through this one myself. Obviously, there's a problem as well. I mean, just recently, I've added conform, which is a good library to manage the configuration. It's been very painful. The original config is done in Erlang and Featual, which is kind of like this. This is just my template for a reference, but you essentially have to create this manually on the server. And now it looks more like, I think it's probably a configuration file. How do you later load this configuration? I create my server and I deploy this thing for the first time. At the moment, I actually have to SSH it into the server and put the configuration into the file. Okay. Does Erlang have the eval? Sorry. Does Erlang have eval? Eval, it doesn't function. I have no idea. Run a piece of string as code. No, because you would have to have also a compiler included in your build, right? It's a compiled language. Ah, okay. Well, because usually that's what used for a configuration file. No, so this is, I think it replaces just some placeholder in the code with the value. I think this is done on compilation level, right? This is replaced. I think conform itself is not a whole lot. Okay, yeah. There's a lot of different things going on there. They've actually got a schema file. Well, if you compile the thing on the target, then you can put some secret there. You don't have to put it in the code base. And so the secret is actually compiled into your... Yes. Yes, yes. Yeah, but there's pros and cons of this. I mean, it's like if you are using Phoenix and you're using Phoenix configuration files, right? It doesn't work very well with XR. Yeah, I haven't tried this with Phoenix. I'm sort of avoiding Phoenix for the time being. I know it's a good web framework, but I don't know any tools. Yeah, so if you don't want to deal with this, this is really good if you are trying to build a release and you do hot code upgrade. This is where the band is. But if you don't want to deal with all this complexity, you can just run your code. You can click up code on the server and you can run it. No problem at all. It will run, it will work, and it is in this way so-called cross-platform. They're just shipping the source code over. Yeah, this makes it really easy. There's no real requirements for your server. You can deploy a binary straight to the ESR to set up the configuration file, but it will basically run. Yes, but there's a few caveats because in Phoenix, we are so used to getting system environments, right? But if you compile this, it's actually grabbing the system environments of the build machine because it is actually compiled to the bytecode for the configurations. So it's not grabbing the system environments when your machine went running? Yeah, in terms of your config file, if you put something like this in there, yes, that doesn't work. That's why you need something that's going to load a config file on when your application starts up in your production environment. Conform is good because it gives you this. This is a little bit complicated for a simple configuration, but you can have a schema. So when it loads your config file in production, it will give you a bunch of validations about whether the configuration is correct. You can have required properties with set all sorts of things in here. And that's just very good for, like, if you can deploy an app to somebody and they have to configure it themselves, it gives them feedback on whether the configuration is correct or not. So, yeah, I mean, check it out. All this stuff is about a lot of GitHub if you're interested at all. That's all. A few warnings and a few consistency issues. So when I first branded it before, this was the initial response. I had a bunch of warnings here. The warnings were for something relatively minor, I think. I had some parameters that were called the same as a function that was in the same module. They were pretty easy to fix. There were some consistency issues. All this is complaining about is that I sometimes I had spaces around operators and sometimes I didn't. Something can be fixed automatically. Yeah, exactly. So I fixed all the warnings. No warnings. I don't know if you have any. He analyzes the conventions you would have and compares which units more. Yeah, and then it says he's consistent. There are spaces around operators most of the time, but not yet. So if you had used the other convention, then it would have given me an error the other way. Oh, that's... Which is quite cool. Also naming classes was sometimes used. I don't know, of course, I don't know. And then, in the case, he would say, Decide, please. Well, it's not about being wrong. It's not about being consistent. So there are only remaining issues, though, that I haven't done in any of the module doc type of tags. Documentation. Yeah, no documentation whatsoever. Maybe I'll... These are the hardest ones. Yeah, yeah. Wow, the code itself is self-explanatory. But it looks like a cool library. I've been pretty stringent in getting rid of the vital warnings and stuff so far.