Versioning

Component: NServiceBus
NuGet Package: NServiceBus (8-pre)
This page targets a pre-release version. Pre-releases are subject to change and samples are not guaranteed to be fully functional.

This sample shows how to handle message schema evolution in a backward-compatible manner. The project consists of a publishing endpoint that has evolved from one version of the schema to the next. The newer subscriber has access to the additional information in the newest version of the scheam while the older keeps operating without interruptions.

In this sample there are two message projects: V1.Messages and V2.Messages:

public interface ISomethingHappened : IEvent
{
    int SomeData { get; set; }
}

The Version 2 message schema inherits from the Version 1 schema as shown below, adding another property on top of the properties in the Version 1 schema.

public interface ISomethingMoreHappened : ISomethingHappened
{
    string MoreInfo { get; set; }
}

Each subscriber may use any of the versions, as the system is upgraded gradually.

Subscribers have a message handler for the messages from their respective versions. Yet there is a slight difference in their subscriptions configuration. V1Subscriber has:

routing.RegisterPublisher(
    assembly: typeof(ISomethingHappened).Assembly,
    publisherEndpoint: "Samples.Versioning.V1Publisher");

while V2Subscriber has:

routing.RegisterPublisher(
    assembly: typeof(ISomethingHappened).Assembly,
    publisherEndpoint: "Samples.Versioning.V2.Publisher");
routing.RegisterPublisher(
    assembly: typeof(ISomethingMoreHappened).Assembly,
    publisherEndpoint: "Samples.Versioning.V2.Publisher");

The only difference is that each subscriber declares the version of the schema on which it depends. In addition, the V2Subscriber also subscribes to the new version of the message.

V2Publisher is publishing a message from the version 2 schema only. However, V1Subscriber receives these messages as well:

Publisher output

Press 'Enter' to publish a message, Ctrl + C to exit.
Published event.

V1Subscriber output

Press any key to stop program
Something happened with some data 1 and no more info

V2Subscriber output

Press any key to stop program
Something happened with some data 1 and more information It's a secret.

When receivers require additional data

In some cases, receivers might require additional data in order to process a message. However, it may occur that the endpoint receives a message of the previous contract. In this scenario, consider using a saga to retrieve the additional data and send a new message that matches the V2 contract.

The handler that accepts the V1 version of the contract might look like this:

public class DoSomethingHandler : IHandleMessages<DoSomething>
{
    static ILog log = LogManager.GetLogger<DoSomethingMoreHandler>();

    public Task Handle(DoSomething message, IMessageHandlerContext context)
    {
        log.Info("Received a v1 message and missing data, do what's needed to retrieve that data");

        context.Publish<DoSomethingMore>(v2 =>
        {
            v2.SomeData = message.SomeData;
            v2.SomeMoreData = 5; // set this value with the retrieved data
        }).ConfigureAwait(false);

        return Task.CompletedTask;
    }
}

The handler that accepts the V2 version of the contract contains the implementation that handles the fully populated message:

public class DoSomethingMoreHandler : IHandleMessages<DoSomethingMore>
{
    static ILog log = LogManager.GetLogger<DoSomethingMoreHandler>();

    public Task Handle(DoSomethingMore message, IMessageHandlerContext context)
    {
        log.Info($"Hi, I did something for you based on your v2 data: {message}");
        return Task.CompletedTask;
    }
}

Last modified