LongCut logo

Building a Concurrent Cinema Booking System in Golang

By Tiago

Summary

Topics Covered

  • Pessimistic Locking Beats Optimistic Here
  • Dependency Injection Swaps Storage Seamlessly
  • Solve Double Booking with Mutex Locks
  • Temporary Holds Prevent Checkout Failures
  • Redis SET NX Enables Atomic Seat Holds

Full Transcript

Hello everyone, my name is Thiago and on this video I'm going to show you how we're going to build this cinema booking system from scratch in go and we're going to also use some other technologies like radius. But the most

important bit is that I'm going to show you how to think and how I'm going to solve this specific problem because there's some interesting things here that you're going to solve like the double booking problem. What happens

when two users concurrently try to select the same seats? We're going to solve all of this on this video and then we're going to build it in Go. We're

going to start pretty low level. I'm

going to show you how I'm going to think I'm going to design this and then we're going to just solve it in Go. I'm going

to show you the the problem that and why Go is really good for this using concurrency. And this is going to

concurrency. And this is going to introduce you to the concurrency mechanism in Go. And then we're going to start moving up our solution a bit to more a production ready system where

we're going to add readies. This is

going to make our data persistent and then it's going to also allow us to solve some of the problems that you'd solve more in a production project.

Okay. So, let me just show you before we design this how it's the final product we're going to achieve. Here I have two browser windows. One on the left, one on

browser windows. One on the left, one on the right. This is already a cinema

the right. This is already a cinema session. So, I have two movies, one for

session. So, I have two movies, one for Inception, another one for Dune.

Just some random movies I selected. no

specific reason. But if I select for example, you can see that there's already some seats booked. These weren't

me. So not these users. I'm going to select for example this seat the five.

And as you can see the first thing is that it automatically showed on the user on the left in real time. We're going to use pooling for this. We could solve this with websockets but this is something a decision that we're going to

do later on. The most important bit is that this is a system or a mechanism that we're going to create checkout sessions very similar to like an

e-commerce. So some e-commercees solve

e-commerce. So some e-commercees solve this way. Um flight booking systems also

this way. Um flight booking systems also do something similar. So if you if you buy a ticket, you're going to have a pre-selected seat and then you can change it. This is something very

change it. This is something very similar to that approach. The reason for this is that the first is that if we didn't have this, we could still solve the problem. This is how we're going to

the problem. This is how we're going to solve it. But then this is kind of a

solve it. But then this is kind of a mechanism to make sure that it gives a better experience to the user so that he can actually see that someone is already trying to choose for example I'm going to cancel hey I'm going to choose this

one actually you can see that someone is trying to book this seat and for example when you're buying a seat on a on a flight this is something very similar as well so it's very important that the

seats are reserved and this is a place where there's a high contention there's a lot of users trying to book the same seat maybe so you want to avoid and implement some mechanisms that make this

easy. Okay. So, for example, I'm going

easy. Okay. So, for example, I'm going to cancel uh my hold. So, this is a shakeout session. I'm going to release

shakeout session. I'm going to release it. You're going to see that it

it. You're going to see that it disappears on the left user, but I'm going to choose, for example, this one.

The same thing is going to happen here.

So, we're going to see that, hey, someone is booking this seat. So, we

cannot actually book it. We're going to get an error. Uh but now, if we actually approve this seat, this is going to be a hold, a temporary hold on this seat. Now

it's a permanent hold and no one can actually book it because someone is already bought a ticket for that seat.

So we can actually choose the one on the left and also confirm it. As you can see everything updates in almost real time.

So now that we know how this is going to work, let's actually gradually get there before we can just write the code for this. So the first thing is that I want

this. So the first thing is that I want to start simple and gradually show you the problem and then multiple solutions because you might want might not want to get to the best solution and my solution

is not the best one. You can get even more complex but that is not the goal.

So I'm going to actually justify the increase in complexity that I'm going through. Then we're going to build the

through. Then we're going to build the solution in a clean and reusable composible way. So we're going to use

composible way. So we're going to use what I always teach in this channel, the repository pattern, the clean layered architecture. This is going to allow us

architecture. This is going to allow us and as you're going to see, we're going to inject dependencies which is going to be the different implementations that we're going to work. So we're going to create a in-memory implementation. Then

we're going to create a concurrent implementation. Then we're going to use

implementation. Then we're going to use radius. And all of these are going to be

radius. And all of these are going to be injected and composable like Lego pieces.

Okay. So this is pretty much it. Let's

start with kind of our happy flow. So

these are are some functional requirements here. I have kind of a

requirements here. I have kind of a sketch of the the cinemas UI that we have. So let's actually kind of start

have. So let's actually kind of start with the synchronous approach. What does

this mean? What is the simplest approach that you can actually go with? Let's

imagine this is kind of a a physical ticket counter. So we're going to have

ticket counter. So we're going to have like someone here on the counter. We're

gonna have some people in line trying to buy a ticket for that cinema. Okay, so

imagine that this is a queue. This guy

comes here, he buys a ticket for, for example, these seats. He gets selected.

Now the next person comes in, buys another ticket. He cannot buy this one

another ticket. He cannot buy this one because hey, someone has already bought it. We can know that exactly because the

it. We can know that exactly because the tickets, the ticket counter, the ticket master knows that, hey, this ticket has been sold. There's no slot for this

been sold. There's no slot for this seat. We're going to actually choose

seat. We're going to actually choose this one and so and so on. As you can see, this solution works pretty well because we're going to completely avoid the double booking problem. So, perfect.

However, it's very slow. Imagine that

this was the ticket line to actually buy, I don't know, the GTA 6 when it comes out, there's going to be a huge line and yeah, it's going to take forever for the cinema to be filled. So,

here we have a scalability scalability issue. Let's go to the next solution.

issue. Let's go to the next solution.

What happens if we make this a synchronous? And it's not really a

synchronous? And it's not really a synchronous. It's more parallel. I'm

synchronous. It's more parallel. I'm

going to create two ticket lines. Okay,

maybe three. So, let's just keep with two. I think you get the idea. So, we're

two. I think you get the idea. So, we're

going to have two ticket lines for the same movie. Now, if this person comes

same movie. Now, if this person comes in, we can buy two tickets at the same time. But here is where the problem

time. But here is where the problem comes. If these tickets are not

comes. If these tickets are not coordinated, if this concurrency is not coordinated or this parallelism, what's going to happen is that they might sell the same ticket for the same seat. So

they're going to sit on top of another.

Of course not. So they're going to there's going to be a problem. Uh and so what happens is that we have to create here a component somewhere in the middle

that is going to coordinate these tickets and the seats that we want to sell. And here comes kind of a a locking

sell. And here comes kind of a a locking me mechanism. The pessimistic approach.

me mechanism. The pessimistic approach.

What happens is that hey, if this ticket is being sold, let's wait. So this queue is going to have to wait. Wait. He gets

a seat. Now we can proceed. You can see that of course this kind of defeats the purpose of having two cues, but not really. We're going to see how this is

really. We're going to see how this is actually not that bad. Now another

interesting approach could be for example a movie specific queue. So very

similar to what we have just done. But

if this were a ticket line for one movie, this is a ticket line for another whole movie. So different cinema

whole movie. So different cinema experience as you see. So this is two rooms. We could sell specific lines for specific movies. That is going to help

specific movies. That is going to help it. That is a part of the puzzle that is

it. That is a part of the puzzle that is pretty interesting, but it would still not solve the scalability issue because if one movie was very famous, hey, we're going to have the same problem again. So

we're going to have a huge queue for that movie whilst the other one might be empty. Right? This is kind of the

empty. Right? This is kind of the celebrity problem that we have for example on Instagram, right? A profile

of someone might be highly requested while the other requests or the other profiles are just they have normal traffic. And so we get to kind of the

traffic. And so we get to kind of the online booking because what happens if instead of cues physical cues because we are um limited with physical space with this physical counter what happens if

you move to an online booking system?

This is exactly what we're going to build. And even on this online booking

build. And even on this online booking session, we still have the same problem because two users can select the same seat. So if they go here, you can see

seat. So if they go here, you can see that someone is going to book this seat.

But this is where we're going to implement concurrency mechanisms and we're going to still keep the idea of the pessimistic approach of the lock.

There's another approach that I want to show you which is the optimistic approach. So this is kind of the problem

approach. So this is kind of the problem I have talked about. So I'm going to move away. Um I have already talked you

move away. Um I have already talked you about uh what is the the problem here and the solution. So we're going to kind of lock the other user from reading the

same seat while we're trying to book.

This can happen in milliseconds or even lowest as you're buying. We're going to implement something a bit more conservative because as you see we can click on this seat. This is going to

create a session and this is going to be quick session but until that we're going to hold this seat. I want to say that there's problems with this approach. For

example, a user might try to book or hold a lot of seats. Of course, as we're going to you're going to see, we're going to implement or solve that. So, if

I move to the other cinema, I come back, I lost the the hold. So, we're going to clean that and head radius is going to be perfect for this because it's going to allows us to solve this pretty much

natively. Now the other approach that

natively. Now the other approach that you might be tempted to using it's it's even better solution I think not for this problem I'm going to explain to you why I want to go with the pessimistic

approach and both solutions are not one is better than the other that is not the case it depends on the problem you're trying to solve and for this problem I think that the pessimistic approach is

going to win because we really don't want to have two users booking the same seats and this is for us very important and we also want to have a good experience is on the UI so that once

