Key concepts

When getting started using MassTransit, it is a good idea to have a handle on messaging concepts and terminology. To ensure that you are on the right path when looking at a class or interface, here are some of the terms used when working with MassTransit.

Message contracts

A message contract defines a contract between a message producer and a message consumer. In MassTransit, message contracts are declared using types, which should be interfaces in .NET, but may also be classes. For example, a simple command contract for submitting an order is shown below.

public interface SubmitOrder
{
    Guid OrderId { get; }
    DateTimeOffset SubmitDate { get; }
    string OrderNumber { get; }
    OrderItem[] Items { get; }
}

public interface OrderItem 
{
    string ItemNumber { get; }
    int Quantity { get; }
}

Serialization

MassTransit is a service bus, and a service bus is designed to move messages. At the lowest level, a message is a byte array (byte[]) which may represent JSON, XML, or encrypted data.

When sending a message, MassTransit uses a message serializer to convert message contract instances (objects) into JSON, XML, or whatever output format is generated by the serializer. When receiving that message, a message deserializer is used to convert the formatted data back into the message contract.

By default, MassTransit uses JSON to serialize messages, while simultaneously suppporting the deserialization of JSON, BSON, and XML messages. Additional deserializers can be added, and the serializer can be changed for each receive endpoint or the entire bus.

To specify that XML should be used when sending messages:

Bus.Factory.CreateUsingInMemory(cfg =>
{
    cfg.UseXmlSerializer();
});

Other serializers can be configured using other configuration methods.

Consumers

Consumers are classes used to consume messages. Conceptually they are similar to Controllers in a Web API, in that consumers implement a generic interface to specify the message types to be consumed.

An example consumer that consumes the sample message contract above is shown below.

public class SubmitOrderConsumer :
    IConsumer<SubmitOrder>
{
    public async Task Consume(ConsumeContext<SubmitOrder> context)
    {
        // do the thing
    }
}

Consumers are stateless by design, and MassTransit makes no effort to correlate messages to a specific consumer instance. For each message, a consumer factory is used to create a consumer instance to which that message is delivered.

Sagas

In MassTransit, a saga is a stateful consumer that allows multiple messages to be correlated to a single consumer instance. A saga, sometimes called a workflow, is typically used to orchestrate other consumers or services in a distributed system while maintaining state between messages. MassTransit persists a saga's state using a saga repository, and automatically creates a new or uses an existing state instance as messages are received.

Consider a saga a long-running transaction that is managed at the application layer instead of being handled inside a database or by a distributed transaction coordinator.

MassTransit supports simple class-based sagas and powerful, advanced state machine sagas using Automatonymous - a declarative state machine library. In a class-based saga, messages are correlated using a Guid CorrelationId property and must implement the CorrelatedBy<Guid> interface. Class-based sagas can also observe messages by specifying a correlation expression, but caution should be used to avoid matching a message to hundreds of saga instances which may cause performance issues.

A simple saga is shown below as an example, which monitors the completion time of orders.

public class OrderToCompletionSaga :
    ISaga,
    InitiatedBy<OrderSubmitted>,
    Orchestrates<OrderCompleted>
{
    public Guid CorrelationId { get; set; }
    public DateTime Submitted { get; set; }
    public DateTime Completed { get; set; }

    public async Task Consume(ConsumeContext<OrderSubmitted> context)
    {
        Submitted = context.Message.OrderDate;
    }

    public async Task Consume(ConsumeContext<OrderCompleted> context)
    {
        Completed = context.Message.CompletionDate;
    }
}

Transports and endpoints

MassTransit is a framework, and being a framework has certain rules. The first of which is known as the Hollywood principle -- "Don't call us, we'll call you." Once the bus is configured and running, message consumer are called by the framework as messages are received. There is no need for the application to poll a message queue or make repeated calls to a framework method in a loop.

Note: A way to understand this is to think of a message consumer as being similar to a controller in a web application. With a web application, the socket and HTTP protocol are under the hood, and the controller is created and an action method is called by the web framework. MassTransit is similar, it handles the message reception, creates the consumers, and calls the Consume method.

To initiate the calls into your application code, MassTransit creates an abstraction on top of the messaging platform (such as RabbitMQ).

Transports

The transport is at the lowest level and is closest to the actual message broker. The transport communicates with the broker, responsible for sending and receiving messages. The send and receive sections of the transport are completely independent, keeping reads and writes separate in line with the Command Query Responsibility Segregation (CQRS) pattern.

Receive endpoints

A receive endpoint receives messages from a transport, deserializes the message body, and routes the message to the consumers. Applications do not interact with receive endpoints, other than to configure and connect consumers. The rest of the work is done entirely by MassTransit.

Send endpoints

A send endpoint is used by an application to send a message to a specific address. They can be obtained from the ConsumeContext or the IBus and support a variety of message types.

Endpoint addressing

MassTransit uses Universal Resource Identifiers (URIs) to identify endpoints. URIs are flexible and easy to include additional information, such as queue or exchange types. An example RabbitMQ endpoint address for my_queue on the local machine would be: rabbitmq://localhost/my_queue

results matching ""

    No results matching ""