Supercharging saga development
Sagas are one of the most powerful tools available in the NServiceBus toolbox. With a saga, a business process that would otherwise have been implemented as a clunky batch job 1 can be built in a much more elegant and real-time manner.
We’ve focused on supercharging your ability to develop NServiceBus sagas in our latest round of releases. As a result, you’re going to feel like you’ve got your own “heads-up display” when developing sagas. We’ll give you suggestions and point out problems before you even hit compile. You focus on your business logic.
Let’s take a look at the new features.
Sagas have a powerful API, but it’s limited by the confines of C#. You can create a class that is a lousy saga but is still perfectly valid C# code. With Roslyn analyzers and code fixes, we can now do a lot better and help guide you toward the pit of success.
In NServiceBus version 7.7, we’ve added a variety of Roslyn analyzers that distill many of our saga development best practices and make them available as hints in your Error List window and as red squiggles 2 directly in your code.
Some of the diagnostics created by the new analyzers simply elevate a runtime error to compile time. This will save you time, as you won’t have to run your code to find out that you can’t use
DateTime as a correlation property type.
Others will save you time by doing some of the coding for you. Check out how you can now generate the code for the
ConfigureHowToFindSaga method when adding an
IAmStartedByMessages<T> to your saga:
That’s for a new saga, where the correlation id
OrderId hasn’t been defined yet. Watch what happens when we add another message
OrderBilled that already has a matching
Since the analyzer already knows that the saga data’s
OrderId property is the correlation id, and the
OrderBilled message also contains an
OrderId property, the generated code already includes the correct mapping, and there’s nothing more to do.
We created a bunch of analyzer diagnostics—15 in total—to supercharge your saga development. Check out Roslyn analyzers for sagas in our documentation for all the details.
🔗Saga scenario testing
While we were making it easier to write sagas, we thought it was also essential to make it easier to test them. So the newest version of our testing framework now includes tools to perform saga scenario testing, which are more expressive than testing sagas with standard unit tests.
This has always worked pretty well for regular message handlers, but it has always seemed a bit clunky for testing sagas. You have to understand too much about how sagas work internally to write effective and accurate unit tests, or you can miss important details. For example, did you know that NServiceBus will automatically assign the saga data’s correlation property value with the value from the first incoming message? How about that an external message handler replying to a saga will include the
SagaId in a message header, which means a mapping in the
ConfigureHowToFindSaga method isn’t required? These details can really trip you up, leading to tests that don’t test what you think they are.
There was also no way to test the
ConfigureHowToFindSaga method. At all. Unit tests have to call the saga’s
Handle methods directly, without exercising any mapping expressions inside the
ConfigureHowToFindSaga method, so you just had to hope really hard that your mappings were all correct.
With our new saga scenario testing framework, you can now do more than make assertions on the result of one message handler. Instead, the framework enables testing the result of a whole series of messages (a scenario) at a time, where handling each message exercises the
ConfigureHowToFindSaga mappings to load the saga data from a virtual data store managed by the test.
This enables tests like “Is the
OrderShipped event published after the
OrderBilled are received, and the buyer’s remorse period has ended?” or “If the messages arrive in a different order than I expect, will the order still be shipped?”
The testable saga also keeps track of a virtual
CurrentTime, stores saga timeouts internally, and plays them only when you call the
AdvanceTime method. This gives you complete control over the scenario and enables testing race conditions—what happens if a specific message arrives before a timeout fires, or the other way around?
We know this new testing framework will make your sagas easier to test. Additionally, we hope that the tests you write with it will be more expressive. With scenario testing, each test can tell a complete story that documents the behavior you expect the saga to exhibit.
🔗Critical time metric
In NServiceBus version 7.7, we’re adding information to outgoing messages to more accurately calculate the critical time metric, which tells you how long after a message is sent for it to be fully processed. This metric is essential to ensure that you meet your message processing SLAs and can also be used as a trigger to scale out infrastructure when it becomes apparent that the SLA will be breached.
We realized that critical time had a serious flaw when it came to sagas: delayed messages caused by saga timeouts include the delay in addition to the delivery and processing time. So if you had saga timeouts from a year ago (which is a normal part of some business processes!), it would look like your critical time was one year, when there’s actually nothing wrong with your system performance.
To fix this problem, a new
DeliverAt message header has been added to outgoing messages that include a delay to calculate the critical time attribute more accurately.
We also updated the critical time calculation in NServiceBus.Metrics version 3.1 to use this new information. If you update your system with NServiceBus 7.7 and NServiceBus.Metrics 3.1, the performance metrics in ServicePulse will reflect the new method of calculating critical time information.
A future release of ServiceControl will update how critical time is displayed for audit messages in ServiceInsight.
🔗Saga not found logging
One small (but important!) way we’re always trying to improve NServiceBus is through our logging and error messages. We want to make sure the exception and log messages we put in front of you are clear and let you know precisely what you need to do. So, as part of NServiceBus 7.7, we adjusted the logging that occurs when saga data is not found.
If a message is handled by a saga but does not start the saga, NServiceBus will log a message. Of course, it’s possible the saga has done its job and doesn’t care about that type of message anymore, so it’s not necessarily a problem—but it could be.
Previously the log message indicated that a saga was not found for a specific message type. However, the log would not include the type of saga that was not found. In cases where a message was handled by multiple sagas, the message would be shown only if saga data couldn’t be found for any of them.
In NServiceBus 7.7, we’ve made this a lot clearer. For each saga type where the data could not be found, we now log:
Could not find a started saga of
ShippingPolicyfor message type
And if all sagas that handle a message are not found, we log:
Could not find any started sagas for message type
OrderPlaced. Going to invoke SagaNotFoundHandlers.
This additional saga type information in the log should provide more clarity when investigating these types of situations.
We’re always trying to make development with NServiceBus simpler, easier, and more powerful. Whether it’s our new saga analyzers, our new scenario testing framework, or our fixes for critical time and saga not found logging, we hope you’ll find something that will supercharge your own saga development.
It may not be an actual video game heads-up display, but we do what we can.
To get all these updates, you’ll want to update to NServiceBus 7.7.0, NServiceBus.Testing 7.4.0, and NServiceBus.Metrics 3.1.0. Keep in mind that you might be using NServiceBus.Metrics as a transitive dependency of NServiceBus.Metrics.ServiceControl or NServiceBus.ServicePlatform.Connector, and because NuGet will only load the lowest matching version of a transitive dependency, you will need to add an explicit package reference to NServiceBus.Metrics 3.1.0 to get the critical time update.