Modern .NET Messaging using MassTransit

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
all right well good morning for me uh you know being in the you know central time zone it's it's early yet but uh today as part of.net conf i'm going to share kind of a a beginner's or introductory look at modern.net messaging and where we're at and kind of the state of things my name is chris patterson i'm the author of mass transit it's an open source project that's been around for i think 14 years now it seems like a long time but the the site is uh masstransitproject.com if you're interested afterwards and i'll have a link to that let me see if i've got my powerpoint the agenda that we're going to cover today because we are kind of keeping the sessions fast and loose today i'm going to start by setting up the context of an existing order api it's written in asp.net core or i guess asp.net 5 now um i'm going to kind of show just a basic controller that talks directly to energy framework core and then you know we're going to take the idea that okay well now how can we make this asynchronous how can we make this so that we're using you know a message broker so that we can do asynchronous processing of order submissions so we can increase our system's availability you know if the database is busy or backed up we can write that off to a message queue and then process those order submissions you know asynchronously from other systems uh so i'm going to do that i'm going to keep it simple i'm going to keep the code within a single solution and i will post the code after the session up on github so that you can get to it so first let's start with the existing solution that we have today now if you've used asp.net core this looks pretty familiar we have a controller called an order controller it has a couple of different methods it has a method to retrieve an order you know obviously you're going to do a get and it has a method to submit an order and i'm going to stop here at the submit and the submit just takes an order model checks if it's valid puts the order into a db context saves the changes and then returns that order back and if we look at that run we should be able to see assuming it opens in the correct window [Music] we should be able to come in here and then submit that order so if i come in here just using the swagger ui i say try it out i put in an order number of order one two three and i'm going to copy this grid because i'm going to need it i'm going to paste this in and i can see that i've submitted an order and nothing really comes out of the log here when you're using visual studio that or sorry writer whatever the logging doesn't contain a whole lot but i'm going to check and see if i can actually get that order so i can verify that my rest call works for getting the order we can see that yep the order status is submitted now i'm the boss i can approve the order so i'm going to come in here and i'm going to post an api request to submit that order or to accept that order and i can see that the order status here has changed to accepted and this is all happening you know synchronously within the controller that i have here you know when i call accept it's just doing a db context lookup for the orders finding an order with that order id and if it's found it's just changing the status to accept it calling update and then save changes async so very simple direct didn't try to do a lot of abstraction here with things like repository patterns and all that i wanted to keep it kind of simple so looking back in here everything is talking directly to the db context which means that i have to know everything about the database and if i go out and i turn off the database which i could do here we just killed the database now if i come out here to do a get well a get is not going to be a good one to do an example of but let's say i want to submit another order now let's make this order number five and this is order number one two three four if i try to execute this now i am going to get the dreaded internal server error that i couldn't connect to the database and the world is not happy and it's it's the end of life as we know it at this point we can't accept orders and our boss is trying to say okay well let's uh let's figure out why our order site is down it's like oh the database is backed up the database recycled you know if anybody uses cloud-based databases maybe you hit a capacity cap and it blocked you server too busy things like that so all that fun stuff so let's see how we can kind of fix that and kind of change move away from that model so i'm going to stop this i'm going to jump out here i'm going to check out using mass transit because i'm not going to live code this if you want live coding there is plenty of that on my youtube channel there is no way that we're going to do that in this type of scenario jump back over to ryder it's going to reload the solution with a number of different changes and i'm also going to nuke the database completely and bring up a fresh one so that we're starting on now in this case i've made a few changes to the system i've added mass transit to the solution and in this example i'm using azure service bus so why not use azure service bus for net conf right mass transit is an abstraction on top of message brokers so with in this example i'm using azure service bus but i could just as easily use other brokers like rabbitmq or amazon sqs or uh so you so you have some different options you're also able to consume messages from event hub and kafka with the exact same consumers and sagas and behaviors that we have built in with mass transit so you get you get some scalability in terms of development of being able to apply the same business components across a variety of different message brokers and ingest paths so pretty flexible there so in the previous solution order was just a simple entity that just had an order id and a status on it but for this example we've updated our controller to use a mass transit saga so sagas are a a component of mass transit that a lot well saga's mass transit supports sagas through a state machine implementation so instead of being a simple entity with no behavior we're able to combine an entity with a state machine behavior that's driven by messages so if i look at the change in my controller now my controller got really simple no database talking whatsoever it just says okay well for a submit when i get an order i am just going to publish a submit order command and return accepted to the api request and by accepted meaning it isn't okay yet i mean i haven't actually committed this to the database yet but if you know in an earlier session today you know there was discussion about eventual consistency and you know you know looser consistency models i'm accepting that order and putting it into a message queue and then returning accepted you know there's a chance that if they immediately turn around and call get that it won't be in the database yet and that's something that can be handled a number of different ways if you need that immediate visibility but for this case all we want to do is be able to accept when they call submit order to get that order captured and into our system even if the database is unavailable so the there's no talking to db context there's no dependency on db context whatsoever i'm dependent upon a few different mass transit interfaces across the system so i did i bring up the database yes i did so let's let's start with just a simple run of this and see how the behavior has changed and then we'll walk through how some of that happened so when i run this solution now it's obviously going to build hopefully i didn't lose any of my connection strings we have the same swagger ui only this time if we look at the log that's output we can see that it's configured a few different things mass transit has written a few log messages out such as setting up starting the bus connecting to azure service bus creating subscriptions all of the topics and queues and message related broker elements mass transit put applies conventions to set all that up for you so you don't have to go up and manually configure all this stuff it's already taken care of by mass transit so let's come in here and post an order just like we did last time we're gonna try it out we're gonna put order one two three we're gonna we're gonna use the happy path first we're gonna assume everything's up and running and we're going to execute we're going to get it back to see that the order has been submitted so we have that same status we hit that controller method and if we go up to the get we should be able to get that same order id so let's come in here we're going to get that order hopefully it works um where are we at where are we at where are we at what i miss oh yeah there it is order one two three submitted great everything's grand we're going to go and accept that order now so let's go ahead and accept that order and say that it's valid we got the accepted status and then even now if we go up and just do an execute on this again we'll see that it came back as accepted so we're able to do the we're able to submit the orders now let's do what we did in the previous example that failed let's go nuke the database because why not right now let's go post another order let's change this to a five let's change this to a four and let's um execute boom i immediately see submitted i see order one two four i see the order id is there but i didn't get an error it didn't give me any problems i was able to submit that order now if i try to get that order with the database down obviously that's going to be an issue i'm going to sit here and it will time out it actually it will come back with a not found oh and it came off the nerd sarah yeah transient failure yeah fun fun because we actually didn't get a fault we didn't catch it and handle it differently but we're not able to get that because that's there but we were able to accept the order so if we look in here we can see that there was an error we can see that the the database server couldn't be connected we could bring the database server back up and come back into here and if we go to we're waiting on this here and so now again if we come in here we execute this little guy here again we still see the submitted and if we go up here to the get which is the 65 we should see the order submitted order one two four and we can also go in and accept that same order down below which i think this was just a five and we should see accepted and then if we go up to the top again and run to get the status we should see accepted so when we brought the database back up the order was submitted we were able to get the order and then accept the order and all was happy so good times great times so what's the difference in code between the two of those and let's take a look at that because that's really what we want to see here is what was required to implement this so we looked at the submit before where all we're doing is publishing submit order now submit order is a simple message class in this case i'm using a record uh it just has the order id and the order number and this is this is what would be considered a command type of message a command is just like a method that you're telling something to do it it's like i'm telling you i want to submit an order this is the order id this is the order number and when we send a command it's it's a it's a fire and forget operation i'm i'm wanting it to happen i know it's going to happen when i get the acknowledgement back that it's been written to the message broker i know that that message is durable and will be there so there i don't have to worry about if it's going to happen or checking result codes and so i'm able to just acknowledge that that order was accepted on the web api the handling of this message happens in a separate thread it's picked up you know mass transit it's it's a it's a framework so when you connect to a broker mass transit will then push push messages into your code through the use of consumers and there are different consumer types they're straight up consumers in this case we use the saga and those consumers i mean it's just like asp.net and asp.net you write a controller and a controller's action method is called in mass transit you write a consumer or a saga and then the methods of your consumer or saga are called by mass transit as it consumes messages from the broker so it it follows a lot of the same principles and and mass transit has a lot of the similar things around like middleware and being able to write pipes and filters and manage scope and all the container type things in this case i'm using a state machine to handle that submit order event and so mass transit includes a state machine it's called auto autonomous and it has a very declarative language of how to declare states and behaviors around the order and the order you know in the previous one the order was just a entity with a primary key and a status and an order number in this case it's very similar it has a correlation id which is a unique grid that identifies this saga state machine instance and then you can see i also have the order number and then a current state because a state machine always has to be in a state and in this case i'm storing that as a string now the behavior that i'm defining is that initially when that a submit order event is received by this state machine instance and it's correlated based on that order id then i want to capture the order number and then transmit transition to a submitted state so my state machine has two sedates well it has two built-in states initial and final and then two states that i've added one is called submitted and one is called accepted to respond to the transitions that can happen when those events occur so when an order is submitted i'm going to capture that order number and then transition to submitted now when the order is accepted by calling that second api call mass transit is going to correlate that accepted event which is right here very similar but only just has the order id it is going to correlate that accept order event into the same instance that was created by the submit order message so it yeah it's essentially doing that lookup in entity framework to get the instance of that state load it up and then execute the state machine passing it that instance so you can see the context that's passed in for these methods instance is the saga state machine instance and data is the actual event that triggered that behavior to happen you know it'd be similar to the arguments in an action method within a controller body but this gives you that state based so you don't have to say if this happened and that happened and all that and it gives it very good flow so when the order is accepted i transition to accepted and then i in this case i respond with the actual order now you know they don't have to wait for that but they can but if they wanted a response it will send the response to that initial and just pass that order back to him with the current state showing that it's now accepted i transitioned beforehand because if i were to respond and then transition after it would still be in the previous state of submitted so so that is how the state machine behaviors work when i configure this so in the order controller when it publishes that submit order in the background that receive endpoint which is a mass transit it maps to a particular queue in in azure service bus it is going to receive that message it is going to create an instance of the state machine execute that event load that state from the database or create a new one if it does not exist execute that behavior and then persist that state back to the database so it isn't like i'm keeping call stacks in memory and i'm not nesting down you know keeping each of those state machines in memory they're all persisted out to disk so it becomes a very scalable solution you don't have to have it all across you know a bunch of memory you know things whereas if you have multiple awaits you know a weight basically just holds a call stack as you call something else so anyway tangent but so then i respond with that accepted and then as you can see when i do the accept which is the api call to accept i'm using a request client because i want to get a response of either order or order not found so i'm going to send that message and it's going to go to that end point the saga the saga engine with this mantra mass transit is going to get that that accept order command it's going to pass it to that saga instance it's going to do its thing so for instance if we come in here and try to accept an order that doesn't exist let's say i want to accept an order number 66 which i know isn't there it tells me that i failed to fetch why did i do that is the site running oh i don't think it's running anymore did i kill it yeah i killed it so there's nothing to connect to so let's just leave it running wait for this to spool up real quick okay so now if i try to run this we'll see that it should come back pretty quick with a order id and a 404 saying it was not found so you tried to accept an order that was not in the database now if i were to go in and actually submit that order with a6 i'll execute that now if i were to actually go in and try to accept that order in this case it would be accepted because it was received out there now this you know it you know distributed messaging you know this is you know just a taste it's just kind of an introduction of how easy it is to add asynchronous processing and i say easy because i've been doing it forever but whatever um the the um sorry i just had a brain freeze for a second um the configuration and setup of this is it it integrates nicely with the di container stuff that's there it's all based on you know using the configure services there's the add mass transit uh expression that opens up you know the configuration in this case i'm telling it i'm using this state machine and i want to store it using an existing database context and in this case i'm using postgrass i'm adding request clients which i use to accept the order and get the order because they're request response type patterns whereas the submission of the order is just published one of the questions that i get kind of often is but but if submit order is a command why are you publishing it and the reason is publish is really i mean pub sub is great because you don't have to know who's consuming the message and i mean i own the system i know that if i publish a submit order command i have one component that consumes that so rather than trying to tightly couple to an address or know how to get that to the destination i can just publish it and i know that i have a single consumer for that command so i don't have to worry about it it's it's it's routing for free i don't have to have like a service catalog or anything now for events like order submitted i may publish that anyway because i may have three or four listeners or consumers that are going to consume that order submitted event that would tell other systems okay well if an order was submitted i need to go and see if we have that stuff in inventory and maybe pre-allocated or i need to go check the customer id of that order and see if they're a bad debt risk you know all the different things that could go in and place a hold or a block or cancel that order based on oh they tried to order a playstation 5 and good luck getting one of those because they just don't exist you know i don't care what people say the pictures are fake they've never seen one in person so it's it's it's i understand the the messaging infrastructure i have and so rather than say okay well i know that i'm going to hard code this address for this endpoint for submit order i can just publish it because i know it'll get there so that's one nice thing about that mass transit does have a number of conventions that applies to all the brokers so i just add the state machine and say configure endpoints and if i look at the output of this i can see that it configured the endpoint in this case it called it order state and it set up the order state machine on it it automatically will use the name of things to create things like cue names and topic names and things like that so for instance if i if i don't want pascal case i could change it to kabob case and i could just say x dot you or set the bob case and now my names would be like order dash state so it's really easy to change a lot of the conventions or write your own and there are a lot of ways to add like filters and retry handlers and that type of stuff as well so for instance with a state machine i would almost want to always have a retry handler and an outbox so that i can handle concurrency exceptions things like that so we're at about 8 22 my time we have about eight minutes left i don't know if there's any questions or if anybody's posted any uh if you do have questions you can post them on twitter with the hashtag.netcomp i think that's how everybody's doing it um if not i'll just kind of keep going um the you know i talked about how mass transit sets up the topology on the broker and everything for you you can see here where it's creating like topics for each one of the message types i have you know i have a get order message a submit order an accept order so it's creating topics in azure for that it's creating the queue for order state which allows the messages to be pushed there and then it's creating subscriptions to bind the messages from those topics into that uh order state queue so like i said it does a lot of the work through the uh through the configuration there um then you can see it start up there's a lot of debugging info you know one of one of the things about asynchronous systems is it can be really hard to debug so putting things like telemetry if you have app insights mass transit uses um the activity reporting so that you can get complete call stacks and call trees and app insights so you can see across all your messages across all of your systems you're able to see kind of a continuous nested flow uh that kind of digs in and you know it almost looks like a gantt chart i guess i can't think of another way to describe it but it's a full timeline view of kind of the whole message behavior and here you can see but with just by looking at the logs within mass transit i can see that i sent a message in this case it was accept order and then that order was actually you know message was actually received by the um by the state machine it was processed responses were set you're able to kind of see that flow all through there so if we don't have any questions thus far i was going to show one more aspect that's kind of nice about mass transit and i don't think i'm running right here did i kill it yeah i did i killed it okay so unit testing how do i test this state machine so obviously i don't want to test with azure service bus that seems like it would be a little heavy to set up a broker connection to actually test so mass transit again it supports multiple transports one of those transports is an in-memory transport that can be used for testing and so what i wanted to do in this case was i want to test the submission of an order and verify that when i submit an order that i can then actually get that order back so there's a little bit of setup here which you know it gets to be boilerplate but you know it's it's it's what it is you gotta set up msdi you have to set up all the different components but in this case i'm using that same edmass transit but here i'm doing here i'm setting up the in-memory test harness i'm setting up the state machine this take machine test harness the request clients the same as it was before i'm setting up some scheduling all that set up but all i'm doing here is i'm saying if i publish the submit order i can then wait and see if that message has been consumed and then i'm doing a request to actually sub to uh get the order and then either get order or order not found and if i run that i could see that i have a lot of the same log messages i can compare production versus unit tests i can see that it actually dispatched did all that and that the assertions passed so you get really nice fast in-memory unit testing with the exact same components i'm running against the production service bust instance so let's see we have two questions let's see here state machine of mass transit that's why that published a new order or an order accepted message i think i failed to see the point of addition of state um okay so you could certainly just use consumers to process and submit the order in this case i wanted to actually have order state because you're going to build that in your system anyway you aren't just going to accept and order orders you're going to go through states and you know order in itself as a noun is going to change its meaning as it goes throughout the system is is this a submitted order is it a accepted order is it a order ready to be fulfilled i mean you know nouns kind of change as they go through systems and different bounded contexts so it's you know state is something that we always end up keeping track of and and the state machine makes it a lot nicer than you know coding all that a bunch of if then statements around a bunch of controller methods so hopefully you know you can see that you could certainly just use a consumer to just go and write to db context if you wanted to very simple another question in the sync example we got a 500 when the database was down in the mass transit example we got a 200 but what if the message is down the database is up yeah so you know your database has got to be pretty highly available or your message broker has to be fairly highly available and in my experience it's been easier to keep a cluster of brokers highly available and up than it has been to keep a database up and available so it's it you're really just moving the most critical piece of the system from one area to another but message brokers because of the way they're designed they don't have to have transactional consistency you can partition them a lot easier than some database systems uh it's just been generally easier now there are cases where some people like to have a backup plan of like oh well if i can't write to the broker i'll write to a local database and then forward it on you know that those are typically used in like mobile device scenarios with limited connectivity but yeah it does put the dependency on the broker being available and i've just found that brokers from a cost and bottleneck perspective are a lot easier to scale and keep up than an actual database so hopefully that was good i think we're about out of time i will pop up a couple messages here um the final slide just has a couple links but masterize project dot com has links to everything else and you know that should get you what you need if you need anything all right oh some presentation thank you so much chris he was very interesting and yeah architecture gets complex right you know the the famous it depends right so i guess i think it depends where the errors come from right absolutely no this is awesome so first of all dot net but really love your session so he gives a thumbs up this is cool really good
Info
Channel: dotNET
Views: 10,929
Rating: undefined out of 5
Keywords: .NET
Id: jQNQDLv7QmU
Channel Id: undefined
Length: 29min 14sec (1754 seconds)
Published: Tue Nov 16 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.