NServiceBus on .NET Core - Why not?
.NET Core is out. Officially. After a lot of waiting, it's finally a real, finished, RTM thing, and as developers who are passionate about the .NET ecosystem, we're very excited about it. How cool is it to know that we can write code in C# (or VB.NET, or F#, etc.) and have it run on Windows, Linux, and even macOS?
So why on earth doesn't NServiceBus support .NET Core yet? Well, good question.
The times they are a changin' and so our plans are changing too. You can now read about our plans to support .NET Core 2.0 in NServiceBus 7.
Yeah, why not?
NServiceBus is a framework that provides reliable messaging. Stability and reliability are core features that we can't compromise on. These goals simply aren't served by trying to chase the bleeding edge.
And, even though .NET Core has reached its 1.0 release, it's still bleeding edge. Many familiar APIs are not yet supported, and not enough time has passed for there to be a proven track record of mission-critical .NET Core systems running on non-Windows OSs. At least, not yet. The time will come, but it's currently too early to dive into .NET Core headfirst.
Instead, we've been working very hard on delivering NServiceBus 6.0, which delivers on our promise to make the framework fully async. Unlike .NET Core, async/await is no longer bleeding edge. It's mainstream and broadly used, so that's what we've been focusing on.
However, that doesn't mean we've turned a blind eye to .NET Core. On the contrary, we've been tracking it very closely and, where possible, we've made decisions that will make future support for .NET Core easier to achieve.
Steps in the right direction
Based on our knowledge of what was coming in .NET Core, there are a few things we've already been able to do to work toward full .NET Core support.
For example, in .NET Core, there is no
ConfigurationSection class. Prior to Version 6, NServiceBus relied upon multiple configuration sections to control a variety of things like message routing rules, number of message processing threads, and where to send poison messages.
In Version 6, we've already started moving away from XML-based configuration sections, using a code-first configuration style instead. As a result, configuration settings are more discoverable than in XML configuration files, and they can provide IntelliSense support to help developers find and understand the available settings. Going code-first also makes it easy for developers to pull this configuration data from wherever they please, whether that be a config file, a database, or a web service.
One side effect of starting to use code-first configuration is that .NET Core will be that much easier to support. Although we will be continuing our support of ConfigurationSection-based settings in NServiceBus 6.0, we've set the stage for a future where configuration can be controlled optimally for each platform.
There are other features of NServiceBus that have dependencies not available in .NET Core. Luckily, the NServiceBus architecture implements these features as independent components that can be individually enabled or disabled. We're continuing to refine the boundaries of these components so that those dependent upon capabilities not provided by .NET Core can be easily broken off into external packages. This will allow customers who are content to run on Windows and the full .NET Framework to continue using those features just by pulling in additional packages where needed.
An example of this kind of split is the NServiceBus.Callbacks package, which has been split apart from the main NServiceBus library in Version 6. We will use this model to split off additional features, like Windows performance counters, that can't work on non-Windows platforms.
What's left to do
NServiceBus isn't a simple library that can be ported to .NET Core in an afternoon. While not exhaustive, here are some of the larger items that will need to be addressed to fully support .NET Core.
Even if you aren't using the MSMQ transport in your NServiceBus project, you still end up hosting the code for it in your process. The reason for this is that MSMQ support has been embedded into the NServiceBus core library as the default transport from the very beginning. MSMQ is a Windows-only technology, so in order to support .NET Core, the MSMQ transport will need to be split off into a separate NuGet package, similar to the other transports.
The thing is, a built-in, default transport is a good thing to have because it makes it that much easier to get started with NServiceBus. We don't want that to change. So when the MSMQ transport is moved to a separate package, we'll ship a new file-based transport in the NServiceBus core library instead. This will make getting started even simpler than with MSMQ since it won't require any system-level installations.
Of course, you'll still be able to use MSMQ as a transport. You'll just need to use the full .NET Framework and stick with hosting on Windows. And if you do want to run on non-Windows platforms, you'll eventually need to pick a production-ready transport that will support .NET Core, like RabbitMQ, Azure Service Bus, or SQL Server.
These are some of the smaller items we know will also need some attention in order to complete our support for .NET Core:
- System.Transactions — Although used mostly for the MSMQ transport, which can't support .NET Core anyway, TransactionScope and related classes have tendrils into other corners of the framework as well.
- AppDomain — This is used for assembly scanning to wire up handlers, sagas, and other components identified at runtime. The AppDomain class is not available in .NET Core, so this functionality will need to be rewritten to use new APIs.
- Windows Performance Counters — As these obviously will not work on non-Windows systems, this functionality will need to be split into a separate package. We also need to find a way to enable this functionality on other platforms.
- Encryption – NServiceBus uses the RijndaelManaged class for message property encryption. This may become available in .NET Core 1.2 but, in any case, it may make sense to split off the encryption feature into a separate package.
And of course, we're sure this is just the tip of the iceberg. A conversion project like this rarely goes exactly as planned. We're certain there will be surprises, but we're confident that the technical problems are all solvable. There's more to the solution than just technical issues, though.
It's not just about tech
There are a bunch of libraries out on NuGet that have been ported to .NET Core fairly quickly, and you could take those libraries, build an app, and deploy it on Ubuntu without much fuss.
But let's be honest with each other for a minute here. Nobody has sufficient experience running production .NET workloads on any of these Linux distributions. If you have trouble in production, the maintainers of those libraries may or may not be able to help you. There may be slight variations in behavior between the .NET implementations on each platform that may mean the success or the failure of your mission-critical system.
But that's not how we do things around here. Before we release support for .NET Core, we'll make sure to put each supported platform through the same rigorous testing process we apply with every release. It will also mean that we'll be there for you — even at 3 AM on the weekend — to help you through any issue on each of those platforms.
We won't ship with support for .NET Core until we can make good on those promises.
.NET Core is here and shows a lot of promise. It's the future of the .NET platform, so we understand all of the excitement around it. In particular, the .NET Standard 2.0 specification seems to do a lot to unify the full .NET Framework with .NET Core, and the .NET Core version that implements that standard will probably benefit from a lot of stability that typically makes its way into "Version 2.0" products. But despite the excitement, it's just too risky for us to go all in on .NET Core just yet.
Users depend upon the reliability of NServiceBus for the backbone of their mission-critical systems, and you can't provide that reliability on an unproven platform. We won't release .NET Core support until it's fully tested and meets the high standards you've come to expect from us. So, for now, we'll continue the gradual, evolutionary approach we've been taking so far.