NServiceBus: Lego style
About this video
Explore the depths of the NServiceBus API. See how handlers get invoked and what is involved when trying to invoke a message handler.
🔗Transcription
- 00:01 John Simons
- This session is all about accessibility in NServiceBus version 5, the upcoming release. I think in day one, Charlie, Mark, and Roy, they've showed really cool stuff, what they were doing and plugging in and extending in NServiceBus. And hopefully the stuff that we are going to be showing today, it actually makes your lives easier. So take it away.
- 00:31 Indu Alagarsamy
- So before we talk about the awesomeness in v5, let's first talk about the coolness that we currently have. So in version 2, we had the concept of Message Modules, what it essentially allows you to do is hook into the message incoming message pipeline, where if you wanted to do a set an action before and after, at the end of processing each and every message, you if you wanted to do something, then you could plug into the Message Module, and it would do that for you. But the limitation of the Message Module was, it would, you could do that only on the receiving side of things. So if you wanted to do something very customy, on the outgoing side, you had no choice. And that was solved in version 3 with the Message Mutators, like Roy showed us yesterday, they had the Message Mutator in on the incoming side, and they had a Message Mutator in on the outgoing side. So when a message came in, through to be handled the framework knew, okay, I got to do something custom first, then I invoke your handler, and then I got to do again, something specific and then dispatched the message to wherever it needs to go.
- 01:45 Indu Alagarsamy
- So that gave a little bit more extensibility. And then in version 4, we had the Unit of Work. Unit of Work, essentially simplified the Message Module. So and it's awesome when it comes to writing your own persistence, where if you wanted to share the same session across the entire transaction that gave you that nice behavior, the other way of how you can also extend the message handling mean, is through the message handler itself. So, if you wanted to do authentication, for when a message comes in, you wanted to do a bit of, hey is this is good enough, is this authorized before we actually go ahead and invoke if you wanted to do that sort of thing, what you would do in the past is you would have, I handle messages of some iMessage, which is a common enough thing that you would have the authentication behavior put in, and then that would get called.
- 02:48 Indu Alagarsamy
- And then you would specify it as a, I specify message handler ordering, where you say, hey, this authentication handler is the first thing that needs to be handled first. And then the other stuff. If the authentication is good, then everything is great. Otherwise, you would call our longest method, I believe is first or do not continue dispatching message to other handlers have something really long. So you had that extensibility. So now, taking that extensibility further into v5, here comes the awesomeness. So in the past, when a message is received in the queue, before we call your actual handler, there's a lot of steps that need to happen. For example, if you have registered your unit off work, we need to call that if you have Message Mutators that needs to get invoked.
- 03:51 Indu Alagarsamy
- So you've got these, all these little bit of steps that need to happen first before we invoke your handlers. The same thing when you do a bus dot send from an API perspective, it's very simple bus dot send. But what the transport does is again, there's these little bit of steps involved before that message can actually be taken and sent dispatch to wherever the destination or to be. So the first logical thing that we did, or a logical thing to do is to separate these pipes into incoming and outgoing. So we've got two logical, separate pipes. And even in that pipe, you've got, I got to do this. First, I got to call the Mutators Unit of Work. So you've got these distinct little steps. So it made sense to kind of, even take that pipe, and break it into tiny steps.
- 04:46 Indu Alagarsamy
- So think of it as like a Lego block. So what this gave us is the real cool thing is you take these blocks and put them in any order you want, or you could take away a block or you could add your own blocks to it. So it kind of gives you really good flexibility on how you want to build, or you want to extend this message processing behavior. So taking these blocks, what it then translates to is, each then becomes a step. So, each step from the outermost step, calls the next step, the next step, the next step, and so on. So, you kind of have like, until the message handler is invoked, and the control passes over. So, how did we actually implement this? I'll let John explain.
- 05:47 John Simons
- So, anyone familiar with the Russian doll model? Excellent. So, we rely on Russian doll model to actually do the nesting of the steps. So, in the previous slide, we were talking about block steps. And in this slide, you can actually see that there is a thing called an IBehavior. So, there's a difference between a step and the behavior were they related. So you first you say, Okay, I want to create a step. And that step gets registered into the pipeline. And we'll show you how to actually do that later on. And then, as part of the registration of that step, you actually create implemented behavior, which is the thing that actually gives you the well, when you write your code of what you want to do. Now, because of this separation of the step, and the actual implementation code, it allows you to replace existing behaviors on its on a step on an existing step.
- 06:53 John Simons
- So the first thing that we did was we created an interface. So the Russian doll model, it's a very simple model, it's actually simple to implement in C#, all it does, it's, we've got an invoke method, we've got this thing called a context. And we've got the next step. So each, the actual outermost step is in charge of actually calling the next step. And then it goes again, and again and again until it gets to the end. And also, the context is the thing that gives us the flexibility of passing data between the behaviors. So think of a context just as a dictionary, where you can add data to it. So I want to send some data between step one to step two. So that guy can write data to the context that one can read it. But he can also this one can write data, and that one can read it back, because it's nested.
- 07:56 John Simons
- So we'll just give you a quick example. So it allows us to do coding like this, which we could not do before in the Message Mutators. In the Message Modules, Units of Work, you actually had two distinctive methods, you had the begin and end. Now with these nesting, what you can do, you can actually use the using pattern in C# for disposables, because you actually calling the next within your code. So it's a much more intuitive and simple API, I believe. Yeah.
- 08:29 Indu Alagarsamy
- And then the other thing is, previously, if you wanted to specify a certain thing that you want to extend, and you wanted to say that this particular thing needs to happen, right after the Mutator gets invoked, or right before or if you had a specific step, there was no easy way for you to specify that you would register in and we would through reflection, look at the registered staff and call them in whatever order. So there was no, if you wanted to say, hey, I want this to happen in this particular step, or right after this, there was no way. But now with now that we've broken this up into little, tiny Lego pieces, and it's easy, where you can say, hey, this needs to happen right before step A or and it needs to happen right after step C. So that kind of flexibility is now possible.
- 09:34 Indu Alagarsamy
- And so to talk a little bit more about the message processing pipe, like I mentioned earlier when a message is received from the queue, and then by the time the handler is invoked, there's a process of middle steps that that happened again when you do a bus dot send all the things that happens before the message gets dispatched. John actually wrote a program really cool program, which would, when you run the endpoint, it would look at all the steps that are happening before a message gets invoked. So that's actually available as a beta sample on GitHub on NSBCon, but essentially, kind of visualizes the pipe. So now you've got a way in which you can say, hey, okay, these all the little, tiny steps that happen before on the on this is on the incoming side, this is all the little things that happen, we need to execute units of work and Message Mutators.
- 10:38 Indu Alagarsamy
- And finally, we need to invoke the handlers. So if you had something that that was very specific, that needs to happen in right after, you could now say. Ah I have this behavior, it goes right between that step and that step. So it kind of like, you can now plop that new behavior in where you wanted to. And again, the same thing on the outgoing side, are good cop, bad cop behavior, the enforce best practices, that happens first, and then all these little things, and then you dispatch the message to transport. So this kind of gives you a visualization of the little steps and the little Lego pieces that are involved before a message gets dispatched out or.
- 11:23 John Simons
- This is going to be a very important tool when it comes to extending the pipeline, because otherwise you don't have. Well, we can't see. So this is basically just a drop in assembly that is dropping into your endpoint and starts up a website, and it shows you things. It's going.
- 11:43 Indu Alagarsamy
- Cool. So now let's build a new behavior. We'll add a new, we'll show you how you can add a new step into the pipeline, we'll show you how to replace something and get going with Visual Studio.
- 12:08 John Simons
- So Indu is actually going to show something that I think Charlie talked about in, there was a slide about I think Charlie was figuring out how long did the actual serialization took. So now we the pipeline, it's actually easy to do like a stopwatch before and after.
- 12:31 Indu Alagarsamy
- So let's say we wanted to measure like, hey, how long is my message handler taking in and say you had, if my message takes X amount of seconds, and I want to do something, type of behavior. So first of all you would code up your behavior, which is, you would implement the behavior. And like John mentioned, and we've got two separate contexts we've got two separate pipes, one on the incoming and one on the outgoing. So the incoming context has relevant data that you would need throughout the lifetime of that message handling until the handler gets invoked. So you've got that piece of data. Now, again, John mentioned earlier, the cool thing is, like you could stick your own piece of data in between steps to kind of pass that data along to the next step, and to do certain things you want. So simple thing, you implement a behavior, incoming context. And here we do a stopwatch and.
- 13:40 Indu Alagarsamy
- Okay. Is that better? is that better?
- 13:54 John Simons
- Maybe collapse your windows, and there you go.
- 14:02 Indu Alagarsamy
- So, you implement the behavior, so simple, you get the invoke, and that's where you kind of write your logic of what needs to happen. And then of course, you would call next. So that kind of calls the next inner step in the Russian doll. So and here. So once you've done that, you've got you've got to behavior now, you got to let the system know that. Okay, where does this need to go? So you kind of specify I got to do this, right before my handler is invoked. So you kind of specify that say, okay, call me right before invoke handlers. So you kind of specify that and again, this is a step. In this step. You have to let the step know when this when this is getting executed, what is the behavior that needs to get run, so you kind of say you pass in the behavior that you just wrote. And then of course, a nice decent description. So what it is.
- 15:08 Indu Alagarsamy
- And once you've done that, you now need to let the pipe know. Hey, I got this new step. Don't forget to call me. So that final bit of registration of like, Okay, I got a new step, that step needs to go in the pipeline. So that's pretty much it. Now let's.
- 15:25 John Simons
- By the way, this is new syntax in V5, the uninitialized. Now things Configure.
- 15:34 Indu Alagarsamy
- Cool, so let's start. Is that better. So now, I'll send a few messages, I have a random sleep between the message handlers. So some messages would take longer to process. So now my behavior kicks in, it saw that, Oh, this one took more than three seconds, and then that thing gets executed. So that's how you add a brand-new step into the existing message pipeline. And now I'll let John show you something else that's awesome.
- 16:20 John Simons
- All right. So what I thought about doing for this presentation is actually do some kind of live coding. And I'm sure Nothing will go wrong. Everything will work.
- 16:34 Indu Alagarsamy
- We didn't say our prayers to the demo gods.
- 16:43 John Simons
- Is in one issues that we have in NServiceBus is that you got to define up front, your serialization. So by default, the serialization that's out of the box is XML. So everyone is familiar with it. And nowadays, JSON is looking pretty cool. So wouldn't it be good to somehow be able to create the next system in using NServiceBus using JSON serialization, but still be able to send messages between the old ones using XML and the new one using JSON. So what I'm going to do is, in the slides that Indu had before, there was an actual step, that that was called the serialized step. So what I'm going to do is I'm going to actually replace the behavior of the deserialize step, to actually create a multi deserialize step. So they will look at the content of the message and decide which serialization to use to deserialize the message.
- 17:48 John Simons
- All right, so let's start. So as you all aware, or you're not aware. In V5, we've gone version framework .NET 4.5. So that's the first requirement. And let's create now firstly the class. Okay, I was told to change the white background is that in the options.
- 18:35 Indu Alagarsamy
- Those options, make that.
- 18:38 John Simons
- Okay. And NServiceBus V5. Now I'm pointing to our continuous integration build server. So you can see that the package source is actually CI. And I'm using version 5, which hasn't been released yet. So I think we need to go with the prerelease. So you can see that Visual Studio now is downloading beta 1 from our build server. Alright, so I thought that I take a shortcut. And I'll have a look what those guys that particular wrote for the single serializer. So I'm going to go just to GitHub and do a search for the serializer. And there we go. So this is the actual implementation of the current deserialization of .NET alpha NServiceBus version 5. So what I'm going to do is I'm going to take a copy of all then I'm going to paste it in here. And I've got a beginning of my code. So the first thing I'm going to just do is just change that to the multi-C. capitalized D, get rid of a few things that I don't need.
- 20:25 John Simons
- Just do that I don't need to worry about control messages. I've actually upped my font size, but obviously, always more. I'm coding.
- 20:44 Indu Alagarsamy
- Set that to like 250.
- 20:47 John Simons
- 250? That's going to be a little square to code in. Okay, we just do this thinking right now. So I was just doing is just cleaning up a bit of the code, before I start, and then we will have a look at the what code left, it should be in a state that that can actually compile. And then what I'm going to do is when Visual Studio finishes doing what it's doing, we're going to start coding. So before we talked about how you can add the existing state add new steps, you can also replace the functionality of an existing step. And there is also the remove capability of a step. Now the Remove is actually a little bit tricky, because if any existing step is dependent on that step, we don't actually allow the removal. So you can only remove things that nothing depends on. Okay. So if we look at, we'll just go with multi.
- 22:08 John Simons
- If we look at what this thing is doing, it's so you've got your invoke method in, we talking about the incoming pipeline, because that's what we want to do. So the message comes in, we want this deserialize that message and pick the right deserializer to deserialize the message. And so we will retrieve so when I say that the context is just a dictionary, it is just a dictionary, that's why it's implemented in the background, except that we've exposed some well, nice properties to have. So one of them is the physical message. So we get access to that physical message is really a transport message. As you can see that. And we call this extract method, we go into that extract method, what we do here, we do some stuff with headers to figure out what the message type is. And eventually, we call this message deserializer.
- 23:07 John Simons
- Now that message serializer, actually, using IOC, it gets injected. So that is the default serializer that you've got pre-selected at configuration time. So that would be the XML message serializer. Now, I don't want that I want to be more dynamic. I don't know what I want, I need to look at the message header. There's the there is a message header content type called content type, that will tell me what serializer to use. So what I'm going to do is, I'm going to start by creating a dictionary. And I'm going to go, I'm going to store in this dictionary, I'm going to store the actual content type and a serializer. So which is an IMessage serializer. Okay, so now we need to feel these dictionary with some deserializers. So I'm just going to use a constructor for that.
- 24:28 John Simons
- So the first one that I'm going to the dictionaries well the XML message serializer. So that one will just create the XML message serializer, new XML message serialize and I need some stuff to pass the weight which I need iMessage mapper and some conventions, okay. So I'm going to use IRC for that. And conventions. Okay, so we've got the first one. And I'm going to add it to my dictionary. So as I said, each serializer that it's actually exposes a content type property, it will tell me what the content type is. Think of it as the HTTP content type, pretty much the same. And we've got some XML. So the next one that I'm going to add to our dictionary is the JSON. Now that one just needs the mapper. And, JSON. Okay, so we've got two of them, I can keep on going. So let's just add one more just to be complete.
- 26:21 John Simons
- And binary. Now that one doesn't take anything. Excellent. So we just go there and add binary, dot content type, and binary. Okay, so we've got our dictionary, fully populated with all our serialization that we've got out of the box. So the next thing we do is actually use it. So what do I have to. Oh, actually, I'll just do one more thing, just to be sure that we are covering everything. I'll just call these the default serializer. Get rid of that one. And I'm just going to check because you guys could have used the custom serialization. So I'm just going to add that one as well. So what I'll do is I'll check if the dictionary does not contain the default serializable dot content type. Just change that to. No, we love it. Default.
- 27:57 John Simons
- Okay. So now we've got our dictionary, fully populated. We've got our code that's going to be invoked. And we've got our serialization here. So what I need to do is just change these extract method to take a string content type. I will pass the content type to it. So first thing I need to do is actually have a look at the physical message headers, so the transport message and have a look at the headers of that. And I think we've got the headers dictionary and content type. Excellent. So if it contains that header let's grab it. And the default header is going to be the default. So, I'm just grabbing the defaults serialization and just defaulting to the default one if we don't have a header. And now, I'll pass that to them.
- 30:23 John Simons
- So that's looking good, I think. So now we passed the content type, we need to use that content type. So that's actually quite simple now because we've got this dictionary. And we should be able to just do that. And I think I'm done. So if everything I've got have sacrificed the right animals, today, that should work. Let's test it. So let's just build, yep, it's building. ReSharper tells me that it's all green. So to test this, I'm just going to create two endpoints, a message class, and we're just going to send a message and then do a reply, full duplex sample. One second. Just before I actually start creating more projects, what I'll do is remember how to show you how to actually register behavior. So what we need to do now is actually, well, we need to replace an existing step with these behavior.
- 31:44 John Simons
- So I'm just going to create a class register. And that's going to be I need initialize. Goes there, we go config the pipeline, dot replace. Now I need to know what the ID of the step that I want to replace. So that's where you go to that graphical tool when you get it from, I happen to know what that ID is, which is this serialized messages, we try to name them. Well, best we could to make sure that they would file names, so that you didn't actually have to. Okay, and that type of is that. Okay, so I've taken care of the registration of the pipeline, of the new behavior. I've created my behavior. I'm ready to test these, I think. So, let's go and create a new project. So think the first one that I'll do is deny messages. So as I said, we're just going to, install the NServiceBus on that.
- 33:21 John Simons
- Excellent. So the first one I'm going to do is, I'm just going to call my request. I'm going to.
- 33:32 Indu Alagarsamy
- Zoom in the screen.
- 33:42 John Simons
- Oh, sorry. Yeah. Okay, so it's going to be a little commands. What I'm going to do in this request is actually so that we've got a visual idea of what the message is, and all that stuff, I'm going to just have a little property string. And I'll have content type. So, I'm going to populate, and then I'm going to do my reply. That's going to be called my reply. I'm very original with naming. And that one will just have another property called a string and message and get rid of that. Probably need to make that public. Okay, so how are we traveling? Good. Alright, so we've got our messages. Now let's, now write our one of the servers that we're going to use when a new endpoint. So these are my sermon. Zoom-in, beautiful. Now, my server needs a host. So just go here and install the host. Beautiful.
- 35:21 John Simons
- So, we should probably make this guy be the one that does the JSON. So just create a class, without any time cutting it.
- 35:51 Indu Alagarsamy
- Zoom in.
- 35:51 John Simons
- Zoom in. So what am I doing. I need to specify the serialization. So we go serialization, and I want to use JSON. Okay. So that's the first one. We also need a class to send messages. Yep. So let's send messages. So I'll have a class, sender. I want to run a startup and stop. Implement that. I'll take that one out. Just going to do a while loop console dot read key. What's the read options. Read key dot equals to console. Q for quitting. Sounds like good. We're going to inject our bus. And we do a bu dot send. And we the new out, I haven't actually added properties to it. So I just need to go to there, references, add references to both of them. And my request that I'm going to populate my content type with JSON. That is one server.
- 37:58 John Simons
- Now what we're going to do is just update... Just going to update the message mappings because I'm going to send it to another server. No, hold on. Okay, so all I'm doing is just that one. Lets go view my messages, is what I called that one. And so the first one was called my server and this one is called my serve 2. Okay, so quickly now create a new server. Zoom in. Install the package on my server too. Five minutes gee, time goes doesn't it, when you having fun. Okay, so my server 2 is actually quite similar to these one. So I need one of those. That one. Want to change that one. So I'm just going to make sure that it is XML but by default these XML anyway.
- 40:02 John Simons
- And I just need to add more references to it, I think. So I need messages in the serializer. And also, I'm going to create a class here to my handler, I handle messages of my requests. And I'm just going to do a quick reply. We just do a string from... Hopefully. What's this message.ContentType. All right.
- 41:24 Indu Alagarsamy
- So far, so good.
- 41:30 John Simons
- Let's see it, I think I'll probably need to just show some stuff on the screen. So I'll just do out there. Okay, I'll just show the content type on that one. And we just go to my server and also display handler. There. So that one is going to be my reply. And message and actually delete all that. I don't even need that. Let's see if these were going to work. I'll just set startup projects, multiple. I want to start that one. And that one, okay. And there we go. Now, I think was in my server, the one that had the console. So let's see if that's true. Hang on. Just make sure I'm doing message. As usual things. Not going to debug then. I'm going to open prepared one. Got to be prepared for all these things over time. All right, this just to show you, it's actually in the prepared one, I actually have multiple, more than one.
- 44:40 John Simons
- So in the prepared one, I actually got a JSON endpoint. I've got the BSON endpoint we also have a BSON serialization. I've got my server here, and I've got the binary one here. So what I'll do is I'm going to send the message from binary. Now that my server is using the default XML, so hopefully, so that one, it says hello from binary. So it's just basically that message that I was sending. If I send a message from JSON a couple of messages and received it again, so my server is talking XML back to the JSON endpoint. JSON endpoint is...am I on mute now? And just for completion, here is the BSON as well. So there you go.
- 45:34 John Simons
- So now you should be able to. well, if you want change the serialization in NServiceBus.
- 45:39 Indu Alagarsamy
- So this essentially, just wanted to add that previously, if you had a server that was using a XML serializer and sending messages, or a client that was sending XML, then on the server side, you were wired up to get JSON, you blow up and throw an exception, and hey, I don't understand what you're speaking. So now with this way, we've added we've essentially switched or replaced the deserialization behavior where we've said that, okay, I can now. Okay, I don't understand what you speak. But I've got these other languages in my dictionary, I can look it up. And okay, now I know you're talking about and therefore, I can reply back in the language.
- 46:28 John Simons
- Yeah, and it doesn't matter which format you send, because it's the deserialization that it's actually just trying everything.
- 46:39 Indu Alagarsamy
- So, to summarize, we saw how you're able to get more extensibility more, we saw that how both the incoming and the outgoing pipes are now broken and not just broken, but individually. You've got these little steps that you can replace, or you could add more, and you can also specify what exact point that you want to add this behavior. So you as master builders now have awesome flexibility.
- 47:09 John Simons
- And with that. Thank you very much.