Fluent Commerce Logo
Docs
Sign In
Essential knowledge

Author:

Fluent Commerce

Changed on:

30 June 2024

Overview

The `run` method is called by the Engine when executing each in a triggered by an .

Key points

  • Rules are the smallest building block for logic, it is important to remember that this method should be implemented to perform 1 simple task
  • You may not need all of these steps within your Rule. Some rules produce an action without a condition. Some rules do not need to query additional data. Make sure your Rule is as lean as possible, and simple to read.
  • The Rubix Plugin SDK provides a useful Util class to facilitate validation: RuleUtils
  • You can retrieve the data by using Context / Entity
  • Typically, a Rule will produce an output action.

Since Rules are the smallest building block for logic, it is important to remember that this method should be implemented to perform 1 simple task. Rarely should your code ever be more than a few lines of code.

Rules are meant to be composable, and this means they should be self-contained, and not depend on any other .

The `run` method receives a `Context` instance, which provides all the necessary contextual inputs to the , as well as access to the Fluent API Client, and the Engine ActionFactory.

Rules are singletons, meaning the same single instance of the is processing multiple threads. To this point, make sure you do not declare any runtime-specific values in the Class properties, as these will not be threadsafe.

To implement the , the following steps are usually followed within the `run` method:

  • Validation - validate the incoming parameters and event attributes are present
  • Retrieve additional Data - if the data required to evaluate or perform the Rule Action is not already available on the Context Entity or Event, retrieve it as efficiently as possible.
  • Conditional Logic - if the Rule Action is dependent on an evaluation, implement the simple conditional logic
  • Build the resulting Action - build the Action data
  • Produce the Action - call the `context.action()` ActionFactory to produce the Action

You may not need all of these steps within your . Some rules produce an without a condition. Some rules do not need to query additional data. Make sure your is as lean as possible, and simple to read.

A good practice for writing Rules where the resulting is conditional, or where validations fail, is to exit early. This means avoiding difficult-to-read nested if statements. The logic should be as simple as possible. If there are too many conditions or evaluations in the , it is likely too big and complex, and needs to be broken down into smaller building blocks for the composition of the logic in the .

Remember, your should be reusable, and should be possible to position it one or more times in a and .

To learn more about how to write well-crafted Rules, see Rule Coding Best Practices.

Validation

This stage typically involves validating all the required inputs to the are both present, and valid. For example, if you have declared a number of Parameters or Attributes for use within the logic, you should validate these first.

The SDK provides a useful Util class to facilitate validation: `RuleUtils`

For example, this snippet validates that the Id parameter exists:

1// imports & Rule Info annotation...
2@ParamInteger(name = "MyInt", description = "An Integer value for the Rule")
3public class MyCustomRule implements Rule {
4
5    // local fields...
6
7    public void run(C context) {
8
9        // Validation:
10        RuleUtils.validateRuleProps(context, "MyInt");
11
12        // continuing logic...
13
14    }
15}

If any required validation fails, your should exit immediately. To do this, you have 2 options:

  • Use a `return` statement to immediately exist the Rule but continue processing the Ruleset.
  • Throw an Exception, and stop processing the Ruleset.

The choice of option is dependant on the behaviour you are expecting when this 's validation has failed.

For some Rules, this would indicate a critical error, which for others, it might simply indicate not to continue the logic and behaviour of the existing , but simply continue executing the following rules in the .

Only you can determine the appropriate behaviour for your . The SDK `RuleUtils` has a mix of both boolean response and thrown exceptions.

Retrieving Data

Once you've completed the Validation phase, you may wish to retrieve any additional data you may need to execute your logic.

The `Context` does already contain an (`context.getEntity()`), however, this is a subset of the itself.

It only contains the primary information (the common generic fields) of an Orchestrateable :

  • EntityType - e.g: ORDER, FULFILMENT, etc.
  • EntityId
  • EntityRef
  • Type - e.g: HD, CC, etc.
  • Status
  • WorkflowInfo - e.g: the type and version of the application workflow.

Should you require more data from the specific itself, you will need to perform a query using the API Client available on the `Context`.

For example, if you are writing a that needs to operate on a field or of the , you can retrieve this via a query.

1// imports & Rule Info annotation...
2@ParamInteger(name = "MyInt", description = "An Integer value for the Rule")
3public class MyCustomRule implements Rule {
4
5    // local fields...
6
7    public void run(C context) {
8
9        // ...preceding logic
10
11        // Retrieve Data:
12        String orderId = context.getEntity().getId();
13
14        GetOrderByIdQuery query = GetOrderByIdQuery.builder().id(orderId).build();
15        GetOrderByIdQuery.Data data = (GetOrderByIdQuery.Data) context.api().query(query);
16
17        RuleUtils.validateQueryResult(context, data, this.getClass());
18
19        // continuing logic...
20
21    }
22}

To learn more, see Working with GraphQL.

Conditional Logic

The next stage within the `run` method is to perform any conditional logic required prior to building and producing an .

Let's say that for example, you only wished to continue the of this if the `totalPrice` of the is greater than a `threshold` parameter with a value of $100.

1// imports & annotations...
2public class MyCustomRule implements Rule {
3
4    // local fields...
5
6    public void run(C context) {
7
8        // preceding logic...
9
10        // Simple Logic:
11        if (data.orderById().totalPrice() <= threshold) {
12            return;
13        }
14
15        // continuing logic...
16
17    }
18}

Build the Action

Typically, a will produce an output . See Rule Actions here.

Some typical examples of output actions include:

  • Mutation - e.g: To update or save some new or changed data to the backend via a GraphQL API Mutation.
  • SendEvent - e.g: To send an event to trigger another workflow.
  • SendWebhook - e.g: To send an even to an external system.

In each of these cases, the data to be included in the will need to be prepared.

1// imports & annotations...
2public class MyCustomRule implements Rule {
3
4    public void run(C context) {
5
6        // preceding logic...
7
8        // Prepare for Action:
9        AddAttributeToOrderMutation addAttributeToOrderMutation = AddAttributeToOrderMutation.builder()
10                .orderId(orderId)
11                .attributeName(IS_HIGH_VALUE_ORDER_ATTRIBUTE_NAME)
12                .attributeType(Attribute.Type.BOOLEAN.getValueClass().getSimpleName().toUpperCase())
13                .attributeValue(Boolean.TRUE)
14                .build();
15
16        // continuing logic...
17
18    }
19}

To learn more, see Working with GraphQL.

Produce the Action

The final stage of the `run` method is to produce the output .

1// imports & annotations...
2public class MyCustomRule implements Rule {
3
4    // local fields...
5
6    public void run(C context) {
7
8        // preceding logic...
9
10        // Produce Action:
11        context.action().mutation(addAttributeToOrderMutation);
12
13    }
14}
Fluent Commerce

Fluent Commerce