once you're trying to enter your payment details, you don't want to click pay and then you're going to have an error because the seat is already bought, right? Someone bought it faster than

right? Someone bought it faster than you. So, we're going to still use the

you. So, we're going to still use the pessimistic approach, but highly recommend you investigate this optimistic approach because you might have to use something like this on your career, especially if you have a a

system without uh where the conflicts are much more rare than this because here we have reserved space. We have

physical room. There's more chance of conflicts. But an optimistic approach is

conflicts. But an optimistic approach is usually a best approach because we don't deal with logs or anything like that and the problems that locks might have. So,

this is kind of the idea. Uh just to iterate again why the cinema approach is best. I think I have already said but

best. I think I have already said but the cinema booking has high contention by design. So popular showtimes limited

by design. So popular showtimes limited seats and opening night rushes are some of the problems that we're going to solve with our approach. And it's also a way for me to actually show you how go

concurrency and mutexes and other concurrency mechanisms work in go for me to show you. And then we're going to separate the video into a couple of parts. This is a long video as we have

parts. This is a long video as we have seen but we're going to start very low level and then we're going to make this system scale. So we're going to go

system scale. So we're going to go distributed. We're going to use radius

distributed. We're going to use radius for that and then we are going to open the gates for everyone. We're going to build a web API using the business logic layer we have built. We're going to

build an API on top of that. We're going

to serve that and we're going to have this you can actually book your um seats with. Okay. So check out the first link

with. Okay. So check out the first link in the description that is a self-made engineer community if you want to learn more about how to build uh production

level APIs in go. I go over that there.

Um but other than that let's actually get coding. Okay. Now that we know what

get coding. Okay. Now that we know what we need to build, let's actually start coding it. For this I'm going to focus

coding it. For this I'm going to focus on two main topics, two main problems that we have talked about. So the first is that we're going to focus on the problem of double booking. So, we're

going to have, as you saw on the UI, the cinema layout that you can actually choose uh the seat you want and then you can actually click on the seats that you

want and we are going to have to prevent that no other user can book your seat otherwise they're going to watch the movie standing. And this is exactly our

movie standing. And this is exactly our biggest seller, our biggest focus for this solution. And that is why we're

this solution. And that is why we're going to focus on a pessimistic uh solution as we have talked about. But

whatever solution you go for, it's fine.

I just want to show you the the essentials or the fundamentals of go and how that translates to this problem. And

we're going to get there in just a bit.

And then the second one, the second focus I want to do is how to handle for when a user starts booking at the same time as another users. So the same

problem from both but there it's not exactly the same problem because yes you can prevent double bookings when you pay for example when you finish your booking but usually on these types of

applications when you click on your seats for example imagine like you you're booking a flight usually the company already creates a seat for you and then you can change that seat if you

want. We're going to do something

want. We're going to do something similar for the cinema room so that they can prevent conflicts at the end of the day because if we can already create a hold for the user then we don't need to

focus much on conflicts. So just two ideas that are going to be pretty interesting and again we're going to solve this gradually. So the project architecture needs to accommodate for

this need for this gradual uh increment of our complexity and for this I always use the same layout. So if you taken any of my courses, any of my videos, you

always see this pattern. It's the

repository layer or the the clean layer, the architecture. We have here a layer

the architecture. We have here a layer which is a service. I already have here a bit of code. I'm going to show you exactly what I have. But basically, I wanted to also give you an opportunity

for you to actually um give a try and build it yourself some of the bits and then I'm going to show you how I did it.

But basically we have the service layer which is nothing more nothing less than just uh a book function. It's going to be a list of the bookings all of that and then the storage layer that we're

going to inject different layers. So

we're going to start with the memory store implementation. And then we're

store implementation. And then we're going to use dependency injection to actually inject this store into the service. And then once we have this done

service. And then once we have this done we're going to run some tests. So I have here a test already on the internal booking. This is a test that we're going

booking. This is a test that we're going to use to actually test if the concurrency is actually working. And as

you can see, we're just going to pass here a store implementation which is we're going to pass as a dependency to the new service. And we're going to run the code. And the behavior should be

the code. And the behavior should be pretty much the same but with a different store. And this is how we're

different store. And this is how we're going to actually test for our concurrency problem. Okay. So the test

concurrency problem. Okay. So the test is going to fail if we have a race condition. So if we have the problem of

condition. So if we have the problem of double bookings, it's going to pass if we don't have that problem. So take that into consideration because this is what exactly dependency injection is. So

quick explanation for you if you don't know. Um and then let me just close

know. Um and then let me just close this. So it's distracting. Uh yeah. So

this. So it's distracting. Uh yeah. So

then we're going to build another solution. It's going to be the

solution. It's going to be the concurrent store. This is going to fix

concurrent store. This is going to fix our problem. We can actually stop here.

our problem. We can actually stop here.

But the thing is that we're going to increment a bit more complexity because later on we're going to build a web API on top of this project. Meaning that we want to serve this functionality to

users. As you can see the UI, you

users. As you can see the UI, you already have it here. Just an index.html file with some JavaScript, some CSS. You

don't need to worry about this because we're going to serve this from our go.

So no React, nothing like that. a simple

HTML file that we can actually have some UI and functionality that you can accompany your API with. And for that we actually need some persistence and we're going to solve the problem in two ways.

So this is more realistic to what you do in your job or in a project which is actually using some persistent storage.

We're going to use radius for this. The

reason that I'm going for rad is it's easy to teach you this problem and it's actually a cool technology that I don't show much on this channel. This could be definitely and it would even be better

solved with Postgress because most likely if you're building a project you need some persistent storage and so Postgress comes for that but because this is kind of a temporary um fast

storage fast access storage I'm going to use Radius for that this is an opportunity for you to also run radius in a fun project not to say that Radius is the best tool for this it's actually

pretty interesting because it solves the problem of this race condition and the uh temporary holds like this one we're going to do and radius is actually

perfect for this. You're going to see in just a bit why. So this is the plan for the next minutes. We're going to um implement this gradually starting with the memory store. Okay. So just to give

you a quick overview of the project it's pretty much empty. I have just created some files to accelerate a bit the the project. But you can definitely check

project. But you can definitely check the kit hub repo and h get the whole file completed if you want. Especially

if you want to copy the static, I would do this copy that file at least and pretty much the rest is just I have the main package here. This is the separation that I usually do. So the cmd

I have this executable and everything on the business logic is inside of internal. I have another folder called

internal. I have another folder called booking. This is our booking business

booking. This is our booking business logic. Pretty much everything is going

logic. Pretty much everything is going to be inside of this folder. And here I have this domain.go kind of a types. I

have started with this so that the test uh has already it has some errors but still we avoid some of the errors because I have already defined what a booking is. This is exactly what I have

booking is. This is exactly what I have defined. So booking can have an internal

defined. So booking can have an internal ID. We're not going to focus much on it

ID. We're not going to focus much on it for now. It's going to have a movie ID,

for now. It's going to have a movie ID, a seat ID, and a user ID and a status.

Now, what does this mean is that a booking is going to be related to a movie, right? So, we can have movie A,

movie, right? So, we can have movie A, movie B. That's basically how we're

movie B. That's basically how we're going to separate the the the rooms as you saw in the UI. And then we have the seats. This is the most important

seats. This is the most important because we're going to define the seats in the booking. So, we can you cannot define uh two bookings for the same seats. And then this is who is the owner

seats. And then this is who is the owner of this booking. And this is the status of the booking. Just some metadata, not too important for now. Next, we have another important part. This is the

interface that we're going to use dependency injection for. So we're going to create multiple implementations of the same store. So as you saw the memory, the concurrent store, the rad store, all of them are going to

implement this interface. So this is how you can implement dependency injection in Go. Pretty easy. I really like this

in Go. Pretty easy. I really like this feature about Go. And we're going to start with just that. So let's create a new file called memory and then store.

So I'm going to say this is a memory store. This is going to be on the folder

store. This is going to be on the folder on on the package booking. And basically

what we need to do here is just implement this interface. I'm going to just copy these two functions and I'm going to put them here. Okay. So

I'm going to make these two functions of a strct. So this is going to be methods

a strct. So this is going to be methods and the strct is going to be called memory store. There we go.

store. There we go.

There we go. How are we going to map first and foremost how we're going to map the seats to a booking? This is exactly what we need to store as we're going to move

into radius. We're going to move this

into radius. We're going to move this into a distributed storage. But for now, we're going to keep everything in memory. So the best data structure to to

memory. So the best data structure to to do this is actually a map. So we're

going to call this uh bookings. It's

going to be a map of a string to a booking strct. So this is why I wanted

booking strct. So this is why I wanted to have this booking strct already created because you can just consume it without having to define it. And now

that we have the map, let me create a constructor. I always like to create a

constructor. I always like to create a constructor so that we can easily replace it on a test as we're going to see in a bit. And we're going to actually return a memory

uh store actually a pointer to drits.

And I'm just going to return a memory store like so. Actually before I do that, let me initialize this map. Pretty

important. So I'm going to say um bookings or in this case map of string to booking and I'm going to initialize it so that we don't have any runtime

errors. And now let me actually make

errors. And now let me actually make these methods from these start. So this

is going to be a pointer receiver. And

now these are basically methods of this strct. So we can say memory store.book

strct. So we can say memory store.book

memory store.list bookings. Okay, let me make this into functions.

There we go. Now as a challenge if you want I highly advise you though pause the video here and try to implement the rest of this function of this file. It's

