Own the future, NSB-style
About this video
If you wanted to implement the architectural patterns mentioned in Udi’s Own the future presentation, you could figure out how to do UI Composition yourself with Angular and ASP.NET, how to route and scale commands differently from events using queues like RabbitMQ or SQS, and of course monitor it all in production.
But, in this presentation, we’ll assume you’d rather focus on delivering business value and show you the hands-on programming techniques to do it all, using NServiceBus and the rest of the Particular Service Platform. Whether you’re running on-premises, on AWS, or on Azure – it all just works. For real.
đź”—Transcription
- 00:00:03 Udi Dahan
- Hello. Good afternoon. Welcome back. Who was in my previous talk on the future? Yes. Okay. Almost all of you. Those that haven't, you can watch the video when the good folks at NDC put it out. You won't be missing something. I'll be putting in the necessary commentary just to make sure that we're fully up to speed. In this talk, we're going to be focusing on how to implement the architectural styles that I talked about in my last talk. All the code that I'm going to be showing you is available online. It's open source. It's at that URL, github.com/ParticularLabs/NetCoreDemo. As you can guess, it is all .Net Core-ified up to the latest .NET Core 2.0 bits. So, if you've been looking to use .NET Core in your projects, this has everything that you need. So, I also have the link to that at the end of the presentation so you won't be missing anything.
- 00:01:02 Udi Dahan
- What are we even talking about here? So, it's this idea of vertically sliced architecture as opposed to layered architecture. Now, we're all familiar with the concept of layered architecture, having UI talking to some kind of API layer, which in turn talks to business logic and data access logic and databases, where, as I mentioned in my previous talk, that always ended up being really tightly coupled, no matter how many layers we put in there. What I've found over time is that ultimately, the better form of decoupling is within a given layer rather than between layers where parts of the UI are often decoupled from other parts of the UI and parts of the business logic can be decoupled from other parts of the business logic, where in many cases the best place to slice things down is not the traditional microservices style based on entities where we'd have in the retail domain when we're buying stuff online.
- 00:02:07 Udi Dahan
- We wouldn't want to have then a product service and a customer service and an order service. That noun based services model just results in a whole lot of coupling with all of these things talking to each other. So, when I'm talking about vertically slicing things, it ultimately means even slicing down those entities right down the middle where something like a product's name might be in one of these vertical slices, but the product price could be in another, and the inventory levels of the product would be on yet a third vertical slice. The reason why something like that could work is because we just don't get business rules that say you need to touch all those things in a common transaction. You won't have rules that tell you if the name is longer than 20 characters, then the price can't be more than $20, or you can't have less than 20 units of inventory. So, it is exactly through that kind of decomposition that these vertical slices kind of sort themselves out.
- 00:03:13 Udi Dahan
- Just another example, bear with me, the customer entity. So, things like a customer name and phone number, that sort of contact information, that could sit very neatly inside one vertical slice. But let's say the customer status, whether they're a bronze, silver, or gold customer. That doesn't have to sit in the same service because we don't have rules that state that if the person's first name is John, then they can't be a gold customer, which would be a stupid rule in the first place. So, when we don't have rules that require both fields to take part in the same transaction, then we can put those fields in different vertical slices, and then we can carry that all the way down into the database because if we don't need to have transactions that operate on both of those fields together, then we can put them in two different databases and not have to do a join.
- 00:04:09 Udi Dahan
- That gives us the ability to actually scale out our data tier by having different databases for them. Once we've done that kind of separation all the way up top to the UI and all the way down to the database, we can actually have these different vertical slices built with totally different layers internally where one of them has two layers in a database and the other one has three layers and a type of sharding model. The third one is built more on a graph type of philosophy. We don't need to have the same layers everywhere. Once we've separated things out and each service knows exactly which fields of data it's responsible for, then it can specialize and decide what data model really makes the most sense to me and how would be the simplest way that I could fulfill the requirements around that, and that's with regards to the top to bottom layers.
- 00:05:05 Udi Dahan
- The final piece that we're going to be seeing actually taking place in this session is how and why publish/subscribe communication between these vertically sliced services is so helpful and simplifies a lot of the back end complex orchestration that we often have. But because we got a lot of code to go through, I'm going to actually start first and foremost at the top in the UI. So, when you've got a UI like this type of amazon.com product page, as we mentioned, things like the product catalog information, the name of the book, the image of the book, that can belong in the yellow vertical slice, the number of stars, the ratings of the book can belong in the blue vertical slice, the price can belong in the green vertical slice, the inventory in the brown vertical slice, and all of that sounds awesome. Cool, great, I get it. But how do I build it?
- 00:06:09 Udi Dahan
- That's what we're going to be talking about over here. How do we take all of those nice hand wavy ivory tower architect type things, say, well, that's great, but does that actually work in code? So, what we're going to do is we're not actually going to rebuild Amazon because, hey, I've got like 55 minutes left. So, we're going to start with something simpler. In that .NET Core demo GitHub URL that I gave you, we have a simple e-shop type of environment where we've got products with names and they have costs and they have shipping information and descriptions and pretty much the bare bones building blocks of what you'd expect on a product page in an e-commerce type of environment. So, how would we build something like this in this composable type fashion?
- 00:07:04 Udi Dahan
- So, it all starts with a generic web host project. I want to drive that point home because the way that we traditionally build these types of web-based applications is that we have this web project that has a whole bunch of views and controllers and those controllers talk to services and the services return data, and all of that ultimately is orchestrated by this top level web host project. What we're going to be doing here is different, and it's going to look kind of odd because what we have in that generic host project is views that are bound to view models that ultimately define how the page is going to look. But the controllers, the controllers are really going to be these simple, dumb type of objects that don't get any data at all. So, here's what it looks like. This is the view for that product page that we just saw where we've got a div that's showing the image URL for the picture of the product and the product name.
- 00:08:13 Udi Dahan
- Inside there, we also have the string that says, "Oh, okay. This thing costs this much money. This is the product description." A view pretty much like any other, fairly simple and bare bones. All right? Now, all of that's fine. The question here once again is great, how do we get the data into that view? Or more specifically, how do we populate the view model? So, here's the controller that doesn't do that. This is the product controller that exists in that generic web host. So, what you see for the product details page, that details method down there, the only thing that that controller does is return the view. Doesn't go get the data, doesn't call any other services. It does nothing but return the view. Now, you might be looking at that saying, "Well, great, that's PowerPoint. Now show me the code so that I'll actually believe that you can build a generic dumb web host that way that will still end up showing the view with the data populated in it.
- 00:09:27 Udi Dahan
- So, let's take a look at that. Here we go. So, what we have here is this e-shop UI, and we've got a whole bunch of projects here. What I've tried to do in this session, and most of the time if people have seen my talks, I tend to rail against the hello world samples that are out there. There are lots of great technology talks that say, "Hey, this is great, this is awesome. Now, let's go build a hello world application or let's go build a to-do list." But it's not really representative of a complex enough domain that you can say, "Would that actually fit in the project that I'm trying to build at work?" So, we got a whole bunch of moving parts here. I'm going to take you through it bit by bit so that you can see how it all fits together and why that's actually a good thing.
- 00:10:25 Udi Dahan
- So, we've got this e-shop UI thing at the bottom, and now I'm going to show you the view and the controller for that product page. All right? So, here's our details product page, exactly the same thing that I showed you before. All right? Now, here we have our products controller, which again, when we're looking at a product page, then the products controller is going to be the thing that is invoked. Right there, you see that the details action is really just returning a view. So, again, you might be looking at this saying, "Okay, great. Let's see that run." So, when I run this, you're going to notice that there's a whole lot of stuff that pops up. Right now, ignore all of these back end process type things because we're going to be looking at them a little bit later. What they're doing is they're starting up the back end APIs, as well as some of the pub/sub stuff that I alluded to before that is going to be serving the web UI that we have over here.
- 00:11:33 Udi Dahan
- So, here is our web UI. Here is the details page of a single phone, again, exactly the same picture that I showed you before, and once again, you can see is we see the details and it's actually populated. There is data from somewhere else that's coming into that, even though our web controller is not actually retrieving it from anywhere. So, how does that even work? Let's stop this for a second and talk a little bit about the design behind this. All right? So, we saw just a little bit of the code. Where's the magic that actually makes all of this happen? There's this composition infrastructure that's available as a part of this solution, all of it in the ITOps directory, that provides this kind of generic double dispatch behavior that allows you to create all sorts of plugins that hook into the rendering of a page.
- 00:12:41 Udi Dahan
- So, it all starts with two simple interfaces. The first is the route interceptor. The route interceptor allows any piece of code to say, "I want to handle a web request. If a web request comes in, call me." So, through that, an object, let's say from sales or billing or shipping or whatever, can hook into the web request handling pipeline and say, "I want to handle that URL." The other interface that's in there is what's called a view model appender because in essence, what those objects do, they don't just handle a web request. What they do is they append data to the view model of that page. That's fundamentally what a controller does when you think about it. It's not so much controlling something, it's getting data into a view model. So, by implementing those interfaces, I can have two independent NuGet packages, three, five as many as I want, that I toss into that generic host runtime.
- 00:13:59 Udi Dahan
- At startup, it'll load them dynamically into memory and say, "Oh, okay. Now I know what each one of you does," based on the fact that you implement a route interceptor, a view model appender, etc. "When a web request comes in like /product, /details1, then I'm going to call you back and tell you do your magic." Then what is the magic that they do? Ultimately, they turn around and update a view model. So, let's take a look at their code and how they actually do that work. So, back to our visual studio. So, we've seen the generic web host. There actually isn't any interesting logic in there. But now let's take a look at sales. Inside sales, we have this section called view model composition. I'll zoom into that, and that view model composition is that project that I'm going to dump as a NuGet package into my runtime directory of the generic host, and it has all of those, let's call them microcontrollers because microservices are such a cool thing. We should have microcontrollers as well as microservices.
- 00:15:20 Udi Dahan
- So, let's take a look at the product details view model handler. All right? So, in here, I think what we have is we've got the order list and then we've got the product list and we've got the product price get handler. All right? So, what happens here is that ... Is that code big enough for everybody in the back? I've made it just about as big as I can and still getting it into the screen. All right? So, you've got the part that is looking to match the URL to say, "Is this a URL that I need to handle?" That's the top method over here. The bottom method is the part that actually handles the request so that when a request comes in, what it does is it says, "Oh, okay, great. Let's pull out the product ID from the URL. Let's go call my back end and get the data from that service."
- 00:16:31 Udi Dahan
- Here's the part where things go from strongly typed to more dynamically typed. This is kind of the magic loosely coupled piece, that we take the results from my back end API and put that data into the view model, and that's that view model object that I get as a parameter. Okay? Now, here what we see is that that microcontroller is only putting in the price. It's not responsible for anything else. It says, "I don't know what else there is on this page, and quite frankly, I don't care. My responsibility is just this little piece of the view model. So, I'm going to put my data in there." Now, let's take a look at another one. So, now we'll go up to marketing.
- 00:17:21 Udi Dahan
- Marketing has its own view model composition. Let's look at the product details handler that we have over there. Once again, what we see in this matches functionality, it's saying, "If the request is for /product, /details, /id, that's something I need to handle." So, it plugs into the web request model and says, "I'm going to handle that request. How do I handle that? Well, when the request comes in, I'm going to grab the ID from the URL, call my back end to go get my data, and then map that data to the view model. In this case, I'm grabbing the product name, the product description, the image URL, and I'm pushing that into the view model.
- 00:18:12 Udi Dahan
- Now, notice here that in terms of the generic web project that we have at the bottom, let's see, there's the details page, again, nothing in there has any awareness that those other projects are even there. The product controller, again, no knowledge at all, not making any calls to those things. If later on the business says, "Hey, we want to do something with, I don't know, ratings, we could have yet another service drop a NuGet package into there and plug and play. We now have more functionality on the website without having to update our generic web project." So, here, out of convenience, again, I want to make sure that I'm not pulling a fast one on you, what you can see in terms of the dependencies is we've taken a dependency on the shipping view model composition, the sales view model composition, the marketing view model composition, but we're not actually calling their code in any way.
- 00:19:18 Udi Dahan
- The only reason I'm taking these dependencies is so that the output of those projects gets copied into the runtime directory so that when I do an F5, it all just springs to life. If I was to work in a more proper production-ready devops type of pipeline, I'd have some kind of octopus deploy script that is taking that generic host package and the shipping view model package and the sales view model package and the marketing view model package and deploying them all up together without having those dependencies in there. But because for this demo, I want myself and for all of you when you run it later on to just be able to F5 and go. All right? So, that's how this magic is occurring and we're able to have code from different packages, from different services all join in together and engage in this type of view model composition, and now we've got a beautiful screen in front of us that shows the composition that we had before.
- 00:20:24 Udi Dahan
- Next up, a grid. All right? So, we've shown how to show a single item. Now, we want to show a list of items or a grid of data from multiple services. So, once again, bear with me so that I can show you the code in PowerPoint so it's nice and big. This is the view of the product list page. What you can see again is that we are looping over right there at the top for of our var product in Model.Products. All right? So, we know we have this list of products and each one of those products has a name and an image URL and a price and all that kind of stuff. Okay? Once again, you saw the controller from before. That list, the index action of the controller, all that it's doing is returning the view. It's a dumb controller. It has no idea where the data's coming from, how many services, who needs to be orchestrated, etc.
- 00:21:35 Udi Dahan
- So, again, let's run this and then we can look at the code. Well, I think you already believe me that it runs because we saw that before when we clicked on the single page. But once again, here is the products controller that's returning the view. Here we have the product index HTML page that is showing us that for each var product in Model.Products, and what we have in there is the product name, the product price, the product in stock, all of that. So, the thing about a grid that makes it a little bit more challenging is we don't just have the ID in the URL. Right? We can't just say each of these services will go to the URL and know which IDs to grab. So, that's the challenge when we want to go and create a list or a grid of things. There needs to be at least something that knows, well, what are all the items that need to be shown here in the first place? So, here's how the composition works when we're doing a grid.
- 00:22:43 Udi Dahan
- We've got this orange service. Let's call that marketing for argument's sake. What it's doing is it's going to its back end and figuring out what are the list of products that need to be shown for that page? Now, note what it has over here, it's not getting back all of the product data because it doesn't have all of the product data. All it's saying is "I've loaded up the product data that I know about," and this is the part where things get a little bit interesting because we're going to use a kind of client side message broker. This is also known as the event aggregator pattern, if you're familiar with that term. So, it's raising an event. This is an in memory, in process event in the web tier that's saying, "I found this list of product IDs. Whoever is interested, do with it what you like." Now, what we have is two other services, two other vertical slices that are subscribed to that event that says the product's loaded.
- 00:23:56 Udi Dahan
- Through that, they're able to get the list of product IDs. Once they have that, then they can turn around and go and talk to their back end. One of them says, "Oh, okay, let's go get the dollar value for each of those product IDs." The other one says, "Let's go get the inventory level for each one of those product IDs." Another one comes along and says, "Let's get the number of stars for each one of those product IDs." So, again, beyond just that event that is going between one service and those other services, there really is no orchestrator that knows how many of them there are. So, now that each one of those front end microcontroller things has the data that it needs, they can start pushing that data into the view model where, again, one of them is putting in that marketing type information of the name of the product, the image of the product, etc.
- 00:24:55 Udi Dahan
- The other one is putting in the prices. The third one is putting in the ratings. Once the view model is full, then we can render the screen, the view, and as far as the end user is concerned, it's going to look like just a regular list of stuff. But again, the trick here is that there is no orchestrator that is tightly coupled to all of those pieces. We can very easily plug in a fourth and a fifth vertical slice that will be able to expand on the data that's coming to the user. This is what gives us that composed view model, that list of products where each field of data is coming from a different service.
- 00:25:47 Udi Dahan
- So, let's go take a look at that code to see just how the publish/subscribe event stuff works because like pub/sub, if you haven't done any type of event aggregation, it can seem a little bit complex when you start out. But once you've seen it once or twice, then it's fairly easy to see how you do it a third time and a fourth time. Okay. So, let's start out with just that part of the service that's going and getting the list of them. So, we've got the product list handler over here. This is from marketing where, again, as we said, there's the matches part for it to say, "I need to handle this URL." Then in there, it goes to its back end and says, "All right, let's go get the list of products," and here's the part where it maps that to some kind of dictionary. So, it's got its back end list of products.
- 00:26:48 Udi Dahan
- As we know, we don't tend to bind the model that we get from our back end directly. So, we transform that to something that is appropriate for a view model. Then this is the magic. Okay? That's the part where the service that knows what the product ID is is raising an event and saying, "These are the products that I've loaded, whoever might be interested in that." Again, just to drive this point home, we're going to go look at the dependencies of the view model composition project. What you'll see over here is that it takes no dependencies on any other vertical slice. Right? It doesn't know that they're there. All it's doing is it's raising that event called products loaded, and something else will then react to that. So, let's take a look at who reacts to that.
- 00:27:48 Udi Dahan
- We've got sales that is responsible for the prices in this case, and inside sales we've got this view model composition thing. So, the part that we are looking at is the product list get handler. So, there's a slight difference here in terms of the interface that it's implementing. Instead of just handling the request, it is implementing the, I subscribe to view model composition events. That's the way that it engages with the infrastructure and tells it, "Look, I'm going to want to register a callback for when I need to work on a view model." So, at the startup of the page, what it does is it gets invoked by the infrastructure that says, "Hey, you told me you needed to handle an event on this page. Do your thing." So, it talks to that infrastructure, says, "I'd like to register a callback for the product loaded event."
- 00:28:51 Udi Dahan
- So, page loads up. That microcontroller gets invoked. As a part of its initialization code, it subscribes to the event. The callback that it has, it says, "When the event is raised, then go grab the product IDs. Then go call my back end to go get back the list of information that my back end has, and for each one of the product IDs that are in there, go and update the product price in that view model." All right? So, again, what we have here is a reactive microcontroller for a page. It's this type of NuGet package that you can drop into the generic web host and it will configure itself and say which requests it needs to handle. For each of those, it'll say what are the events that it wants to handle, and when those events get fired, how it's going to actually grab its data and update that view model.
- 00:30:00 Udi Dahan
- So, we can see here, again, that's sales, and let's look at one more, which is shipping. So, right over here, we've got shipping and its view model composition and the product list get handler and we'll see the exact same thing. Inside shipping, again, you see the name space called shipping, so you know I'm not pulling the fast one, has a product list get handler that is subscribing to the view model composition event, it's handling the product index page, so the exact same URL as all of the others are, and registers a callback for that same product loaded event. Once it gets that, it goes to its back end with the product IDs, does a request for the product stock list, and for each piece of data that it gets back, it updates the product in stock property of that view model, and voila, now we have a grid with all of those pieces running together.
- 00:31:01 Udi Dahan
- Again, you can imagine that in the future when the business says, "Now I want to have a customer-specific discount for every single item in the list," we can create a customer microcontroller thing that is also handling the product list URL, it is subscribing to the products loaded event, and then puts its own data into that view model. Okay? So, now we can see how a grid like this can be shown, and most importantly, that we can do that without having an orchestration layer, that everything is this type of pluggable vertical slice that I can have three of them, five of them, seven of them, where I contest each one of them by itself, I can version each one of them by itself, I can deploy each one of them by itself, and I get all of the benefits of microservices that you might have heard about before, but all the way up to the UI. I get this decoupling at the UI layer, I get the decoupling at the business logic layer, and I get the decoupling at the data layer.
- 00:32:19 Udi Dahan
- So, again, this is not amazon.com, but this is a working .NET Core 2.0 solution that you can take home and start playing with and seeing how you could build something like that yourselves. Let's move on. What about the actions. Right? Because it's not all about queries. We've got data that the user wants to put into our system. They want to add items to their shopping cart, they want to buy items, they want to cancel orders, they want to do stuff with our system and not just query it. Now, in this example, we're going to see how we even do composition for that so that it's not necessarily just one controller action that is handling everything. Because as we know, a lot of times, there's a need to do an orchestration for a given user action, or saying, if a user says, "I'd like to buy an item," yes, there is the regular sales process of buying an item. But we might have other types of, I don't know, user click tracking type of stuff that we also want to have in our system.
- 00:33:39 Udi Dahan
- Now, if all we have is just one big uber controller across the top and it's orchestrating all those other things, again, that creates a lot of coupling, and that's coupling on the command side, not on the query side. So, we've gotten rid of a lot of the coupling on the query side. We're going to talk about how we get rid of that coupling on the command side. But just for a second, want to pause that and talk about something else entirely. The issue with handling these types of commands that the user is performing is that sometimes when production gets under load, bad things happen. So, let's take a look at one of those scenarios where we're using an HTTP API for those types of commands and what can happen to a system under load.
- 00:34:31 Udi Dahan
- So, we've got data that's flowing into our system. People are clicking buttons saying, "I want to buy an item, I want to do this, I want to do that," and we have our web service at that front end. It's processing that request, turning around, talking to some kind of database saying, "Okay, open up a connection, go and put something in the parent table, go and put something in the child table." As luck would have it, because our server is under so much load, IAS does the very despicable act of shooting our process in the head and bringing it back online. This is known as recycling your application pool. It sounds so much nicer that way, right? Oh, I didn't shoot your process in the head, I just recycled it. Recycling is green and ecologically friendly. But think about what that means when your web server, which is in the middle of processing hundreds of web requests, talking to however many database tables, all of a sudden disappears. Database is kind of sitting there saying, "What just happened?"
- 00:35:45 Udi Dahan
- You open up a connection, you start doing these SQL statements and you don't commit. You don't close your transaction. You don't end up cleaning up your connection. So, it's sitting there waiting. Just so that you know, the default transaction timeout of a SQL server database can be as high as 10 minutes, not 30 seconds. Most people think it's 30 seconds because that's the HTTP timeout interval. But the database transaction time out is something else entirely. So, you could end up with hundreds of zombie connections in your database that are left lying around for minutes, resulting potentially in other servers not actually even being able to get a connection to your database, thus resulting in some severe downtime for your system.
- 00:36:42 Udi Dahan
- So, it's pretty bad when this happens, but it's kind of hard to stop servers from crashing or web servers from recycling the app pool. But eventually, the database says, "Okay, I give up. It doesn't look like these transactions are ever going to commit. I roll back." So, whatever data we put into the database is no longer there. It's gone. Whatever data we had in memory on our web server similarly is gone because the process has been recycled. So, now the $64,000 question is where's the data? Where's the hundreds of orders that the users were placing on my website? The answer is, well, it's not in the database, not in the web server. What should we do?
- 00:37:35 Udi Dahan
- Well, the way that most developers handle something like that is they'll say, "Not my problem. The ITOps people should have clustered things. They should have highly availablized these types of things. I used best practices. I used web API. I used Angular on the front end. I used transactions against a SQL server database on the back end. I did my job. If that results in data loss because of downtime, well then somebody should pick some cloud provider that never goes down." Does anybody know of a cloud provider that never goes down? No. I'm sure that you some of the vendors out there will say, "No, no, no. We actually give you that. You just need to pay an arm and a leg and a kidney and proverbially you're firstborn, and then we take care of that for you. Everything will be highly available all the time."
- 00:38:36 Udi Dahan
- But if you're not willing to give up your firstborn, there's another solution that works really well for commands, and that is to introduce a little bit of queuing into the behavior of the command processing and a little bit of service bus technology over the top, such that when the user is saying, "I'd like to buy an item," or is submitting some other kind of command, what we do is we get that as a message into a durable queuing system. This could be RabbitMQ if we're talking about on premises. This could be Azure Service Bus or Azure Storage Queues if we're talking about the Microsoft Cloud. This could be Amazon SQS if we're talking about the AWS Cloud, almost every type of deployment environment that you can think of has a queuing technology.
- 00:39:25 Udi Dahan
- Queues are such a good idea in software that they've been around forever and there is just a plethora of implementations available. Pick one. But having a durable queue is not always enough. You need something else that will manage the transactions of pulling the message out of the queue, invoking your own business logic for that, where you'll then go and talk to a database, and either it'll make use of some distributed transaction stuff, which for example, MSMQ and SQL server supports, or if you're using RabbitMQ or Azure Storage Queues or the type of queuing technologies that don't support distributed transactions, then that bus technology will use all sorts of cool little implementation patterns like the outbox, you can Google that, NServiceBus Outbox, that gives you a kind of de-duplication safety that will make sure that a given transaction is processed just the one time.
- 00:40:27 Udi Dahan
- So, if you want to figure out how that kind of stuff works behind the scenes, that's not this talk. You can go and find the information in the documentation online so that whenever something bad happens, whether you're processing server crashes, whether the database runs out of connections, whatever, when all of that rolls back, not only does the database roll back, but the queuing system will roll back that message, meaning that you don't lose any data. The million dollar order that was submitted on your website is back in the queue and then you can process it again. Now, there's little bit more complexity to how a bus works, but that's roughly what it is at a high level and why you want to have one for when you're processing data that's coming in from your users.
- 00:41:21 Udi Dahan
- So, what does that look like in terms of code? In order to send a message into a queue, it looks something like this. You have some controller, and it could be that sales controller or the pricing controller or what have you, that is handling that HTTP request and you turn around for that HTTP request and create a object. In this case, we're talking about the RecordConsumerBehavior message because we want to track what it is that the user's doing on our website, and we take that and we send that into a queue. So, this API, await session.Send, with a message object is the NServiceBus wrapper for the queuing system. So, it's really that simple. Two lines of code to put a message into a queue, and it does all sorts of other nifty things that we're going to talk about in a minute. Now, let's talk about the back end side of it, of what it means to handle a message.
- 00:42:25 Udi Dahan
- So, here again, we have the part of our system that is tracking the user's behavior on the website, where it is implementing the interface. IHandleMessages of RecordConsumerBehavior. That's the message object again, and in our handle method, it does the actual work. So, what we're doing over here in our sample, just to make this a little bit interesting, is we're simulating that we're doing some work by putting in a task delay. The reason that we do this is so that in just a minute when I show you how we monitor all of these different processes, that we'll actually see some kind of load in the system because when we don't have any task delays, it just runs too fast and you don't get to see anything interesting that's going on. So, now that we've talked about that, let's go look at that bit of code, the sending of the messages, the handling of the messages, and then from there, some publish/subscribe that's going to be happening behind the scenes between our services. So, onto code.
- 00:43:34 Udi Dahan
- So, for this button, remember what I said, we want to do two things. The user wants to buy an item and we want to record the consumer behavior. So, what we have here, I'm going to start with the RecordConsumerBehavior because that's what marketing is doing. Marketing, as a part of that behavior that the user is doing, has a BuyItemPostHandler. So, again, it's implementing that view model composition interface, IHandleRequests, that allows it to say, "I want to handle when the user clicks the buyitem command." In other words, the HTTP post is buyitem, for the URL called /product, /id. So, again, that's our matches behavior for an action as opposed to just for a query. Then here's that code that I showed you before where what it's doing when the user clicks that button is it turns around and puts a message into a queue. Okay?
- 00:44:42 Udi Dahan
- So, here we have one button click handler from one vertical slice, and now what we're going to do is look at another vertical slice that is handling that same button click. So, we'll go into sales and inside sales again, we see a buy item process handler, which again is doing that IHandleRequests, handling the HTTP post by item action for the /product, /id, and for that button click, what it then does is it sends its own message saying the user wants to place an order. Reiterating, why is this a good thing? Now, I don't need to have a big orchestrating controller that I need to update anytime the business says, "I want to do something else in addition to what is already happening when the user clicks Buy Item." In other words, I can enrich the behavior of my system for each user action without having to change existing code from other vertical slices. I can continue putting in new code that plugs into the existing behavior of that system.
- 00:46:10 Udi Dahan
- So, now let's look at what happens when we're handling the PlaceOrder command. So, notice here I have the PlaceOrder command inside the sales internal project. So, sales internal is the place where I'm putting the messages where sales front end code is talking to sales back end code. So, this is just internal vertical slice communication without anybody outside my slice knowing that I'm doing anything at all. So, here's my PlaceOrder command. As you can see, it's a fairly simple object, just a DTO with some get; set; properties on it, the order ID, the product ID, etc. Now, when I look at the sales API, the back end part of it, I have a message handler that is called ... Well, actually, this is the part where things get interesting.
- 00:47:15 Udi Dahan
- So, what's going to happen here is that I don't have just any old regular message handler. Instead, what we have is a kind of longer running process. What we've discovered is that when users go to buy stuff online, especially iPhones, it turns out that they sometimes cancel their order soon after. Now, although this is being recorded, this has never happened to me. Okay? I have never gone online when a new iPhone has come out and immediately done an impulse purchase of the new version of the iPhone where five minutes later, my significant other walks by and says, "Hi, hon. What you doing?" Then I never immediately said, "Oh, nothing," and immediately canceled the purchase that I had just not made.
- 00:48:19 Udi Dahan
- Now, from the perspective of the company, they've noticed that this is happening kind of a lot for the new iPhones that we're putting out there. So, when the user clicks Place Order, if we immediately start going to the warehouse and getting stuff out and charging the person's card, and then five minutes later, they cancel their order, it's like, oh, crap. Now, we need to refund their purchase to the credit card, but unfortunately, I still have to pay the credit card company for the action that we've performed, and then we got to go and take the iPhone out of the bin and put it back on the shelves. It's really a wasteful operation when someone buys something and then immediately cancels afterwards.
- 00:49:07 Udi Dahan
- So, we put some logic in there to say after the user clicks the Buy Item button, what we're going to do is we're just going to sit on that order for a while just in case they cancel their order soon after. So, the way that we do that is that we have this thing, it's called a buyer's remorse saga, and there's this thing called buyer's remorse is over in the solution. But let me just find that buyer's remorse thing. Inside sales. Is that the one? Okay. So, this is the cancel order part of ... Oh, okay. We didn't actually put in the behavior. There it is. Okay. Here, I found it. Sorry about that. Intermediate blackout of the speaker. So, what we have here is an OrderAcceptancePolicySaga. Sagas are this feature of NServiceBus that allows you to introduce a delay between the processing of one message and another message.
- 00:50:28 Udi Dahan
- So, here you can see that we have an object that is started by a PlaceOrder command. It also handles timeouts for when the user's buyer's remorse period is over, and it also can handle the CancelOrder. Now, again, we don't have time to delve into all of the nifty things that sagas enable you to do, but what I'm going to do is I'm going to run this so that you can see that behavior in action, that when I click that Place Order button, what's going to happen over here is I'm going to put in ... Well, actually, what I'll do is I'll just show you the log output. So, there's all of our back ends. Here's the front end and once it pops up, I'm going to go buy an item. So, let's go buy an iPhone X. I'm going to click Buy Now, and it says, "Your order has been placed." When I look at the My Orders section, it says, "Hey, you want to cancel your order?" Like, "Uh, yeah."
- 00:51:38 Udi Dahan
- Go cancel the order. What's happened here behind the scenes, I'm going to pop up sales, is that there's that thing in there that's saying the MessageHandlers.OrderAcceptancePolicySaga is now waiting for the grace period to see if the user cancels their order, and because I did cancel my order, it then associates that with the previous saga instance and then knows, oh, okay, great. Let's just get rid of that order without telling anybody. Here, we can see that our other services, billing API, for example, has not received a thing. It doesn't know that any order was placed, which is exactly what we want to have happen. So, now what I'll do is I won't cancel the order. I'll go in and actually buy my item. What we'll see happening back inside sales is that it has accepted that request and it's sitting there. Right now, I think we're opening up a timeout for about 15 seconds to say, "Let's see if the user canceled their order."
- 00:52:52 Udi Dahan
- Now, if the user doesn't go ahead and cancel their order, then the saga will spring back to life and say, "Oh, okay. It looks like they didn't cancel their order." What I'm going to do is I'm going to publish an event to say, "Now everybody can go handle this thing." Now, here, what you can see is that billing has now received that event. The OrderAcceptedHandler has been triggered, we've done a publish that an order was accepted, and we'll also see that inside shipping, shipping has also received that OrderAccepted event. Now, shipping is also doing a saga and you can see how it behaves there because it handles the OrderAccepted event and the OrderBuild event and associates those two events together. But ultimately, all of those things enable us to go and take action behind the scenes.
- 00:53:49 Udi Dahan
- Why is all of this pub/sub even important? So, I've been talking code, code, code for a while. Want to zoom back out for a minute and talk about our higher level picture, our vertical slices again, where, again, what we have here is that lightweight pub/sub happening behind the scenes. The reason that this is important is it allows us to have multiple vertical slices engaging in a given back end flow without creating coupling. So, when the business says, "I want to award the customer loyalty points for every purchase that they make," I can create another vertical slice that is going to subscribe to that CustomerBuild event or the OrderPlaced event and have that logic off to the side without making any changes to the code that's already in place. That's the advantage. That's what I'm trying to achieve, that ability to grow my system functionality over time without creating more complexity through bigger orchestrations. Okay? So, that's the point of all of this exercise.
- 00:55:13 Udi Dahan
- Now, what I'm going to do is I'm going to go really briefly through some of the other nifty things that happen when you use messaging. So, really quickly, message processing failures. It could be that your database is down. It could be that deserialization fails when you deploy a new version of your system into production. Of course, that's never happened to any of you here, but I'm pretty sure that you know of somebody who's had that problem once upon a time. Queuing technology. When it sees that it's processing that message over and over again and it keeps failing, it can bump that message out to a dead letter queue. We can do more than that when using NServiceBus and the rest of our service platform.
- 00:56:03 Udi Dahan
- So, when a message fails, we take that out of that error queue, put it into some back end process, and show visualization on top of that and say, "Hey, a message failed." This was the specific message type that failed. Here was the exception that caused it to fail. This is the number of those types of messages that have failed. You might want to deploy and roll back to the old version of your system where everything was working okay. Then there's that button over at the bottom there that says, "Retry it." So, you can send all of those 206 failed orders back to be reprocessed again, once again, without having lost a single piece of valuable business data. So, this is something that administrators really start to appreciate when they need to do continuous deployment. You might have heard of continuous deployment before. The problem that nobody seems to speak about in continuous deployment territory is what happens when you screw it up and your system loses data? You don't want continuous deployment to equal continuous data loss. That would be a bad thing, right? So, this service platform gives you a lot of that kind of safety net.
- 00:57:27 Udi Dahan
- Other things, auditing and tracing. Most queuing systems give you the ability to copy messages that are processed somewhere else. That's nifty and great, but when you have hundreds and thousands of parallel requests from different users all being audited together and all of this asynchronous pub/sub happening behind the scenes, it's really difficult to know what actually happened in the system. Within NServiceBus, what we do is we decorate all of your messages with a correlation identifier so that when message number two is published as a result of processing message number one, when we audit that, we store that trail of breadcrumbs so that that way, when you have a given flow through your system where PlaceOrder resulted in OrderAccepted and OrderAccepted triggered a saga and then saga triggered something else, which triggered OrderBuild and which triggered OrderShipped, and you have all of those events happening in parallel for multiple users, we can make sense through these breadcrumbs across all of the message headers, taking all of that audit stream, putting it into some kind of indexable database, where then we can create a nice kind of visualization, here is what happened in your system.
- 00:58:52 Udi Dahan
- That endpoint sent a message over there. When over there, we published an event. We triggered a saga. That saga resulted in this other thing, and then we can trace everything as it flowed through the system. In addition to that, what's really important when you have a queuing system, everything's async, you still want to know when the system gets slow. With traditional HTTP, when the system gets slow, users complain because they're just waiting for the page to load. When you start introducing messaging, everything that the users do is async so they don't even notice that things are going slowly.s But you can have messages building up in your queue, and that can be a problem. So, most of the queuing systems will give you some element of visibility into what that looks like. Give you a quick peek at what that looks like with RabbitMQ.
- 00:59:45 Udi Dahan
- So, RabbitMQ has this type of management environment where it will show you for each one of your queues, what is the number of messages that are in there, and if you want, you can drill into a given queue and see ultimately the flow that's happening in there. Now, what I'm going to do is I'm going to run the solution again, but when I run it, I'm also going to include something that generates some load so that we can actually see this thing running and doing something because I can't press the button fast enough to actually generate meaningful load. So, as a part of this solution, at the bottom, we have this thing called a load generator. So, I'm going to bring that thing up, and what it's going to do is it's going to start pushing messages. You'll see it kind of logging. It's saying sending a PlaceOrder message, sending a PlaceOrder message, etc., and we can start seeing in our back end sales is chugging along and processing those messages.
- 01:00:53 Udi Dahan
- When I look at RabbitMQ, where's my RabbitMQ, we can see, once RabbitMQ wakes up again ... Well, right now, it's processing too quickly. So, we can't actually ... Oh, there it is. RabbitMQ just woke up. We see the graph ticks up and it says, "Oh, okay. Something is happening for that queue." But if we're looking across all of the things, we can't get a good sense of where things are. For that reason, with part of the service platform that you get with NServiceBus, we have all of this type of information that shows you as well, not just the number of messages in the queue. It'll show you the throughput of messages processed per second, retries, which is a really important thing to monitor in your system because it could be that everything is processing okay, but if it takes two or three retries, that could be an indication that something's not designed very well in your system. Right there, you saw that there was a blip at the bottom that was in our shipping API, where potentially because there was a database deadlock or something like that, that is what resulted in the system getting slower.
- 01:02:05 Udi Dahan
- We also tracked the processing time and this thing called critical time. Critical time is a value that says roughly speaking, how long is it going to take for the next message to work its way through the queue and complete its processing? That can be very meaningful when you're in an SLA-sensitive domain when it matters how quickly you process things. So, there's a lot of this stuff available as a part of this broader platform, and ultimately, if you're using queue based systems, you're probably going to want to have these types of things. So, again, whether you're using RabbitMQ or MSMQ or SQS, each of them provides something. You often will require all of these types of elements in place. Now, getting to the end of my time, but I want to show you some other nifty things. So, talked about visualizing the system, and that's performance monitoring. My machine is barely keeping up with all of the load.
- 01:03:18 Udi Dahan
- So, all of this that I've been showing you works across multiple stacks. So, if you're on Azure, you can use Microsoft Azure Service Bus, you can use Azure Storage Queues. If you're working on premises, you can use MSMQ. You can use RabbitMQ. If you're working in the AWS stack, you can use SQS, and even you can use SQL Server because sometimes people don't want to install a queuing system. They just want to say, "Can you make it work on the database?" So, ultimately, you can get all of that decoupling without taking on any other infrastructure. So, one more thing, because I know I've been saying, "Hey, this is .NET Core 2.0," and you've just sort of taken my word for it. What I want to do, and I know I'm a little bit over time, is I want to show it to you now running on a Mac, and then afterwards, what we're going to do is we're going to run it on Linux running in AWS. So, the exact same solution that you've been seeing up until now, but running the way that you'd expect in production.
- 01:04:27 Udi Dahan
- So, David's got the Mac, and we're going to switch over the screen and away we go. So, what we have over here is Visual Studio on the Mac. So, when we're running this, ultimately what you're seeing here is that same UI that's going to be popping up. Just give it a second, as we know that it can kind of take a minute. But again, this is native Mac development. This is not Mac via Parallels. This is none of that. This is actual running on a Mac .NET Core locally. So, you can do all of your development with .NET Core, and again, all of this is happening with the pub/sub and with the reliability and all of that. So, let's go buy an item really quickly and see that that process is working. So, we buy an item, we go into the orders page, and we'll see that there is an order there.
- 01:05:33 Udi Dahan
- The challenge with running Visual Studio on the Mac is that it's much more difficult to see the log output of all of those consoles. What it does, it just sets them up as little console windows over there. Now, that can be a bad thing in the sense that it can be difficult to see as the system is running. But on the flip side, at least you don't have 15 windows that open up every single time you go and debug your system. All right? So, now we've run it on the Mac running locally. Now, what we'll do, let's switch over and see it running on Linux in AWS. So, what we're doing now is we're in an Ubuntu shell. Correct?
- 01:06:17 David
- Well, Mac still.
- 01:06:18 Udi Dahan
- Oh, it's still on the Mac, but we're going to be from there going into our Linux Shell. From here, we can now run our entire solution. So, here we can see we've got all of our .NET projects in there and we're going to run these just via this start-all solution script. We can have all of them spinning up to life. So, you can see in those comments, it's spinning up the marketing API, it's spinning up the sales API, it's spinning up the billing API, and now we can see we have all of these .NET processes that are running. So, currently, we're not having any kind of log output as a result of this, but when we start putting some load into this thing, then we'll start seeing the behavior happening. So, right now, if we look at the URL, we can see that we're actually connected to the Linux thing. So, it's remotely connecting. You can't really zoom into that, but the URL there is netcoredemo.particular.net. This is actually going and running against that AWS environment. So, it's connecting to that cloud-hosted environment.
- 01:07:40 Udi Dahan
- All right. Now, let's take a look at the monitoring side of things. Here, we can see all of those endpoints that are up and running in the cloud. They're not currently processing any load. That's not very surprising because we haven't clicked any buttons. But now what we'll do is we'll invoke that load generator and we'll be able to see as those messages are coming in, let's actually ramp up the load a little bit so that we can get some more messages flowing in. So, right now, it's sending through just about six messages per second in parallel, and we can see that each of those endpoints now has a little bit of a spike as it's processing them. So, again, what you're seeing over here, the exact same solution that was running .NET Core on a Windows machine and now running in Linux with all of the monitoring and tracing kind of stuff as before. We also have service insight open up.
- 01:08:34 David
- Oh.
- 01:08:36 Udi Dahan
- Oh, okay. So, now for the final piece of the puzzle, as they say in Steve Jobs terminology, just one more thing. So, all of this is great because I have a new .NET Core project running Linux AWS and everybody's happy with that. But now the business turns around and says, "We have this old warehouse management system that we've lifted and shifted and we're running it in Azure. We want the new system that you've created to subscribe to events from that legacy system. The only thing is our new system is running RabbitMQ. The old system is running with Azure storage queues, and now we want these two systems to talk to each other." So, what we can do here, you can close that down-
- 01:09:36 David
- Okay.
- 01:09:39 Udi Dahan
- ... is go and look up that Azure website. That's our warehouse management thing that allows us to influence the stock of a given item. So, let's switch back to the UI of our shop and then we can see that currently, the Galaxy 8 is not currently in stock. Now, what we'll do, we'll go into our stock management website, we'll switch that and provide it some stock, and when we go back to our website for the sales and we refresh that, what we'll see is that now that item is in stock. In other words, we had the publish/subscribe behavior happening from the Azure cloud into the AWS cloud, .NET full framework ASQ on one side, going to cloud-hosted RabbitMQ running Linux AWS on the other side, and all of that is enabled by this end NServiceBus thing. So, when you're starting out on a project, whether you're talking about a brand new system or a legacy system that you need to connect to, we try to give you that layer of abstraction and this whole set of tools around all of that that will enable all of that to work.
- 01:11:04 Udi Dahan
- So, just to wrap this up, again, all of the code that we've shown you is available online here, github.com/ParticularLabs/NetCoreDemo. Whether it's the UI composition piece that we started out with, the publish/subscribe piece, the monitoring tooling, the error management, all of that is available online. Give it a try, see how it works. I hope that this has given you an idea of how possible it is to actually build and run and scale this type of vertical slice architecture. It's not just a theory hand wavy thing that you go to a conference and say, "Oh, that sounds like a nice idea, but I don't know how to do that." Now, you have the code to actually implement that, and you've seen all of the pieces of it from whether you're querying a single item, whether you're querying multiple items, whether you're taking action when a user clicks a button in such a way that you have a truly decoupled solution.
- 01:12:19 Udi Dahan
- So, if you want more information about how to find those service boundaries, because that's pretty tricky as well, there's a video of a presentation that I've given at an older NDC. You can find the link to that there, bit.ly/finding-service-boundaries, where I talk about the business analysis that you'd be going through to be able to say, "Well, what actually goes into which vertical slice?" So, I hope I've given you a lot of food for thought and a lot of code that you can take home and play with. If you ever have any questions, feel free to ping me, whether that's over Twitter or go to the particular.net website and chat with us. Thank you all very much and have a great rest of your conference.