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.
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.