pretty simple. You don't need to know to know any other files. Basically, what

you're going to do is that you have the map, you can create a booking there and you can list bookings. Give that a try.

Just try to obey the same syntax or the same interface so that you can actually replace it in a test and see if it works. Cool. So, let's actually solve

works. Cool. So, let's actually solve it. Hopefully, you gave that a try. If

it. Hopefully, you gave that a try. If

not, it's fine. I'm going to show you.

It's not rocket science. It's pretty

easy, actually. Uh kind of my thought process here is that I'm going to access the map. I'm going to check hey is this

the map. I'm going to check hey is this seat taken basically. So I'm going to see if the seat is taken. If the seat the seat is taken I'm going to return an

error and if the seat is not taken I'm going to actually populate the seats and this is basically the algorithm for this. So pretty simple. Uh let's

this. So pretty simple. Uh let's

actually do this in line. I think we can check if exists on the store dot bookings and then we have the booking dot uh for this we actually need to use

the seat id okay so we're going to check by seat I forgot to mention but this is the seat ID so for example it could be uh a tree so this is the row and the

column probably it's inverted but you get the idea but this is this is start to this booking there's probably better

ways to solve this. I like the map because of the fast axis of of one. Um,

but either way, this is a pretty interesting solution. The only thing is

interesting solution. The only thing is that don't go for a nested map if you want to create kind of the rows and the columns. I wouldn't go that far. That is

columns. I wouldn't go that far. That is

not uh optimal in the first place. Um,

so yeah, let's now go to the the function and I'm going to just say that if uh in a line if the booking does exist, we can actually get a boolean.

This is a boolean in case it does exist.

It's true. And then we're going to evaluate it to true. Hey, is it exist?

So is it true? Basically the same as doing this. If this is true, I'm just

doing this. If this is true, I'm just going to return an error. So the error I'm going to do it's going to be errors dot new and I'm going to say that the

seat is already taken. Okay, instead of doing this error here in line, what I'm going to do is that I'm going to move this into a constant into a variable. So

I'm going to go here to the domain.go

and I'm going to create some errors in this project. I'm going to already

this project. I'm going to already create it inside of uh um parenthesis and I'm going to say that this is going

to be called error sit already booked and then I'm just going to place here the errors new and I'm going to import by saving the errors package. Now we can

just use this error constant like so and wherever we use it elsewhere in the project we can just reuse it like so instead of just typing the error. This

is going to be interesting once we get to the web API part and we want to return a status code depending on the type of error. Now the path that the in

this part of the solution is just go to the store bookings booking dot seat id and then use the um

start the booking itself. Okay. And at

the end we actually need to return a new letter because we don't have anything.

We are uh we have to actually return it either way. Now this is pretty much the

either way. Now this is pretty much the book implementation. If you actually

book implementation. If you actually went with the ID that is fine but um yeah I think that is fine if you even go with ID. I'm going to use the seat ID

with ID. I'm going to use the seat ID because it's the source of truth. That

internal ID is going to be useful for radius. Before that, I'm not going to

radius. Before that, I'm not going to even touch that ID for this inmemory implementation.

Uh, this is the book. Can we actually run the service? Uh, let's see. No, we

cannot run the service because we actually need to also implement the list bookings. Okay, so I'm going to do that

bookings. Okay, so I'm going to do that as well pretty quickly. So, this one is easy. There we go. I have just

easy. There we go. I have just implemented this off camera to speed up a bit the the the recording. But

basically we are just uh iterating over the bookings we have. We're going to find the movie by the ID and then we're going to append all the listings for this movie. Basically this is what we're

this movie. Basically this is what we're doing is doing a a filter a filter and return from the bookings we have on the store and then we're going to return what we find. Okay, this basically finds

all the bookings for a specific movie.

And now that we have the store implementation, we can see that we are going to uh be able to implement this store. So if I do new memory store and I

store. So if I do new memory store and I come here and actually um what do we need too many arguments? Oh that is because the service is not made yet. I

have just this skeleton but the service actually needs to have a couple of things. The first is that this going to

things. The first is that this going to be the dependency injection part where we're going to actually inject our dependency our store. And now that we have a booking store interface very

important this is not any concrete notation. This is the interface. We can

notation. This is the interface. We can

just put it here. And then on the constructor, we can actually say store is going to be a booking interface. And

then this allows us to actually do something pretty interesting. It allows

us to pass the inmemory store. Later on, we're going to create another store. We're

going to be able to pass it because both of these stores are going to implement the same interface, which is this one.

As long as we implement these methods, we can inject anything inside of here.

Okay, if it doesn't implement, for example, let me actually show you. If I

make this uh I don't know, a string, the book now returns a string, which it shouldn't. We can see automatically that

shouldn't. We can see automatically that we're going to have this error. It says

uh memory store does not implement booking store because the format is wrong for the book function. It actually

requires an error, but we are returning a string. This is one of the things I

a string. This is one of the things I really like about Go is this implicit implementation of the the interfaces.

Let me just change this to error back again. Uh and now we have no errors. We

again. Uh and now we have no errors. We

actually need to still implement the book. Makes sense. Let's go to the

book. Makes sense. Let's go to the service. And basically we need to create

service. And basically we need to create a method that is going to receive. Let

me call this S for service. It's going

to be a pointer receiver to the service and it's just going to call book pretty similar to the exact that we have on the store. The reason that we're kind of

store. The reason that we're kind of duplicating is so that we can actually uh have that project structure where we can inject dependencies that will maybe

in the future you want to pass here a logger you want to pass um I don't know any other dependency you want to pass you can actually just do it here on this service layer and not on the storage

layer. If this doesn't make sense, I

layer. If this doesn't make sense, I teach this in detail on the the self-made engineer on the back end engineer course. Click the first link in

engineer course. Click the first link in the description if you are interested.

But uh this is kind of the whole idea behind that. The book is just going to

behind that. The book is just going to call the service.book

and it's going to send the booking. We

can actually just return this because they have the same signature of returning an error. So we can just do this. Uh I deleted S by mistake. Let me

this. Uh I deleted S by mistake. Let me

revert it. And now we have no errors on the test. And what we're going to do is

the test. And what we're going to do is that we're going to run this test. You

don't need to understand what is going on here. We're using some weight groups

on here. We're using some weight groups to make sure that we can actually get the successes and failures of all these gins. But at the end of the day, what

gins. But at the end of the day, what we're doing is that we're going to launch 100,000 gins. And this is basically to simulate

gins. And this is basically to simulate that we're going to have 100k users trying to book a seat at the same time.

And now imagine this scenario that you're trying to book your seats A1 and we have 100,000 people booking at the same time. This is kind of the the the

same time. This is kind of the the the scale that we're going for. Of course,

this is a very exaggerated scenario. Uh

but still we're going to be able to uh pass this test. In this case, we're going to fail because we're going to see we're going to have a race condition.

Okay, let's actually I'm going to open here the terminal and I'm going to do a go run and I'm going to say I'm going to run everything inside of this project.

I'm going to use the verbose flag and it might pass. It might not because there

might pass. It might not because there is a risk condition. There's a a a chance in 100k,000 users that they might overrite the same seats and there's

going to be two bookings. Let's actually

see if we are lucky.

Oh, I forgot to actually do go test and not go run. So, go test with the v flag.

So, I can see the test actually passed.

So, we got lucky. Let's try again. And

as you can see, the test got cached. So,

what I'm going to do is that I'm going to say count equals one. This basically

means that we're not going to cache the test. Uh, as you can see, it's still

test. Uh, as you can see, it's still passing. Let me try a couple times. So,

passing. Let me try a couple times. So,

it actually failed this one. So, we can see that we have a huge huge error.

Basically, we're trying to concurrently write at the same time on this map. And

the best and the most important finding here is that go maps are not concurrent safe. Very important. If you want to

safe. Very important. If you want to make a go map concurrent safe, you need to add any concurrency mechanism that you're going to add like a mutex. Okay.

How can we make sure that there's actually a race condition here without running 20,000 tests like I did? You can

actually use a flag in go called race.

And this is going to detect if there's a race condition. This is a more secure

race condition. This is a more secure way of actually testing what we're trying to do. So if I just run this, you can see that it's going to take a bit

more time, but we have a race condition detected on the test. Okay. So our goal now for this uh part of the lesson is that we're going to try to fix this

issue. How can we avoid this concurrency

issue. How can we avoid this concurrency issue on our store because right now 100k users trying to modify the same key on the same app. Yeah, that's that's no

good. So let's actually fix that. Okay.

good. So let's actually fix that. Okay.

So what I'm going to do is that I'm going to create a new file called concurrent store uh go. I could call it concurrence memory store to be more

correct but this name is already huge as it is. Uh this is going to be inside of

it is. Uh this is going to be inside of the booking package as well. And what

I'm going to do is actually I'm going to copy and paste everything inside of the memory store. I'm going to copy

memory store. I'm going to copy everything because the implementation is going to be the exact same. And the only difference is that this is going to be

called concurrence store. Let me actually remove this and

store. Let me actually remove this and everything here. I'm going to replace it

everything here. I'm going to replace it with concurrent storage. Uh what am I missing here? Concurrent storage. There

missing here? Concurrent storage. There

we go. Okay. The only thing I'm going to change is that I'm going to add a concurrency mechanism. So I'm going to

concurrency mechanism. So I'm going to add a sync from the sync package from the standard library. I'm going to add a mutex. Okay. You have two types of mutx.

mutex. Okay. You have two types of mutx.

