Cloud Native Objects for High Scale & Performance

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey everybody it's derek martin from codeopinion.com most http apis or services are stateless meaning that they have to query a database or cache to getstate and make another query to the database to write state and this has to happen for every http request so why is it common to create stateless applications because creating stateful applications can be difficult especially when you're horizontally scaling but it doesn't have to be i'll explain how you can use cloud native objects to create stateful applications that can help scale and performance this video is brought to you by event store db the stream database built from the ground up for event sourcing cqrs and event driven microservices for more on event store db check out the link in the description so let's start off with the simplest example of a stateless web application or http api so we have our clients or browsers that are going to make a request to our application to perform some state change on an entity or some business process so what that usually looks like is they make the request to our app service our app service then has to go and query the database or cache to getstate so that can determine if all the business rules or validation passes to make sure that we're in a good state to even perform our state change if all that's successful then we can then go and write our insert or update whatever statement to do that state change on our database subsequently every other request kind of goes through that same type of life cycle where no state is actually on the app service we have to go to the database or cache to get it so to turn this into a staple application let's start with our clients are still hitting our app service and we have no state yet so we query our database we get our state back but what we're doing now is we're persisting this in memory on the app service for whatever again entity or object that we're working with so as we do normally we perform our business logic validation against that state and we determine yes okay it passes so we can make our state change but in memory this little object here that is up to date that is current state so that means that a subsequent request for that same type of entity or object that we have in memory we don't have to query our database anymore we have it directly so we can just directly perform our validation or business rules and if they pass then we can go and make our write statement so we've eliminated having to do that initial read to get state because we already have it in memory directly in our app service so this becomes much more difficult when we start scaling horizontally our app service so what i've done here to illustrate this is we have our load balancer and i have three different instances of our app service so if a first request comes in and we're just doing round robin here from our load balancer if the first request comes in and we hit the first app service it goes into the database gets the state and again keeps it in memory but if another request comes in and hits the second app service we do the exact same thing here's the problem that both instances now have a copy of what that entity or object is and if our second instance here made a state change to the database now the first one has a stale out-of-date version so really what we want here is we want our app services to only have one place with a single point of truth where any one object exists in memory and we're doing this because we are just trying to limit the number of requests we're making to our database as you can see here our database ultimately becomes the bottleneck so we want to limit the number of queries that we have to perform on it and we can do that creating staple applications but how do we solve this issue where each different app service is kind of working independently we need them to communicate in order to know where a particular object is in memory so that we don't have to go hit the database so how do we do that you can accomplish this using cloud native objects i'm going to use microsoft orleans which has the concept of virtual actors which are known as grains but reuben bond who's on the microsoft orleans team often calls them cloud native objects which i think is a really good term so i'm going to steal that and call them cloud native objects so to illustrate how orleans works it has a few different building blocks two of which i'm illustrating here which are cloud native objects and again orleans calls these grains live in silos and you can create a cluster of silos so to illustrate this i have this app service and removing it from having that state in memory rather than those objects living in a silo somewhere so i'm illustrating here just kind of separation but the way this can really work as well is you could turn your app service to also co-host a silo and what this means is that now you see that i have different arrows here where our silos are communicating with each other so if you have a request that hits a particular say let's say the first app service and you need to talk with a cloud native object you'll see with the programming model you don't really know where it lives but that actual object that you're working with could live in any app service in any silo in an app service so you'll see the programming model here in a second but this allows us to have state within our our app services distributed across them in a cluster of silos i want to say thank you to all the members of my youtube and patreon i really do appreciate the support they'll get access to all the source code i'm about to show as well as a private discord server if you want more information on joining check out the links in the description so while this isn't a full-blown orleans tutorial i am going to explain how it works and how you implement some of this so the first thing i need to do is to find an interface that's going to expose all the behavior we want for our cloud native object and again orleans calls this a grain so i have this i invoice number generator grain and i am using the eye grain with grid key now the purpose of this and this is from orleans this eye grain with grid key is because you need to identify a grain so that you can retrieve it this could be done with a grid an into string some type of identifier and i'm defining on this interface all the methods that we want to expose so i'm calling the reserve invoice number so for our implementation here i'm extending grain i'm implementing the invoice or the interface that we're creating and why i'm using this example is i'm you creating i'm generating invoice numbers um sequentially and that's because all operations done in a green are single threaded there's no concurrency that we have to worry about so the method that i've implemented here our reserve invoice number we're just using a logger to output some data that you can see later in the console and i'm incrementing our invoice number and returning it so i'm keeping this example simple i'm not using any persistence orleans has just a way to do persistence this is all done when an object is activated from a silo and it's all kind of done behind the scenes for you one of the ways here is there's an override for onactivate async you could do work there to go fetch out the initial state from the database but at that point once you actually have that state within your object it's in memory and it lives there so the next thing to show is how to create a silo and i'm going to show you two different ways the first is just the standalone console app that i'm using a host builder here and calling use orleans to specify that i'm just doing local host clustering in memory grain storage and just run and this is really the entirety of it and this project is referencing my domain project though however because it needs to know where the grains are and what the implementation is the second way to do this is as i mentioned in some of the slides is that you can host this alongside asb net core so here i'm creating a builder again i'm using orleans and i'm also configuring asp.net core with our web host defaults and doing the same types of things that you would expect so i still have for example my startup that i'm using i'm configuring asp.net core but i'm also hosting a silo alongside that in our asp.net core project so there's two different ways that you can do that so the last piece of puzzle is how we actually interact and evoke methods on our cloud native of object our grain so to do that i have a route here that i'm injecting the i cluster client and i'm also taking the grid of tenant id in our route so what i can do is use that cluster client called get green and the type parameter here is going to be the interface that we've defined of our invoice number generator that's what we're always working with as an interface and then as i showed earlier we have to identify in some way so i'm using that grid that i'm going to be passing in from the route so once i have our interface we have our grain out i can call any method on it this feels like a local object you're not really realizing where that object came from where it's executing so i'm just going to call it call await and return that value from our http api so to test this out what i've done is i've created a console application where i'm going to send 10 parallel requests to the route that we've defined for a particular unique uh cloud native object green so you can see here i'm using parallel for each async and i'm going to make a request based off the tenant that we're passing into it and i'm just doing the console.writeline it's basically passing everything all the way through from our hp api so that we can see where it's running at and to do that i'm actually going to create a cluster of two silos so that you can see that some greens will live on one silo some will live on another but i'm only hosting one http api all right so i have everything running at the very top the top left and the top right are two instances of the silo project that i illustrated so it's the ones that can be executing our cloud native objects our grains on the bottom right is our http api and this is what was invoking our grains and on the bottom left is our client which is making the http request to our http api so what you're going to notice is logging in a couple different places when i run the client it was outputting the invoice numbers that were being generated but as well as you're going to see because i had it in my grain my cloud native object was logging as well you're going to see that happen in one of the silos because that's where it's actually executing so i'm going to specify tenant one and we see that it executed in the the silo here on the top left that's what we're printing out this was our tenant id and then our invoice number and you can see everything was happening uh sequentially uh single threaded so here's our output we didn't duplicate any numbers nothing was happening concurrently but because i was doing this in parallel the output's going to be a little bit off here because we were doing it all with a parallel for each async so if i do tenet one again it will again it will execute in this same silo because that's where that particular object lives so let's run it again and we can see it ran over here where it should be and we get our sequential invoice numbers with no duplicates so now what i'm going to do is i'm just going to run this multiple times to try to hit the second silo because again as you see in the code you're not really specifying where they're actually going to be executing live at so let's just run this multiple times here so i'm going to do two three four five six seven eight nine so there we go i did a bunch of them nine and i executed now on the second silo so here it is um here's our invoice numbers i'm going to do 9 again and we can see that it's going to run on the second silo again and again it ran there and we're getting our numbers again with no duplicates again because we're single threaded so why would you want to use cloud native objects kind of what i illustrated the first is it offloads a lot of work from your database where you don't have to go to the database all the time to get state so that you can apply some type of business logic or validation rules so you're offloading a lot of work there the second is because cloud native objects are generally single threaded if you have that need that can be really really beneficial an example of this most commonly uh that applies to the two that i'm talking about are aggregates often times you want an aggregate route to perform functions in a single threaded manner as well as it needs all the data to apply its a variance in business logic because it is a consistency boundary so the key thing there is that you get the benefit of being single threaded and you get the benefit of being in memory so you only have to pay the cost when you need to build up that cloud native object you can hit the database get all that data that then you're using to apply your variance in business logic if you found this video helpful please give it a thumbs up if you have any thoughts or questions make sure to leave a comment and please subscribe for more videos on software architecture and design thanks you
Info
Channel: CodeOpinion
Views: 1,017
Rating: undefined out of 5
Keywords: cloud native objects, software architecture, software design, design patterns, software architect, .net, .net core, asp.net, asp.net core, microservices, rabbitmq, distributed transactions, messaging patterns, microservice architecture, domain-driven design, cloud-native, cloud native technologies, actor model, virtual actors, microsoft orleans, cloud native, cloud-native objects, grpc, rpc, cache, In-memory cache
Id: iE8cisVgoj8
Channel Id: undefined
Length: 12min 54sec (774 seconds)
Published: Wed Dec 15 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.