Skip to content

Fundamentals

At its most basic concept, Business Rules feature is responsible for listening to a specific event and execute a series of activities as a consequence, those activities being part of a well defined distributed transaction.

A Business Rule is constituted of two main concepts:

  • A trigger, which itself is composed from an event and the conditions under which the event should be triggered.
  • An itinerary, containing a sorted list of activities to be executed once the business rule is activated.

Events and activities are stored as entities on their own, without necessarily being related to a specific rule. This allows us to reutilize events and activities as building blocks for different business rules, and combine them in several ways according to our needs.

Events

An event is virtually anything we can keep track of and has the capacity of triggering a business rule, usually a MassTransit message in our ecosystem. Alongside a display name and a description (for displaying purposes) we store the message's publish address and a set of fields that compose the event's payload.

Activities

An activity represents a small piece of logic that takes arguments as an input and can complete, fail or compensate if required. Again, due to our tight integration with MassTransit, we base our concept of activity in MT Courier activities.

Just like with events, we store some display-purpose data for each activity and the execution address, which is required by business rules executor in order to build the specified URI and execute each activity.

By default, an activity must define a set of arguments which will hold the data required for the execution. We also store information about the arguments, specifically the name and type for each one, which will be used at the moment of building a business rule.

Optionally, activities can define output arguments that can be used by the subsequent activities. Hence, a list of outputs is also stored in the activity definition, also for building purposes.

Arguments and outputs

When defining a MassTransit courier activity, we're required to provide an arguments class as well, which can be an interface or (preferred) a record, even though this class is empty, since the activity may not require any specific argument to proceed.

Arguments are mapped automatically by key matching from the Courier's variables bag, which gets populated with key-value pairs provided by previous executed activities and by the trigger's initial arguments, the payload that comes with the message.

On completion, each activity is allowed to add its own variables to the bag. These variables are known as the activity outputs. While MassTransit allows to declare outputs in a most unconstrained fashion, we've defined some conventions for this process.

As an example, lets define an Activity that simply takes a list of user ids as an argument, notifies those users about a specific event and propagates the notification id to the bag, just for tracking purposes.

C#
public class NotifyUsersActivity : IExecuteActivity<NotifyUsersActivity.Arguments>
{
    public async Task<ExecutionResult> Execute(ExecuteContext<NotifyUsersActivity.Arguments> context)
    {
        /// Your notification logic goes here.

        return context.Complete<NotifyUsersActivity.Outputs>(new Outputs()
        {
            NotificationId = notificationId,
        });
    }

    public record Arguments
    {
        public IEnumerable<Guid> UserIds { get; set; } = new List<Guid>();
    }

    public record Outputs
    {
        public Guid NotificationId { get; set; }
    }
}

By convention, we define arguments and outputs as inner classes inside the activity, respectively named as Arguments and Outputs.

Note

Activities with compensation are fully supported as well, in which case the definition of the activity log follows the same convention.

We've decided to make output values type-safe in order to make the process of building the business rule less error prone by defining output keys before hand.

As a summary of conventions and recommendations when writing business rules activities:

  • Always suffix your activity class with Activity.
  • Arguments and Outputs should be inner records of the activity, with the proper accessors configured.
  • Completing an activity with MassTransit CompletedWithVariables method is strongly discouraged. Always use our typed overload when you require to provide outputs.
  • Keep activities as simple and cohesive as possible. These are building blocks that should be reutilized to compose different business rules (embrace single responsibility principle!).
  • Every output that you define in your activity is added to the variables bag and travels with the courier itself, meaning that a bad definition of what should and shouldn't be declared as an output may quickly flood the bag with unnecessary loads of data. Be careful on this matter whenever you need to propagate large amounts of data between activities.