I'm going to use the read write mutex because this one is going to be um actually I'm not sure if you're going to use the functionality of this. Uh oh no we are going to do that on the listing

book is way I can actually show you the the real lock but the idea that I want to show is that there's locks right and we're going to solve this the pessimistic way with locks and basically

what I'm going to do is that as we saw on the diagrams before when we were talking about the solution we're going to go for the pessimistic route so we're going to lock so we're going to lock

this resource when someone tries to access this I'm going to already very importantly I'm going going to defer the s do unlock. Very, very important. I'm

going to put this in a defer because once the function returns, this is going to be called pretty much. That's what

defer does. You can hover over probably h you can see what it does. But this is pretty much what we're going to do for this solution. Just lock it and unlock

this solution. Just lock it and unlock it once it returns. This means that another concurrent user trying to read the same resource as we are the same

seat. He's going to have to wait until

seat. He's going to have to wait until we unlock this lock to actually read it.

Okay? Or in this case to actually book it to modify it. Now for the reading part, I'm going to do something similar.

I'm going to actually copy and paste this paste it here on the top. But

instead of a normal read lock, I'm going to use a right read lock. So if you see what the read lock does, it locks the read write for reading. So it should not

be used for recursive uh locking. It

doesn't specify much but basically you usually use read locks for reading. I'm

trying to put this into few words.

Basically what this means that kind of the definition should use that use this when you want to modify data and you actually need to uh ex exclusive access right. So when I'll create a lock but

right. So when I'll create a lock but for reading resources you can actually just use read lock because this is going to allow other gurins to read the data and that's fine okay that's fine to to

just reads data but modify we want to really lock hopefully you understood that but most importantly you could even just solve this with the locks that that would be fine that would lock that would

uh work just like this but we're going to be a bit more correct and use the bit lock let's actually replace the implementation of the memory store on the test with this one. Let's actually

see what happens. So again, this is kind of the beauty of dependency injection.

I'm just going to replace this. It works

because the start implements the interface and I'm going to run the test and let's see what is going to happen.

So go uh I'm going to use the same commands with the race flag. And as you can see, takes a while. It works. If it

works with the race flag, I'm pretty confident that it will work. and without

it because what we're trying to actually solve is to avoid risk conditions. And

if you don't have risk conditions, it means that we're not going to have double bookings. But you can be sure you

double bookings. But you can be sure you can run it multiple times, but it doesn't going to change the results as you can see. So we have just solved our

double booking problem from our initial design documents. Okay. So this is kind

design documents. Okay. So this is kind of the most important part of this video is to actually understand how you can solve concurrency with concurrency mechanisms in Go. This is one of them

mutexes a pessimistic one though but still it's one of the most used and the most important to understand because you can get to work with mutexes you understand how maps are not concurrent

safe if this would be another problem for this case it doesn't make sense but if you want to pass data around goins you might use channels that's other stuff that I cover here on the channel

but for this use case it doesn't make sense so what we're going to do is that we're going to move one step higher on abstraction and we're gonna actually implement radius here and see how a

different storage a persistent storage a real world uh database that you might use works for this project the same way as our concurrent stars work and our memory stores.

Okay, so before we actually code the radius flow, let me show you how this is different from what we have been doing.

But at the same time, it's actually the same. We're going to step up a bit. This

same. We're going to step up a bit. This

is going to be more production ready. So

we need to take in consideration some things. The first is that this is going

things. The first is that this is going to be divided into two flows. And this

is very interesting because this is kind of a pattern that you're going to see in a lot of software use. For example,

we're going to create a session which is going to hold the seat for some time to leave. So we're going to reserve the

leave. So we're going to reserve the seat for example 5 minutes, 3 minutes and then if the user pays or confirms. So this is going to be an abstract action. We're not going to implement

action. We're not going to implement payments. We're just going to create a

payments. We're just going to create a confirm button. But the idea is the

confirm button. But the idea is the same. Once we confirm or buy that seat,

same. Once we confirm or buy that seat, this is going to transform into a persistent seat. So the seat is going to

persistent seat. So the seat is going to be taken until this case forever uh or until the movie ends, right? So this is kind of the two steps. So the first is

that we're going to reserve the seat. So

we're going to avoid collisions from the start if we create a session. So what

happens that on the UI once we enter the the movie cinema uh seat selection if we click on a seat this is going to send a request to the API that we're going to

write to radius a TTL so a time to leave for this seat for example 2 minutes 3 minutes how much shorter we can go the better because this is going to it is going to

have some problems which is if you have some user trying to hold a lot of seats this is going to yeah you're going to lose some sales on that. We're going to mitigate this by once the user leaves

the web page, we're going to delete his reservation. But yeah, this is kind of

reservation. But yeah, this is kind of the disadvantage of our solution. Just

to point that out. But there's a lot of systems out there that do this. For

example, airlines uh the even cinema booking systems use something like this.

If you want, you can actually just delete this whole part and just rely on our optimistic on our pessimistic sorry uh part. This is going to solve it. So

uh part. This is going to solve it. So

once you click a sit and you enter your payment credentials and hit buy, if there's someone already doing this at the same time, like there's a one in the million collision. If someone does this,

million collision. If someone does this, uh you're going to get the error at the payment level. I don't like this because

payment level. I don't like this because yeah, you have just entered your credentials and now you're going to have that error. You're going to have to go

that error. You're going to have to go back, select a new seat. This is why I really like to add this bit of more complexity with the create session because it almost ensures that you're

going to have that seats that you hold.

Now, how does this look into code? It's

not that hard. I'm going to actually code with you a bit of the code and then I'm going to kind of speed up and show you the implemented code. I think that's more interesting than just typing it out. Again, you have access to this

out. Again, you have access to this whole project so you can just take your time with it. But I want to also teach you a bit of radius. So radius is an in-memory database and meaning that it

runs on a fast access for example using RAM. Now nowadays it rad is a bit more

RAM. Now nowadays it rad is a bit more complex than that but the idea is that we're going to set keys. So there's a key value storage. This is a radius command. So we can set this is a key. So

command. So we can set this is a key. So

this is a unique identifier. We're going

to say that it's going to be a seat.

We're going to say the movie ID and the seat ID. We could even add here the

seat ID. We could even add here the showtime. All of that. We're going to

showtime. All of that. We're going to keep this simple. just the movie ID and the seat ID. It's a unique identifier.

And then I'm going to store the session JSON. So some uh the booking information

JSON. So some uh the booking information that we just saw, we're going to actually store this inside of the the ready database. And the most important

ready database. And the most important part is this NX420.

What does this mean? These are Rady's commands or uh arguments. And this is the key part because what we're going to do is that we're going to use the set NX or the NX. And this is what the lock is

going to hold. This is how the lock is going to work because NX mini means that only sets only sets the key on the map.

So basically uh very similar to what we're doing. So it's going to only set

we're doing. So it's going to only set to the map. For example, with this locks only if the key does not already exist and this is already with the lock built

in. So we don't have to care about how

in. So we don't have to care about how radius implements this. Radius could do this any other way. We don't care it.

that we have the assurance that this is not going to have race conditions.

That's what's important for us and then the rest is just saying hey auto expire after x minutes because we are just holding the the the seat. This is where

I like radius for this problem. It's

because you have this TTL this auto expire. We don't have to have background

expire. We don't have to have background jobs running and cleaning the seats in case the user doesn't uh confirm. What's

going to happen is that once we create this key, imagine that we're going to add uh the seats A1.

This is going to create the seat for 2 minutes. After 2 minutes, Radius is

minutes. After 2 minutes, Radius is going to automatically delete this key and make the seat available again. This

is why I like Radies for this problem.

Um that is pretty much it for the first part. So if the key exists, you win. The

part. So if the key exists, you win. The

seat is yours. It's held for you. If

not, we're going to return the other that we have already built. And again,

this is kind of an atomic operation that Hadis does for us because radius is single threaded. So there's if two

single threaded. So there's if two simultaneously requests come in, they're going to be serialized exactly one winner uh gets the seat and there are no risk conditions. Exactly what we want.

risk conditions. Exactly what we want.

We don't care about the speed of the program per se. We care about the consistency of the data. It's very

important for our business that two people don't have the same seat.

Otherwise, as I said, someone is going to be standing the whole the whole uh movie. The second step is the confirmed.

movie. The second step is the confirmed.

This is kind of the most simple one because we're just going to say, hey, does this session exist? Let's validate

the user. Let's add some business rules here because this is kind of the payments. But the most important part

payments. But the most important part here is that we're going to persist.

We're just going to say, hey, remove the TTL of this key because it's permanently is permanently booked for this guy.

Okay. So this is pretty much it. Um

hopefully you got the idea. Let's

actually code. I'm going to be a bit more uh I'm going to speed this up a bit because um I think what's more importantly here is really understanding how this all works and not how to code

this because even with AI you can just prompt what I have just told you and he's going to implement this. But it's

this fine detail of knowing what needs to be done that software engineers are more curated towards nowadays. So I have

just added this docker compos yl which just has a service which is radius. I'm

also going to add another one which is going to be the radius commander which is going to be an interface. This one

right here. So this is just another service that is going to be a UI on the web that you're going to be able to see and inspect our radius keys because radius is usually CLI tool and this is a

nice UI for us to see it on the browser.

Again you can get access to the source code. You don't need to be typing any of

code. You don't need to be typing any of these on your own. So our ready store is currently empty. I'm just going to write

currently empty. I'm just going to write the skeleton of what we have done here.

