Sometimes upgrading a framework could be a pain. Upgrading NServicebus to 5.X.X from 4.X.X is not any different. There are a lot of changes in the public API and not everything is documented or obvious. Sometimes the error messages could be misleading, too. This was a surprise for me from a mature open source project. So, I decided to document the missing links and save some time for anyone who is facing similar issues.
Documentation
On Particular's site, under the migration tag, you will find only two posts which are fairly long.
The API-diff documents only shows what was removed like below:
It only shows what was removed. So, if you were using one of the removed types or methods, you are lucky to find a mapping in V5 straight away. In my opinion, users would much appreciate the document that shows how a method looked in V4 and how it is in V5 now. The complete API differences can be found here. It looks like a work in progress and there are signs of improvement.
The upgrade guide from V4 to V5 is a little better but does not cover everything that is changed in V5.
The best form of documentation is the code base. For V4.6.7, you can go here and for the latest here. This is the beauty of the open-source software. The good tests are always more helpful to show the usage than the exhaustive documentation. If so many changes get done in a big bang approach like this release, it can be a time consuming affair for the user.
To be fair, very few open source projects have awesome documentation like Knockoutjs
After upgrading the version, depending on your usage, you may see a ton of compile time errors. What is the reason behind this?
The biggest change in V5
The Configure
class has lost most of its useful static methods such as Configure.EndpointName
. The BusConfiguration
is introduced. An instance of BusConfiguration
is being passed in. So, to further illustrate this, if you have your endpoint like below in V4:
public class MyEndpointConfig : IConfigureThisEndpoint,
AsA_Server,
IWantCustomInitialization
{
public void Init()
{
}
}
The Init()
is a part of IWantCustomInitialization
.
In V5,it looks like below
public class MyEndpointConfig : IConfigureThisEndpoint,
AsA_Server
{
public void Customize(BusConfiguration configuration)
{
}
}
IWantCustomInitialization
is removed IConfigureThisEndpoint
is sufficient.
This means you will have to change your endpoints and build them if you are implementing NServicebus Endpoint interfaces.
Configuration Changes
If you had extensions added on to Configure class, they are pretty much useless. You will have to re-implement those on BusConfiguration
instead.
Some of the methods that are available on BusConfiguration
are :
Backwards compatibility and obsolete errors
The way I understand backwards compatibility is, you keep old and new implementations marking the old one with obsolete
attribute. It gives users the opportunity to make gradual changes.
The V5 of NSB has methods like below:(Source- NSB github repo)
[Obsolete("Please use `ReadOnlySettings.GetConfigSection<T>` instead. Will be removed in version 6.0.0.", true)]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public static T GetConfigSection<T>()
{
throw new NotImplementedException();
}
I can imagine the reasons behind not keeping two implementations (probably too much of a hassle) but that error message is not helpful at all. Will be removed in Version 6.0.0? It is removed in this version too,right? Moreover ReadOnlySettings
is an interface and does not have GetConfigSection<T>
method. It is an extension method of ReadOnlySettings
. The extension code file can be found here. The SettingsHolder
class implements ReadOnlySettings
interface, so you can access it from a property Settings
(an instance of SettingsHolder
) like below:
public class MyEndpointConfig : IWantToRunWhenConfigurationIsComplete
{
public void Run(Configure config)
{
var configSection = config.Settings.GetConfigSection<TransportConfig>();
}
}
If you need to access EndpointName then:
public class MyEndpointConfig : IWantToRunWhenConfigurationIsComplete
{
public void Run(Configure config)
{
var endpointName = config.Settings.EndpointName(); // readonly
}
}
However, it is a readonly
property.
Now, why do we need to implement IWantToRunWhenConfigurationIsComplete
to access this type of information? IConfigureThisEndpoint
has Customize
method with instance of BusConfiguration
parameter. I tried to do
configuration.GetSettings().GetConfigSection<TransportConfig>();
for config section and
configuration.GetSettings().EndpointName();
to get the name.
The prior throws the KeyNotFoundException
with a message "The given key (TypesToScan) was not present in the dictionary." The endpoint one also throws the KeyNotFoundException
with a message "The given key (EndpointName) was not present in the dictionary." but you can definitely set it there like
configuration.GetSettings().EndpointName("MyEndpointName")`.
Perhaps, this could be a write-only property.
ConfigurationComplete
event that used to be in V4 has disappeared. I took it to Stackoverflow. The community and open source nature of the project helped. Again, IWantToRunWhenConfigurationIsComplete
comes to the rescue but it was not obvious.
So, the endpoint class may start looking like below in order to make it work:
public class MyEndpointConfig : IConfigureThisEndpoint
, AsA_Server
,IWantToRunWhenConfigurationIsComplete
{
public void Customize(BusConfiguration configuration)
{
configuration.Transactions().Disable();//
}
public void Run(Configure config)
{
var configSection = config.Settings.GetConfigSection<TransportConfig>();
var endpointName = configuration.GetSettings().EndpointName();
}
}
The follow up post for more changes such as persistence is coming.
Update
The part2 of this series is now online.
Comments