Message Context

Introduction

Messages that are sent through RawRabbit are delivered with a message context. Any class that implements IMessageContext can be used as a message context. This means that it is possible to replace the default context with a domain specific context. The goal is to seperate the message and its metadata/context.

Forwarding Context

Message context can be forwarded to subsequent message handlers. This is useful when a consumer communicates with other services that needs the message context to process the message correctly.

firstResponder.RespondAsync<FirstRequest, FirstResponse>((req, c) =>
{
   firstResponder
     .PublishAsync(new BasicMessage(), c.GlobalRequestId) //forward context.
     .ContinueWith(t => new FirstReponse());
});

Another useful aspect of forwarding message contexts is that the global request id can be traced though the different systems, making it easy to folllow a request from its originator to all systems that handle the message.

Example: User authorization

A user requests data from a UI. The user is authenticated and has a set of claims that allows the user to do access some (but not all) data. The request arrives at the backend of the UI. The endpoint knows what claims the user has, but the data is fetched from multiple underlying services communicate with over RabbitMq. Things like authentication and authorization doesn’t have anything to do with the request itself, but it is something that the services needs to know of for filtering data. The message context for this setup should contain a list of the users claims, so that the service can evaluate if the requested action is authorized.

Default Context

The default message context, MessageContext, has only one member; GlobalRequestId.

Advanced Context

The AdvancedMessageContext contains properties that can be used to requeue message with delay and send negative acknowledgements. Note that there is nothing magical with the AdvancedMessageContext. It is just a custom context.

Instansiate bus with advanced context

The easiest way to create an instance of a RawRabbit client that uses an advanced context is to use the generic CreateDefault<TMessageContext> method on BusClientFactory (from RawRabbit.vNext).

var client = BusClientFactory.CreateDefault<AdvancedMessageContext>();

Custom Context

The Message Context

There are only two requirements for a message context class. It needs to implement IMessageContext and it needs to be serializable/deserializable by the registered IMessageContextProvider<TMessageContext> (by default Newtonsoft.Json).

public class CustomContext : IMessageContext
{
  public string CustomProperty { get; set; }
  public ulong DeliveryTag {get; set;}
  public Guid GlobalRequestId { get; set; }
}

The Context Provider

Message contexts are provided to the messages by the registered IMessageContextProvider. The default implementation, MessageContextProvider<TMessageContext> can be used for most context (typically POCO classes).

The Context Enhancer

A recieved message passes through the registered IContextEnhancer before any message handler is invoked. The method WireUpContextFeatures is called with the current context, consumer and BasicDeliverEventArgs (from RabbitMQ.Client).

public class CustomContextEnhancer : IContextEnhancer
{
  public void WireUpContextFeatures<TMessageContext>(TMessageContext context, IRawConsumer consumer, BasicDeliverEventArgs args)
    where TMessageContext : IMessageContext
  {
    var customContext = context as CustomContext;
    if (customContext == null)
    {
      return;
    }
    customContext.DeliveryTag = args.DeliveryTag;
  }
}

The RawRabbit Client

The easist way to create a client is by using the generic CreateDefault<TMessageContext> method on BusClientFactory.

var client = BusClientFactory.CreateDefault<AdvancedMessageContext>();

The client can also be resolved from the service collection.

var service = new ServiceCollection()
  .AddRawRabbit<CustomContext>()
  .BuildServiceProvider();
var client = service.GetService<IBusClient<CustomContext>>();