So I'm just going to implement the same uh where is it? The same interface.

Okay. So, I'm going to do this and I'm going to see you in just a bit. All

right. So, thanks to the wonders of video editing, I have just worked on this file a bit. So, I have just built a skeleton. I think we're going to code

skeleton. I think we're going to code most of the things still. So, don't

worry. I have added two packages that I'm going to show you and imported in a bit. But first, I have created the

bit. But first, I have created the interface basically here. So, I have created a radct. I have added some documentation. So, this is exactly from

documentation. So, this is exactly from what I wrote on the diagram. This is so that you can reference the key and for us to reference as well. I have made this constructor and then here we have

our session key. So this builds the reverse looking key for the session.

This is what we're going to use to fetch the session on radius by a function. So

we can always get the same results. It's

just a utility function. And then I have added our book function that we already know and our list bookings. What's new

here is that I have already added some logic here. I have just added some

logic here. I have just added some logging and I am calling this private function called hold. This is the hold method that I showed you on the diagram.

This is where the bulk of the radius implementation is going to be. I have

just returned an empty booking. So it

doesn't have pretty much anything. And I

have actually made it with UU ID. So

this is going to be autogenerated ID for the user. And then I'm using the now uh

the user. And then I'm using the now uh from the time dotn now to actually add an expires at. We're going to have to update our booking abstract with an

expire at. Let me just do that. This is

expire at. Let me just do that. This is

going to be a time dot time um type. Let

me save that. And we should have uh what's going on here? Unknown fields. I

think it's going to compile once we get the other errors out. So what I'm going to do is that I'm going to import this package and I'm going to also import

this package. So go get uh radius v 9.

this package. So go get uh radius v 9.

So exactly this one and we should have on the go mods exactly um everything imported. As you can see we got no

imported. As you can see we got no errors. We still got these expires yet.

errors. We still got these expires yet.

So what's going on? Did I save this file uh or what's going on here? Um let me see. Oh, I misapped this. I wrote expire

see. Oh, I misapped this. I wrote expire that instead of expires at. Okay. So we

should have no errors now. Sorry for

that. So this is the an empty canvas for us to start implementing. So what we need to do is basically just interact with the radius CLI or the read SDK.

This guy here which is a dependency already in our strct. So we're using and we're using the same patterns we are always using. We have here the

always using. We have here the constructors. And now what we're going

constructors. And now what we're going to do, let's start actually here with the hold. And what we need to do is call

the hold. And what we need to do is call that nx h commands to actually get a hold of that temporary sit. Okay. So

let's start implementing this. The first

thing is that we're going to have to access the radius client from the start and then I'm going to say uh set args and this is going to require uh what do

we need from here? We need to send the context. Basically, this function is

context. Basically, this function is just sending uh a command via ready. So,

we're going to set set commands, but we're going to be able to pass more options as you're going to see in just a bit. I'm going to send in the context.

bit. I'm going to send in the context.

I'm going to actually create here as well the context dot background just like so. Uh the next one is going to be

like so. Uh the next one is going to be uh what do we need more? Uh it's not autocompleting.

Uh we need a key. Sure. So, let's send our key. I'm going to say um let me

our key. I'm going to say um let me actually put this into a variable as well. So it's going to be fmpt.print

well. So it's going to be fmpt.print

uh f and then is this actually the same?

No, this is the session. So we actually need the key for the seats. So this is going to be seats. We can actually make a function like this one for this. What

I'm going to do, I'm going to keep it here and if we need to reuse, I'm going to reuse it. Um create a function for that. If not, uh I think I think this is

that. If not, uh I think I think this is just gonna the only place we're going to use this honestly. But this the key of the seat as you saw here, it's going to be movie and the seats. So it's going to

be the booking dot movie. And then the other variable is going to be the seats ID. Uh we can pass in the key.

ID. Uh we can pass in the key.

And then the third argument is going to be um the value. the value just going to be I'm going to put it into a variable

because the value as you remember it's actually the session JS uh JSON so we actually need to marshall that uh let's

do that here so let me do value I'm going to ignore the other just because I'm lazy I'm going to say JSON dot marshall in go it's actually pretty easy

to marshall JSON let me actually do I'm doing this reverse but we actually need uh data and this is going to be a going struct so I'm going to say booking

um I'm just going to call it I think we can reuse the same booking that we have uh can we because actually what we can do is just say that the booking ID it's

going to be this new generated ID and we just say that the booking is going to to be this one I was thinking of creating a new strct for this but I think the

fields are going to be all these ones.

Yeah, I think so. So, let's just use the last argument which is going to be the radius set args. And here we send various uh

set args. And here we send various uh multiple things. If you see the

multiple things. If you see the documentation, we can send the mode which of which of them is going to be the nx. This is the one we want. This I

the nx. This is the one we want. This I

think it's going to write if it exists the same. So, it's going to override

the same. So, it's going to override this one is what we want. If the key exists, it's going to to draw the error.

Uh, and then we also need the the TTL. I

think this is pretty much what we need.

So, let's actually say mode. It's going

to be an X. So, this is what I say by set if not exists. And then the last argument is going to be the time to live, which I'm going to say uh I'm

going to put this into a variable called default holds TTL.

And we already have this variable here which I'm going to uh I have already defined this good. So it's going to be 2 minutes. If you want you can put it is

minutes. If you want you can put it is this like 7 minutes or something like that. I'm going to keep it as at two. I

that. I'm going to keep it as at two. I

think it's a reasonable number. Uh and

then yeah this is pretty much it. What

do we get from this? It's uh radius commands. So we get um do we actually

commands. So we get um do we actually get just one command? H

command? H okay. I thought there was multiple

okay. I thought there was multiple arguments. So if this is the response uh

arguments. So if this is the response uh from this response we can actually do uh let me actually double check what

this is going to be status cmd we get the value from it. I see okay because this is new syntax before there was let me actually show you because you might

see code using the sets nx but as you can see this is deprecated. I'm actually

still learning how to do this with this new syntax. But I think if you just

new syntax. But I think if you just check if the result is okay or in this case the result do value is okay. I

think this is going to work either way.

Um yeah, we're going to we're going to have to test this out. I'm going to say that if this is not okay, I'm going to return an empty booking. Okay. And then I'm going to say the error is the seats

already booked that we have already defined previously here on the domain.

Uh so yeah this is kind of the the hard part is just calling the NX function. So

we have just created an entry for a seat on radius. Okay. But now we also need to

on radius. Okay. But now we also need to create an entry for a session. The

reason for that is that this is going to be a reverse lookup for us on the other method. So I'm going to show you this is

method. So I'm going to show you this is going to make more sense. But basically

um store radius I'm going to say set.

This is much more easier because we already we're just setting a value. It's

going to be session key uh constructor.

I'm going to send the ID this ID right here. And then this is going to create a

here. And then this is going to create a key with this name. So it's going to be um session and then the session ID.

Pretty much what we have done for the SIDS. We can actually create a function

SIDS. We can actually create a function for this one. But as I said, I think this is the only place we're going to use it. uh and like this key we're going

use it. uh and like this key we're going to use on the list bookings. The key is going to be uh this one. And then the value uh actually this is not the value,

it's the yeah, it's the duration. So

it's going to be the default TTL. The

session is going to have the same TTL as the uh the holding. Okay.

And this is pretty much it. Okay. So for

the booking, this is it. So we're going to hold the seat. we have this functionality before we can actually um I think we can actually test this right.

I think if we call uh the new constructor on the test, this should work here on the service test right the only thing is that we

actually need to run an instance of radius because this requires um an instance of radius to work. So what I'm going to do is that we need to create a radius client. So I'm going to come here

radius client. So I'm going to come here into uh internal I'm going to create a new folder called adapters. This is what I like to have connectors. For example,

if you have a connection to to Postgres, you create here. If you have a connection to radius, you create another one. So, I'm going to say adapters

one. So, I'm going to say adapters radius, another folder, and then a file called radius.go.

called radius.go.

And this is where I'm going to just say um package radius. And to speed things a bit, I have just pasted this new client.

So, basically, I have reused this from another project. I just copy and paste.

another project. I just copy and paste.

Just to give you an overview, I'm renaming the name of the package goddess to to goddess so that it doesn't collide with the same package name I have. And

then I'm just creating a client. This is

how you normally connect to radius. I'm

pinging to see if the connection is working. If it's connected, I'm going to

working. If it's connected, I'm going to log and then I'm going to have the client the actual go radius client that we are using here on the rad store to

actually do the commands. Okay. So I'm

going to copy and paste this new clients right here. So I'm going to say radius

right here. So I'm going to say radius and then I have to import it from my own uh package. So let's see if it imports.

uh package. So let's see if it imports.

No, it's not this one. Let me see if it can manage itself. No, it's importing from No, it's not going to figure it out. Okay, so it's going to be

out. Okay, so it's going to be github.com my username and then the project name. I

think it's cinema uh internal and then uh adapters. Yeah, this one right here.

uh adapters. Yeah, this one right here.

It to actually shows adapters and radius.

There we go. This is the clients. And

then here's going to be the address. The

address is going to be local hosts. And

let me just double check here on the docker compose. It's the 6379.

docker compose. It's the 6379.

There we go. Let's actually docker compose up. So let me start radius for

compose up. So let me start radius for this to work. So docker compose up. Um

yeah, let's just run it.

Okay, there we go. We have radius commander running. And we also have our

commander running. And we also have our radius instance running. Let's actually

run the test and see if this works. I'm

