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; }
}
Full names instead of fully qualified assembly names
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
.