Consumer Driven Contracts

Component: NServiceBus
NuGet Package NServiceBus (6.x)
Support for consumer driven contracts relies on serializers being able to support multiple inheritance. At this time, only the XML or Newtonsoft JSON serializers meet this requirement.

Introduction to consumer driven contracts

This sample shows a consumer driven contract(CDC) approach to messaging. The essence of consumer driven contracts is that the ownership of the contract is inverted. Instead of the producer providing the definition, consumers define the contract they expect, and it's up to the producer to fulfill it.

In NServiceBus terminology, "producers" are called "publishers" and "consumers" are called "subscribers". Contracts translate to message contracts and are defined using plain C# types. To honor a consumer contract, the producer would make the relevant message contract inherit from the consumer contract type.

Contracts as interfaces

CDC is the main reason that interface messages are supported in addition to classes. More than one consumer can provide a contract that would be satisfied by the same publisher message. This would not be possible if only classes were used because multiple inheritance isn't supported by C#. The solution is to use interfaces instead, which lets the publisher implement all relevant contract types on the same message type.

Assuming that the producers MyEvent would satisfy both Consumer1Contract and Consumer2Contract, it would look like this:

class MyEvent :
    Consumer1Contract,
    Consumer2Contract
{
    public string Consumer1Property { get; set; }
    public string Consumer2Property { get; set; }
}
One limitation of this approach is that two or more consumers can't require a property with the same name but different types because that wouldn't compile using C#.

Full names instead of fully qualified assembly names

This step is only required for the Newtonsoft JSON serializer.

By default, NServiceBus publishes messages with the fully qualified assembly names in the enclosed message type header. CDC for the Newtonsoft JSON serializer requires duck typing to be able to load the locally defined contracts. This can be achieved by replacing the fully qualified assembly name with the full name only. The following behavior demonstrates this:

class PublishFullTypeNameOnlyBehavior : IBehavior<IOutgoingPhysicalMessageContext, IOutgoingPhysicalMessageContext>
{
    public Task Invoke(IOutgoingPhysicalMessageContext context, Func<IOutgoingPhysicalMessageContext, Task> next)
    {
        if (context.Headers[Headers.MessageIntent] != "Publish")
        {
            return next(context);
        }

        var types = context.Headers[Headers.EnclosedMessageTypes];
        var assemblyFullName = typeof(MyEvent).Assembly.FullName;
        var enclosedTypes = types.Split(new[] { ";" }, StringSplitOptions.RemoveEmptyEntries);
        for (var i = 0; i < enclosedTypes.Length; i++)
        {
            enclosedTypes[i] = enclosedTypes[i].Replace($", {assemblyFullName}", string.Empty);
        }
        context.Headers[Headers.EnclosedMessageTypes] = string.Join(";", enclosedTypes);
        return next(context);
    }
}

Running the sample

Run the sample and notice how each consumer receives its contract when the producer publishes MyEvent.

Sharing contract types between endpoints is a larger topic, and this sample is using linked files for simplicity. See the message contracts documentation for more details.

Last modified