also not sure what's going to happen. So

let's do go uh test and the same command as we did before. I'm going to just use the race flag to actually just see if there's a problem here. And hopefully we

have no race condition. It's taking a while probably because it's using radius and connecting to it. And as you can see, as I just said, it connected to radius. The test actually passed. So

radius. The test actually passed. So

awesome. It created a session. So just

one of these 100,000 users trying to book the same seat. One of them created a session and he managed to actually get a hold of it. This is the session ID.

the seat uh is this one. And as you can see, we have here the detail and yeah, everything is working. Let me remove the the rest flag. It's going to be much faster. And we got an error. Now, the

faster. And we got an error. Now, the

reason for this error, I wanted to show you this as well, is that the reason for this is that we actually still have a hold of the seat. The reason that we got the error because the seat is already

existing. That's why we got the error.

existing. That's why we got the error.

If you actually look at Freddy's commanders, I think it's this UI. It's

not found. What is going on? Um, for

some reason the the radius commander is not showing. I'll fix that in later. But

not showing. I'll fix that in later. But

what I can do is just docker compose down and I'm going to docker compose app. So I'm killing radius. I'm booting

app. So I'm killing radius. I'm booting

up radius again. And if you run the test again, it's going to pass because that key has already gone uh because we don't have persistence on that. So you can see that actually passed. If I run this

again, we're going to fail because the the seat is being held for two minutes.

Okay, so let's let me see if it actually works. It doesn't. Okay, I'll fix that

works. It doesn't. Okay, I'll fix that before actually publishing this project.

Don't you worry. Uh but yeah, this is pretty much the radius implementation.

Although let's now go to the second part or the last part of this video, which is let's provide this functionality to everyone. So let's create a web API

everyone. So let's create a web API where we're going to actually use our register and show on the UI how you can actually hold a session confirm the

session and yeah basically much pretty much how you can create a a rest API in go this is going to be a very fast crash course for you to do that so pretty

interesting uh let me close here the terminal and let's work on that okay so let's actually work on our web API in go if this is your first time working or

building a web API, don't worry. It's

pretty easy and I'm going to show you how quick it is to do it in Go. This is

one of the strong uh one of the things I really like about Go is how easy it is to create a web API without any external packages or dependencies. Um yeah, I'll just use a standard library for this.

So, I'm going to create a max which is just going to be an HTTP new serve max.

If you're coming from other languages, you might not be familiar with what is a max. It's basically a multiplexer, a way

max. It's basically a multiplexer, a way of sending a string, for example, a get uh users to a handler. This is what a multiplexer does. It's not tied to HTTP,

multiplexer does. It's not tied to HTTP, although it's from the HTTP package, but a multiplexer basically handles requests like a handler if you think about it.

I'm going to create a service. I'm going

to run it. This is how easy it is to do.

So, listen and serve. I'm going to run this on port uh maybe 42 or 8080.

Yeah, I think 8080 is going to be fine.

I'm not sure if I'm running anything on this part, but I think this is what the front end is expecting or I think it's expecting anything honestly because it's running on the same part. So yeah, it makes sense. Um the max is what we're

makes sense. Um the max is what we're going to send as a second argument. This

is the handler as I I said. So if you read here the documentation, HTTP.andler, I'm going to handle this

HTTP.andler, I'm going to handle this error in a line. And if there's an error, I'm just going to say log dot uh fatal and pass in the error for context.

This is it. This is the server. If I run the server right now, you're going to see that we actually have So, if I do go run cmd.m go, we have a server running.

run cmd.m go, we have a server running.

You can see the server that the the terminal is hanging because the server is running. However, we don't have any

is running. However, we don't have any handlers. So, let's actually work on

handlers. So, let's actually work on that.

basically is has impulse has saying max handle function. You can see that this

handle function. You can see that this is um the pattern we're going to send.

So the URL okay and then this is the most important part which is going to be the HTTP handler funk. I'm going to skip a bit on explaining stuff because I have

a lot of videos and courses on this channel on this. So I'm just going to say that this is going to be the get movies the movies listing which is going to be hardcoded and then this is going

to be the handler. It's going to be a list movies. I'm going to come down here

list movies. I'm going to come down here below and I'm going to implement this function.

Okay. So a couple of things I have just written to speed up a bit the recording but you can pause and type everything.

Uh this is the function we're going to use for the handler for the handler right. So the list movies it receives an

right. So the list movies it receives an HTTP handler funk a response writer and the request this is what we send to the users. This is kind of a uh mutable data

users. This is kind of a uh mutable data structure that you can use to send status codes write JSON. I have created this utility utility function that is going to receive some writer. It's going

to receive a status codes and it's going to return data. So I always use this function on all of my Go projects and I just paste it into a folder. So I'm

going to create here new folder utils and actually do we need to do this? I'm

going to just create the utils once we need but I'm going to keep here in the function because this is kind of where I'm going to keep the HTTP code on the cmd. Um but basically this is it. Here

cmd. Um but basically this is it. Here

are some hardcoded movies. This is the the structure for it. So the struct so it's a movie response. This is what the front end is expecting. So it expects uh

a JSON ID, title, rows, and seats per row. This is automatic marshalling in

row. This is automatic marshalling in go. And this is pretty much it. So here

go. And this is pretty much it. So here

are the hardcoded movies. This is the number of rows and the seats per row. So

this is setting the cinema stage as you saw on the UI at the start of the lesson. And now we have actually this

lesson. And now we have actually this get movies. Let's actually run the

get movies. Let's actually run the project and the UI and see what happens.

So I'm going to come here. I'm going to run again the go run cmd.main.

Okay, so I have here Firefox running and if I go to port 8080, you can see that we got 404 but it's not found. So what

is going on here? There's one step missing which is we got the /mov. So we

can actually go to /mov and we can see that we got the movies. So this is actually working but

movies. So this is actually working but our web UI so our static index.html is not being returned. We actually need to solve that. So to do that in go it's

solve that. So to do that in go it's pretty interesting. So just say

pretty interesting. So just say max.andle

max.andle and we're going to say that hey once you hit the slash homepage basically let's return the http dot file server and the

file server is going to be the http directory and that directory is called the static. So once we run the project

the static. So once we run the project on the homepage so on the root folder we're going to reference this static folder and because we have the index.html HTML. This is going to be by

index.html HTML. This is going to be by default the index page. So, let me restart the server. And if we just

refresh on if I go back to slash um to local host refresh this page, you can see we got our UI from the static.index.html.

static.index.html.

Right? So, this is our first step. We

can get the movies listing. If we click on one of those, you can see that the screen shows but the seats are not here.

This is the dynamic part that we need to fetch. The UI is trying to fetch but we

fetch. The UI is trying to fetch but we are getting an error. And the reason for that you can actually see here. Can I

drop this to the bottom? Uh yes.

Awesome. Cool. Cool. Basically what's

happening is that the front end is pulling for the seats every two seconds or so. And this is how you're going to

or so. And this is how you're going to get the real time fill. Of course you could solve this with web hooks but we can just simply use pulling for this.

Then you can see that the seats endpoint is 404 because we actually don't have it. This is the second part. Let's

it. This is the second part. Let's

actually implement the handler for the seats. This is basically getting this

seats. This is basically getting this function that we have not implemented yet here on the register the list bookings. This one right here. Let's

bookings. This one right here. Let's

implement this and then hook this up to a handler and then this is going to automatically show here. All right. So I

have just quickly typed the list bookings because this is not the main show of the the main focus of this show you could say. Basically what we're doing is that we're scanning the radius

instance for this pattern. So we're

going to check all of the seats.

Actually we should have reused this function now that I'm thinking about it.

And we're going to iterate each finding we we have. We're going to create that into a session. I have this parse session function basically just on Marshalls. and just encapsulates a bit

Marshalls. and just encapsulates a bit of this code. And then last but not least, we just return a sessions list.

Okay, the last missing part is to hook this to an endpoint. So here on the main go, we need to create another handler for this and then for the confirmation

and the holding of a seat. So three new endpoints we need to create. Let me

quickly show you what that is going to look like. I have here multiple cursors

look like. I have here multiple cursors for some reason.

Okay. So, let's have the handle funk and let's have the Now, for this handlers, I'm going to do something more interesting because we actually need

access to the store. So, I'm going to come here to the booking. I'm going to say handler.go. So, this is another

say handler.go. So, this is another layer. This is the transport layer. Uh,

layer. This is the transport layer. Uh,

and so it's going to be on the package uh booking.

I'm going to say that we're going to have to create a strct. is going to be the handler.

There we go. It's going to receive the service. So, this is going to be um just

service. So, this is going to be um just a service like so. We can actually pass a structure. We don't have an interface

a structure. We don't have an interface for now. And then I'm going to say new

for now. And then I'm going to say new handler. Let's just return a pointer to

handler. Let's just return a pointer to the handler and just do this. So, handler. There we

go. Now, we have one of the functions we need. It's going to be I'm going to

need. It's going to be I'm going to actually come here and say um booking handler.

It's going to say uh booking dot new handler. We're going to need a service.

handler. We're going to need a service.

I'm going to initialize it here. So svc

for short. Booking dot um new. Do we

have a constructor for a service? I

think we do, right? Yeah, we do. Which

receives a start? This is basically Oops. What have I done here? Uh, this is

Oops. What have I done here? Uh, this is basically what we do here on the service test. If you think about it, this is

test. If you think about it, this is pretty much what we have just done. I'm

going to actually copy and paste. Uh,

there we go. We're going to use the ready store which we actually need to import from uh booking and this one as well from booking. The connection is going to be the same. If you want, you

can put this on a different variable from it's fine. Um and then at the at the end of the day, this is a service that is a pointer.

Uh right, is that why it's not working?

Let me actually make this. Yeah, there

we go. Lists uh sits the name of this handler actually. Let me decrease here my

actually. Let me decrease here my Firefox window.

There we go. So we can fit everything.

Um list sits here on the handler. I'm

going to call this a handler uh from handler with sits and there we go. This is going to receive the same thing that we have done

here. So the HTTP handler funk I'm going

here. So the HTTP handler funk I'm going to copy and paste this here import it and there we go. This is basically the whole skeleton. We're going to of course

whole skeleton. We're going to of course implement the lists which basically at the end of the day just going to call the service dot store uh in this case the service.list lists bookings and this

the service.list lists bookings and this is going to return what we need to okay but before that let me double check what is going on here in terms of errors

because the service um it's not let me see the booking I that's because the booking doesn't I forgot uh to update the constructor

let me do this and now the may not go is working and it doesn't have errors okay good this is the first handler. We're going to have the lists. It's going to call the

the lists. It's going to call the service dot list bookings. Doesn't the service have? It doesn't have actually. So, let

have? It doesn't have actually. So, let

me also create the list bookings. So, we

don't directly access the store. That's

not good. Uh, and I want to list movies uh bookings from a movie filter. So, I'm

going to filter by movie ID. We can just do startlist bookings because we already have this implemented. We can just pass this in. This does return an error and a

this in. This does return an error and a list of bookings. So we actually need to say that we want a booking slice

uh and an possible error. So we can actually uh oh this doesn't return an error.

Okay. So let's just road it. Okay. Um,

the surface is actually done. We don't

need to touch it anymore. We just need to send here the movie ID, write to JSON, and that's pretty much it. Because

we need to write to JSON, I'm going to get this util function. I'm going to create here a folder called utils.

This is going to be um I'm just going to call it utils.go

package utils and paste here the write JSON. And now we can actually remove it

JSON. And now we can actually remove it from the main.co. I'm going to say utils write JSON. In a better world, we could

write JSON. In a better world, we could actually move all of this logic to a new file. I'm going to leave that up to you.

file. I'm going to leave that up to you.

If you want to move this into like a a movies folder and then create a handle like we're doing, that would be what I would do, but because we're just short on time, I need to to do this. And then

again, this is just for the front end to render. That is fine. Let's get the the

render. That is fine. Let's get the the movie ID parameter from the path. So we

can actually do do request dot path value and get the movie ID. So this

exact name here we're going to get it.

We can just send the movie ID here. This

is going to get our bookings.

Uh is this correct? It's list booking.

Uh I forgot the plural bookings.

There we go. And now I just need to send this to the user via the write JSON. So

I'm going to say uh utils write JSON. We

can just say the writer HTTP status.

Okay. Status. Okay. So this going to be a status 200. And then the data. This is

actually not going to be the bookings because what the front end expects is actually sits. Um so we have to do some

actually sits. Um so we have to do some conversion here. I'm going to just do

conversion here. I'm going to just do this off camera really quick and show you what this means. Okay, so I'm going to say that the seat is going to be a slice of seat info. I actually forgot to

move this from uh yeah, I pasted here, but I forgot to actually let me save this and move it to to here. Okay, now

we don't have any errors. Basically, I'm

just iterating over the seats from the bookings and then I'm going to create a seats entity because this is what the front end is expecting. Um yeah,

basically because of that the front end doesn't care about the whole booking information. If you want to hide some

information. If you want to hide some information from that like the ID and such you just need like yeah I'm just mapping the seat ID the user ID and just saying that that is booked so the UI can actually change the seat color to the

booked color. This is pretty much it.

booked color. This is pretty much it.

Let's actually uh test if this works. I

think I have everything set up and hooked. Um yeah, let's give that a try.

hooked. Um yeah, let's give that a try.

Why not? So let me restart the servers.

We can see already that we are connecting to to radius. Uh and if I refresh the seats are actually not showing. Did

I forgot it's movies the movie? Oh, I

see what's going on. I completely forgot that this is not the syntax for the parameters. I'm sorry. It's the

parameters. I'm sorry. It's the

parenthesis. I because I I always change programming languages and frameworks. I

always forget that they change to as well. So yeah, as you can see it

well. So yeah, as you can see it automatic it automatically refreshed.

Super cool. Um let me clear. Yeah, you

can see that we got the seats that there's no seats booking. If we click on it, we're going to call the hold. But

the thing is that it's not going to work because this method is not allowed. This

signature does not exist. This is what we're going to create now. It's going to be the slash movies name sits and then we're going to call the hold. So let's

me just do that. Uh let me actually close here the terminal.

And what we're going to do is that this is going to be a post request uh like so. So post movies seats and then I'm

so. So post movies seats and then I'm going to say the seats id and then this is going to be slashhold because it's going to be an action. So I'm going to

say book seat or hold uh seats is like the reservation and this is going to be from the same handler. So, pretty much the same thing we're going to do here.

Going to actually copy and paste this.

Uh, I just want the name here like so, so that there's no typos. We're

going to have to get the movie ID. We're

going to also have to get the seat ID from the parameters.

Let me actually make sure that they are correct now. So, using the curly braces.

correct now. So, using the curly braces.

There we go. And now basically what you need to do just call the service again.

So handler svc book and now we need to send a booking.

Now the thing is that we're not just going to send our own booking like this because the thing is that the front end is actually sending some payload. He's

just going to send the user ID for now.

But that is enough data for us to actually create a booking ourselves. So

we're going to use the parameters as well as some of the payload. So I'm

going to show you how you can actually parse user payload in go and then also uh basically code it the the JSON that the user sends. So we're going to hold this into a variable called request.

It's going to be called it's going to be a strct called hold uh request. It's

going to be private. I'm going to actually just type it inside of the function. That is fine. And basically

function. That is fine. And basically

this is going to be sending a user ID.

It's going to be a string. And there

we're going to have some JSON signature here. This is what the front end is

here. This is what the front end is actually going to send. And then we're going to dstructure this to go to be like this. This is how we're going to

like this. This is how we're going to access it. So going to be user ID with

access it. So going to be user ID with uppercase. But the front end actually

uppercase. But the front end actually sends the user ID because in JavaScript, this is kind of the um the way you do things. So the way that we can also

things. So the way that we can also unstructure this, it's pretty easy. You

can just use the JSON package for this called the new decoder. We're going to send in the request body. This is going to be an IO reader. It's a very

important um interface to to use because we can just send anything that implements an IO reader to this decoder.

Could be a body, could be a file, could be anything. Pretty interesting. And

be anything. Pretty interesting. And

then uh before that, let's actually call the decodes. Of course, we're going to

the decodes. Of course, we're going to send a pointer to the request, right?

Pretty important to have a pointer here.

And then I'm just going to handle the error in line like we have been doing.

If we have an error uh oops what happened here uh I think I changed file my mistake no it's the same so I think what we need to do is just return this is the most important bit if you want to

add error uh handling or logging we can do it as well but as again I'm not going to focus too much on this video because of the sake of time but if you want to see how in production this is usually done please check out the first link in

the description we go much more in detail on those resources there um but this is pretty much it we have the request Now we can create a booking. I'm

just going to say data and then this is going to be the booking. We're going to send the user ID

booking. We're going to send the user ID from the request dot user ID. And then

the rest we actually have the seat ID.

Um we don't need to send everything actually. So just the seats and the

actually. So just the seats and the movie ID is enough for the booking to to be used. So let me movie ID. There we

be used. So let me movie ID. There we

go. And then I'm just going to send instead of this booking in line, I'm going to send the data. And as you can see, we don't have no errors. It means

that it works. Now there's a thing or a change that we actually have to do on the booking function because head of now it just returns an error. But as you're going to see in just a bit, we are going

to need to get a return of the session.

For now, I'm just going to say that this is going to return an error because it does return an error. So, I'm going to quickly handle it, but we're going to have to go inside of this this function and do some changes very quick though.

Uh, we just want to get the session because if you remember from the diagrams, the hold sit is going to create a session. So, once we go here, click on a sit, a session is going to be created for 2 minutes. And this is

exactly the data we want because we want to extract the session from the booking.

We don't want to hardcode anything here.

We're going to see exactly what I mean.

Um let me copy and paste here actually the login and then we can just basically return to the user. Now what are we going to return? Basically I have just

typed already this. So let me just paste it and show you. This is the strct that we're going to use for the response.

This is what the front end is expecting.

So this is important that it's the same as I have here. Otherwise the front end is not it's going to throw an error uh and you're going to see it's on JavaScript. And then I'm just sending

JavaScript. And then I'm just sending this with JSON. Now you can see that there's a session here that is undefined. So I can actually save this

undefined. So I can actually save this to import the time package. And this

session is what I mentioned that is going to come from the book from the service. Okay. Oops. Uh session like

service. Okay. Oops. Uh session like this. Okay. So as you can see we have

this. Okay. So as you can see we have all of the data for the user for the UI to render. So he knows the when it's

to render. So he knows the when it's going to expire. So he can do some JavaScript functions to uh remove the seat after this. On the back end this is going to be protected by our service. So

um that is fine. Let's just go to the book and let's actually change this to return a session. And if you think about it, the session is nothing more, nothing less. We don't need to create any other

less. We don't need to create any other domains. You could come here and create

domains. You could come here and create a type for the session. But you can actually just use the booking for simplicity. Uh I think this is going to

simplicity. Uh I think this is going to be yeah because the session is the booking at the end of the day. So I'm

just going to say booking error. If you

want to actually deviate a bit from my solution, you can create a session type.

That would be more correct because there's some overlap between the booking and the session. Okay, but this is pretty much the same types that you're going to use. So, I think it is

completely fine for now. The service is done. But as you can see, we're going to

done. But as you can see, we're going to have some errors. We have to actually change the interface here on the domain to we have already done this. We have

changed the service. But here on the ready store we also need to store to return um here right we have to say booking uh because if you look at the test the

tests are failing now uh we can actually just ignore here the session on the test this is going to work it fix it and then here we can just return an empty booking

if there's an error we're not going to care about it but if we have uh if it works we're going to actually just return the session that we got from our private hold function. This is the the

thing we have just implemented in a bit with radius. Okay, so this is pretty

with radius. Okay, so this is pretty much it. Let me save. We go back to the

much it. Let me save. We go back to the file. Make sure that we also save here

file. Make sure that we also save here the handler. And as you can see, no

the handler. And as you can see, no errors. What we should expect now if we

errors. What we should expect now if we run our server is that we are going to have a functionality to actually see the seat we hold on the UI. Let me refresh

here the UI as well. Let's go to the the Dune part two. We should have the seats in just a bit. Uh what's going on here?

I think my Firefox is struggling a bit.

Okay, I had just to close it and open it. For some reason, Firefox just didn't

it. For some reason, Firefox just didn't want to work with me. I'm going to select the seat, for example, this one, A3. And as you can see, as I click it, a

A3. And as you can see, as I click it, a new piece of UI has just shown. Let me

decrease the UI. You can see that we have a checkout hold very similar to like a checkout you'd do on a on a uh e-commerce. You can see here the request

e-commerce. You can see here the request we have just done. This is the payload and here is the response. You can see this is what we have just typed on the back end. This is what the front end

back end. This is what the front end sent. So the front end just created a

sent. So the front end just created a random user ID for simplicity. Of course

this would come from authentication. Uh

but as you can see we got a hold of two minutes that we created on the back end on the the register. This is exactly our default hold. If you change this, you're

default hold. If you change this, you're going to see that the new session is going to have a different time. And if I open a new tab actually and I click on the dun part, you can see that this is a

different user than this one. You can

see there's two tabs. I can already see someone is holding this seat. So I

cannot click on it. You can see that I got an error. Uh, of course, we're still not handling that error. But basically

what happens that I cannot select anyone's seats even though he hasn't yet paid for it. Okay. Uh we're going to still implement the confirm functionality which is going to make the

seat preminent on radius. And by the way you can already see here the the other.

So our business logic is actually working and we're going to also implement the release functionality which is important. If we leave the page we can remove our hold. But for example,

if I select seat, we should see also on the other user that he also sees that you're hold and someone also saw. So I

have implemented this differentiation just for you to see, but in a real application um I think you just see that hey there that's it taken. Okay, so this is pretty much it for the hold. Let's

now go to the confirmation and then the release.

Okay. And to finish all off, I have just come here to the may not go. And I'm

going to give you a challenge. I have

just pasted here the signature of the last twin points we got to build. So

this is going to be the confirm. So when

you have a sit checkout session open, we're going to confirm that session.

Basically, what we're going to do is that we're going to go to the radies and we're going to set the key to permanent.

Don't worry if you cannot figure out that part. You can also always check

that part. You can also always check online or ask any LLM of your choice to help you code this. And then the delete is going to be also pretty similar because what we're going to do is that we're going to delete a session. The

front end is going to send some payload.

It's going to be the user ID and we're going to delete that key on ready. So

this is even simpler. And as you can see, we have used all of the HTTP methods, the get, post, put, deletes. So

this is kind of a a nice restful API designed for you to also learn from.

Okay. So give that a minute. If not, I'm going to show you my implementation that I have done.

Okay. So I have just implemented this.

It's pretty similar to what we have done. So I'm going to skip the coding

done. So I'm going to skip the coding part. Basically I have made the

part. Basically I have made the signature. So as you can see the confirm

signature. So as you can see the confirm session is going to receive a session ID from the front end which is going to be on the URL. The request is going to be the same as the one we have done for the

book. So I have actually moved the strct

book. So I have actually moved the strct out of the hold seat um function. You

can see here the new code I have written. So if you want to actually

written. So if you want to actually check I have made a commit with the previous code and this is going to be a new commit with the the code I have just implemented now. So if you want you can

implemented now. So if you want you can always check both commits to see what I have actually done previously and to these states now basically just I have

implemented a new service function which is going to be the confirm and then I'm sending this to the user pretty much what we have done before and then the last function is the release session. So

this is a handler to release. We're

going to do pretty similar decode the request the user sends. I'm going to also check if the user is actually empty. some some validation here. Um and

empty. some some validation here. Um and

then I'm going to release the seat and then here on the service I just have to show you that hey I have added these two services instead of directly communicating with the starter because we have multiple stores that we have to

uh play uh plug and play. I have not updated them though I'm just focus on the ready start which is this one. This

is where most of the bulk of the work has been done. So the confirm um function on the radius just basically gets the session just to validate it and

then we're going to persist. This is the key. This is basically what it does is

key. This is basically what it does is that it's going to persist the key. It's

going to remove the TTL and so it's going to be persisted forever on the radius uh database. Of course is not the best database to do this but as I have mentioned before I'm completely fine

with that because we're just using radius as a a play tool you could say.

And then I'm just sending this to the user some data to the function and there we go. Okay. And then the release it's

we go. Okay. And then the release it's even more simpler because we're just we get the session for the sake of it. And

we delete that key. Okay. So let's

actually test this out. I have also updated the test. Uh actually I haven't touched it have I? No the if you see the start has an error. It means that it's not um implemented correctly. In this

case, the ready start is correctly implemented because I have also changed here the domain with these two new uh signatures that I have just shown you.

So, let me run the uh application one more time. This is going to be the last

more time. This is going to be the last time probably. Let's refresh the UI.

time probably. Let's refresh the UI.

And if we actually go to for example Dune again, I think Firefox is struggling again. Let me see if I can fix this.

again. Let me see if I can fix this.

Okay, it seems to be working now. I'm

sorry for that. Let's go again to Dune.

We're going to have all of the seats.

I'm going to also go to the other accounts. Actually, uh I cannot do split

accounts. Actually, uh I cannot do split screen on Firefox though. But I think it's fine. I can just switch tabs. Uh so

it's fine. I can just switch tabs. Uh so

I'm going to reserve the this seat, right? You can see the checkout. The

right? You can see the checkout. The

other user, he sees that, hey, I cannot take this seat. So I'm going to reserve this one, for example, the one on the right. There we go. You can see on the

right. There we go. You can see on the other user that it automatically shows because we are pulling the API. You

could of course solve this with websockets. Maybe it's a better

websockets. Maybe it's a better solution. I have thought that on the

solution. I have thought that on the channel already before, but for simplicity using bowling. Uh let's

confirm the seat. Okay. So it's

confirmed. I have this seat for me. You

can see that the checkout session already disappeared. And here on this

already disappeared. And here on this user if I refresh for example I come here to Dune you can see that there is

uh actually it didn't go to confirmed.

So let me double check the front end.

Okay so sorry for that. I figured out what was the issue. Basically it was actually not on the front end. It was on our listing. So here on the seats we

our listing. So here on the seats we have two keys that we have been working with. So whenever we fet a seat we have

with. So whenever we fet a seat we have the booked and then we have the confirmed which was the one that we forgot to add. So what I have just done

here quickly is that I went to let me go from above to drop down I went here to the slash uh where is it the slash sit this one the list sit I went into it and

I have added this line here. So I'm

basically checking that hey if the status is confirmed we're going to send the uh confirmed property has true to the front end and then I went here to

the seat info and I have just added this line pretty much it restarted the back end and now we can see that we have this two users that I have already selected those seats let me actually refresh this

you can see that this session has already two two seats booked so we cannot book on them uh we got the other but if we try to book this one again.

Let's see. Someone already put a hold on it. Let's select for example the one in

it. Let's select for example the one in front. This one. And you can see that on

front. This one. And you can see that on the other user it automatically changes.

Uh so I'm going to do release. I have

released it. On the other UI, we should see that this seat is open. Now I'm

going to confirm this one. So just

playing around. And as you can see in real time, we see that's updating. So

I'm going to confirm this one as well.

And as you can see, uh someone already booked that one. This is the whole um fold that I wanted to show you. It's a

pretty interesting exercise to play around. It's a nice exercise if you want

around. It's a nice exercise if you want to learn concurrency. Uh this is this could be really an interview exercise that you might see. Of course, you're

not build the whole thing, but at least know how to solve this problem. Um and I think it's a very interesting project as well if you want to put on your CV. Now,

just before I go, let me show you that you have access to the soft engineer community. This is a platform and a

community. This is a platform and a community that you have access to all of my courses, all of my life's work.

Basically, these are 20our courses and you have access to all of them. All of

them are inside of the same pack. You

can see user reviews. You can see that it's a very extensive course that I've made last year. Um and again this is just a combination of multiple ones that

you can um get a guided path if you want to become a backends engineer or level up your skills. So thank you for watching and see you in the comments below.

Loading...

Loading video analysis...