Fluent Commerce Logo
Docs
Sign In

Rules SDK - Writing Rules - Overview

Topic

Author:

Fluent Commerce

Changed on:

30 June 2024

Overview

This section provides Rules SDK reference and guidelines for Writing Rules.

Additionally, make sure you read the Rule Coding Best Practices.

Rule Actions

Author:

Fluent Commerce

Changed on:

30 June 2024

Overview

Rule Actions are the single output of a Rule.

Rules do not perform actions directly. Rather, they inform the Workflow Engine what it should do as a result of the rule execution. The Workflow Engine interprets the specific Action produced by the Rule and handles it appropriately.

Most Actions are queued internally within the Workflow Engine and executed at the end of the current process.

Key points

  • There are 4 types of Actions available to produce from a Rule:
    • `SendEventAction`
    • `MutateAction`
    • `WebhookAction`
    • `LogAction`

SendEventAction

The 

`SendEventAction`
 provides the mechanism for sending new events.

A common use case for this is to control flow, and trigger a new Ruleset for execution after completing the current one. These types of events are typically queued and executed within the same execution thread, as long as they are for the same context.

Another common scenario for this action is to trigger a Ruleset in a different Workflow. These events cannot be executed on the same execution thread, and should be sent out of Rubix, to be processed on a separate execution context.

Using the 
`SendEventAction`

There are various purposes for sending an Event:

  • Flow control - move the current workflow execution onto the next ruleset.
  • Notify another workflow - trigger a ruleset within a different related workflow.
  • Notify another retailer - trigger a ruleset within a different retailer's workflow.
  • Future-dated - schedule an event execution for a later date.
Flow Control

In order to move your process to the next Ruleset, you will need a rule to send an event for the same context as the current event, and name the Ruleset to be triggered.

The best way to do this is to build the Event off the current execution event:

1public class MyCustomRule {
2
3    //...
4
5    public void run(C context) {
6
7        //...
8
9        Event currentEvent = context.getEvent();
10        Event flowControlEvent = currentEvent.toBuilder().name("MyNextRuleset").build();
11        context.action().sendEvent(flowControlEvent);
12    }
13}        

Language: java

Name: Example

Description:

[Warning: empty required content area]
Notify Another Workflow

When required to send an event to a different workflow, it is recommended to build the new Event from scratch, but including the relevant details from the current Event.

In the following example, let's assume that the current Event if for an Order, and it needs to notify the Inventory Catalogue:

1public class MyCustomRule {
2
3    //...
4
5    public void run(C context) {
6
7        //...
8
9        Event newWorkflowEvent = Event.builder()
10                .rootEntityRef("IC_123")
11                .rootEntityType("INVENTORY_CATALOGUE")
12                .entityRef("IP_321")
13                .entityType("INVENTORY_POSITION")
14                .entitySubtype("DEFAULT")
15                .scheduledOn(new Date())
16                .retailerId(context.getEvent().getRetailerId())
17                .source(context.getEvent().getId().toString())
18                .name("RulesetName")
19                .attributes(myEventAttributes)
20                .build();
21
22        context.action().sendEvent(newWorkflowEvent);
23    }
24}

Language: java

Name: Example

Description:

[Warning: empty required content area]
Notify Another Retailer

When sending an event to another retailer, you need to ensure you set the new Event Retailer Id to a value different to the current Event, as well as any other differences in context:

1public class MyCustomRule {
2
3    //...
4
5    public void run(C context) {
6
7        //...
8
9        Event currentEvent = context.getEvent();
10
11        Event newRetailerEvent = currentEvent.builder()
12                .retailerId(context.getEvent().getRetailerId())
13                .source(context.getEvent().getId().toString())
14                .name("RulesetName")
15                .attributes(myEventAttributes)
16                .build();
17
18        context.action().sendEvent(newWorkflowEvent);
19    }
20}

Language: java

Name: Example

Description:

[Warning: empty required content area]
Schedule an Event for Later

Should you require some behaviour to trigger at a later time, you can schedule an event like this:

1public class MyCustomRule {
2
3    //...
4
5    public void run(C context) {
6
7        //...
8
9        Event currentEvent = context.getEvent();
10
11        Date anHourFromNow = DateUtils.addHours(new Date(), 1);
12
13        Event newRetailerEvent = currentEvent.builder()
14                .name("RulesetName")
15                .scheduledOn(anHourFromNow)
16                .source(context.getEvent().getId().toString())
17                .build();
18
19        context.action().sendEvent(newWorkflowEvent);
20    }
21}

Language: java

Name: Example

Description:

[Warning: empty required content area]

MutateAction

The 

`MutateAction`
 provides the mechanism for calling GraphQL Mutations as per the Fluent GraphQL Schema. Mutations provide a way to create or update data within the platform.

Using the 
`MutateAction`

When you want to create or update an entity within the Fluent Platform, you will need to use a 

`MutateAction`
 from a Rule to execute against the GraphQL API.

First, you will need to construct a Mutation object. You should add your mutation query GraphQL file to the 

`graphql`
 folder in your plugin project, and perform a Maven build to generate the relevant mutation object.

Once you have the Mutation object generated for use within your Rule, you simply need to build it with the relevant data, and pass it as a parameter to the Mutation Action:

1public class MyCustomRule {
2
3    //...
4
5    public void run(C context) {
6
7        //...
8
9        UpdateOrderInput updateOrderInput = UpdateOrderInput.builder()
10                .id(context.getEntity().getId())
11                .attributes(attributeInputList)
12                .build();
13
14        UpdateOrderAttributesMutation updateOrderAttributesMutation = UpdateOrderAttributesMutation.builder()
15                .input(updateOrderInput)
16                .build();
17
18        context.action().mutation(updateOrderAttributesMutation);
19    }
20}

Language: java

Name: Example

Description:

[Warning: empty required content area]

WebhookAction

The 

`WebhookAction`
 provides an integration ability, whereby data can be sent to an external endpoint.

Read more on Webhooks, and how to build the Webhook receiver in the Integration section.

Using the 
`WebhookAction`

The example below demonstrates sending the current event to an endpoint:

1public class MyCustomRule {
2
3    //...
4
5    public void run(C context) {
6
7        //...
8
9        context.action().postWebhook(url, context.getEvent());
10    }
11}

Language: java

Name: Example

Description:

[Warning: empty required content area]

LogAction

The 

`LogAction`
 provides the ability to add a custom Orchestration Audit Event.

The additional orchestration audit event will be associated to the same context as the parent event, making it a useful approach to adding additional information about the current execution.

Using the 
`LogAction`

To log additional information to the Audit log for the current Event context, simply use the Log Action as follows:

1public class MyCustomRule {
2
3    //...
4
5    public void run(C context) {
6
7        //...
8
9        context.action().log(message, detailedMessage, attributes);
10    }
11}

Language: java

Name: Example

Description:

[Warning: empty required content area]

The run Method

Author:

Fluent Commerce

Changed on:

30 June 2024

Overview

The 

`run`
 method is called by the Workflow Engine when executing each Rule in a Ruleset triggered by an Orchestration Event.

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

The 

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

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

To implement the Rule, 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 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.

A good practice for writing Rules where the resulting action 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 Rule, 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 Workflow.

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

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 Rule are both present, and valid. For example, if you have declared a number of Parameters or Event Attributes for use within the Rule logic, you should validate these first.

The Rubix Plugin SDK provides a useful Util class to facilitate validation: 

`RuleUtils`

For example, this snippet validates that the Order 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}

Language: java

Name: Example

Description:

[Warning: empty required content area]

If any required validation fails, your Rule 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 Rule'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 rule, but simply continue executing the following rules in the Ruleset.

Only you can determine the appropriate behaviour for your rule. 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 Rule logic.

The 

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

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

  • 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 Entity itself, you will need to perform a query using the GraphQL API Client available on the 

`Context`
.

For example, if you are writing a rule that needs to operate on a field or attribute of the Event Entity, you can retrieve this via a GraphQL 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}

Language: java

Name: Example

Description:

[Warning: empty required content area]

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

Let's say that for example, you only wished to continue the action of this rule if the 

`totalPrice`
 of the Order 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}

Language: java

Name: Example

Description:

[Warning: empty required content area]

Build the Action

Typically, a Rule will produce an output action. 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 action 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}

Language: java

Name: Example

Description:

[Warning: empty required content area]

To learn more, see Working with GraphQL.

Produce the Action

The final stage of the 

`run`
 method is to produce the output action.

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}

Language: java

Name: Example

Description:

[Warning: empty required content area]

Rules SDK Testing Rules

Author:

Fluent Commerce

Changed on:

30 June 2024

Overview

This page describes the best practice testing approaches for rules and plugins.

Key points

  • As per standard industry practices, rules should be tested as part of the development cycle, on the developer's local machine.
  • Testing on Sandbox after local testing has been completed.

Testing Locally

As per standard industry practices, rules should be tested as part of the development cycle, on the developer's local machine.

Rules should be atomic in size and complexity, they are a great candidate for adopting a TDD approach, whereby you write the test first before you even have one line of rule code. You may have heard the phrase "Red, Green, Refactor", and this is a great approach for writing quality rules, that have high test coverage and quality.

There are 2 ways to test locally:

  • Mocked Unit Testing
  • Testing Rule Execution within the Test Executor (this is like a mini Rubix simulator that runs on your local machine)

You should always include both types of testing for your rules.

General Best Practices to Writing Tests

  • Tests must be isolated and repeatable
  • Tests must cover positive and negative paths
  • Tests must be proven to fail when they should (Red, Green, Refactor)
  • Tests should have a single assertion
  • Tests should not assume anything
  • Tests should where possible only use mocked dependencies, including data objects, utils, services, etc.

Unit Testing

The first type of tests that should be written is Mocked Unit Tests. These tests are ideal for TDD.

Fully Mocked Unit Tests for 
`MyCustomRule`
 

Below is an example Test Suite for a Rule.

1public class MyCustomRuleTest {
2
3    MyCustomRule rule = new MyCustomRule();
4
5    @Mock
6    Context context;
7
8    @Mock
9    Entity entity;
10
11    @Mock
12    ReadOnlyFluentApiClient apiClient;
13
14    @Mock
15    GetOrderByIdQuery.Data data;
16
17    @Mock
18    GetOrderByIdQuery.OrderById orderById;
19
20    @Mock
21    ActionFactory actionFactory;
22
23    @Before
24    public void setup() {
25
26        MockitoAnnotations.initMocks(this);
27
28        when(context.getProp(anyString())).thenReturn("1000");
29        when(context.getProp(RuleConstants.PARAM_NAME_HIGH_VALUE_THRESHOLD, Integer.class)).thenReturn(1000);
30        when(context.getEntity()).thenReturn(entity);
31        when(entity.getId()).thenReturn("123");
32        when(context.api()).thenReturn(apiClient);
33        when(apiClient.query(any())).thenReturn(data);
34        when(data.orderById()).thenReturn(orderById);
35    }
36
37    @Test(expected = MissingRequiredParamException.class)
38    public void MyCustomRule_withMissingThresholdParam_ThrowsMissingRequiredParamException() {
39
40        //arrange:
41        when(context.getProp(RuleConstants.PARAM_NAME_HIGH_VALUE_THRESHOLD)).thenReturn(null);
42
43        //act:
44        rule.run(context);
45
46        //assert:
47
48    }
49
50    @Test(expected = MissingRequiredParamException.class)
51    public void MyCustomRule_withEmptyThresholdParam_ThrowsMissingRequiredParamException() {
52
53        //arrange:
54        when(context.getProp(RuleConstants.PARAM_NAME_HIGH_VALUE_THRESHOLD)).thenReturn("");
55
56        //act:
57        rule.run(context);
58
59        //assert:
60
61    }    
62
63    @Test
64    public void MyCustomRule_withValueGreaterThanThreshold_AddsAttributeHIGH_VALUE() {
65
66        //arrange:
67        when(orderById.totalPrice()).thenReturn(1001.00);
68        when(context.action()).thenReturn(actionFactory);
69
70        //act:
71        rule.run(context);
72
73        //assert:
74        verify(actionFactory, times(1)).mutation(any());
75
76    }
77
78    @Test
79    public void MyCustomRule_withValueLessThanThreshold_DoesNothing() {
80
81        //arrange:
82        when(orderById.totalPrice()).thenReturn(999.00);
83
84        //act:
85        rule.run(context);
86
87        //assert:
88        verify(actionFactory, never()).mutation(any());
89
90    }
91
92    @Test
93    public void MyCustomRule_withValueEqualThanThreshold_DoesNothing() {
94
95        //arrange:
96        when(orderById.totalPrice()).thenReturn(1000.00);
97
98        //act:
99        rule.run(context);
100
101        //assert:
102        verify(actionFactory, never()).mutation(any());
103
104    }
105}

Language: java

Name: Example Test Suite for a Rule

Description:

[Warning: empty required content area]

The TestExecutor

The 

`TestExecutor`
 allows developers to set up a mock workflow for unit testing of specific rules and simulates the Rubix Orchestration Engine.

It provides the following methods:

  • `rule(Class.class)`
    : load a rule
  • `ruleset(RuleSet ruleSet)`
    : adds a ruleset to the workflow
  • `entity(Entity entity)`
    : adds an entity to the workflow
  • `validateWorkflow(Event event)`
    : validates the workflow with the given event
  • `execute(Event event)`
    : executes the workflow with the given event
The 
`TestContext`

The 

`TestContext`
 provides functionality to retrieve results post-workflow execution.

  • `action()`
    : Provides the action interfaces of the fluent retail API client.
  • `api()`
    : Provides the API interfaces of the fluent retail API client.
  • `count*()`
    : Provides access to the number of events or actions that have been executed in the context.
  • `get*()`
    : Provides access to the entities, rules, events, and properties.
The 
`scanAndValidateAllRules()`
 Method

The 

`scanAndValidateAllRules()`
 unit test provided with the SDK, and should be present in each plugin. This validates the accuracy of the rule annotations and parameters.

Testing on Sandbox

Once you've got good coverage of your Rules in Unit Tests and using the TestExecutor locally, you can deploy your plugin to your Sandbox account.

Once uploaded, you will want to test your rule in a workflow. In many cases, it may be best to test your rule in an isolated ruleset first, where you can trigger it directly with a specific event, and assess the results via the Audit Events. This should include positive and negative test scenarios. For example, if you rule is expected to fail under certain conditions, make sure to test those conditions, and that the resulting audit log reflects the expected information to help users and support staff understand what happened and why.

Integration Testing

Once you've tested your rule in isolation, you should include in within the intended Ruleset as part of the orchestration lifecycle logic, and perform end to end tests for various scenarios, each time using the Audit Events to validate the expected outcomes and behaviour.

Rules SDK - Debugging Rules

Author:

Fluent Commerce

Changed on:

30 June 2024

Key Points

  • the best approaches to debugging your custom rules and plugins
  • Debugging locally in your IDE can be done as per standard Java practice while running rules in Unit Tests or with the TestExecutor.
  • Debugging on Sandbox - The primary mechanism for debugging on a deployed environment is by activity tracking or Orchestration Audit Events. You can easily query these for any context via the Event API, or the Admin Console.

Steps

Step arrow right iconDebugging Rules

Overview

This page discusses the best approaches to debugging your custom rules and plugins.

Debugging Locally

Debugging locally in your IDE can be done as per standard Java practice while running rules in Unit Tests or with the TestExecutor.

Modern IDEs such as IntelliJ provide great tooling for debugging running code.

Debugging on Sandbox

The primary mechanism for debugging on a deployed environment is by activity tracking or Orchestration Audit Events. You can easily query these for any context via the Event API, or via the Admin Console.

One of the most important aspects of getting complete and helpful information out of the Audit Events is your Exception Strategy. If you are swallowing exceptions or throwing new exceptions excluding the root cause exception, then you may well be missing key information that will help you identify the root cause of the issue.

Another aspect is to always check your data. Check the incoming event data. Check the Entity data. Are there any missing or incorrectly formatted fields? Are your rules robust enough to handle missing or invalid data values on all event attributes, parameters, and entities?

Yet another aspect is based on how your rules are designed. Are they large and complex, or small and granular? Small and Granular rules will always be easier to debug, as you'll be isolating the issue to a much smaller level of complexity from the get-go, and the Audit Events will be more helpful in revealing where the issue resides. Read through Designing Rules for a better understanding of this concept.

If you are unable to determine the issue from the Audit Events, then you may need to break the issue down further. Try to reduce the issue down to a specific Ruleset, then a specific Rule, and then hopefully a specific line of code. As mentioned above, if your rules are large and complex, identifying the problem rule is still potentially a long way off identifying the line of code, so keep this in mind from the get-go.


Exception Management in writing Rules

Author:

Fluent Commerce

Changed on:

30 June 2024

Overview

The Workflow Framework Engine ensures that all Exceptions thrown out of Rules are recorded within the Orchestration Audit Events, accessible via the Event API. This is important for providing detailed information about what went wrong.

Key points

  • To ensure that the Orchestration Audit Events contain as much useful information as possible, it is important to consider the Exception Strategy used by your Rules.
  • Don't swallow exceptions inside Rules, and always allow a caught exception to be re-thrown, or added as a cause to a new exception to ensure the cause is included in the Audit Events.
  • The Workflow Framework provides a special exception type called RuleExecutionException which provides special handling of exceptions differently from all others.

To ensure that the Orchestration Audit Events contain as much useful information as possible, it is important to consider the Exception Strategy used by your Rules.

Exceptions are handled by the Workflow Engine in two ways:

  • Special handling for exceptions of type 
    `RuleExecutionException`
  • Default handling for all other exceptions

We typically don't recommend using try-catch blocks within Rules, and if any exceptions are thrown from the code to allow them to bubble through.  However, there may be some specific scenarios where you would like to throw an exception from within your rules. In this case, make sure to always include the cause exception in the throw:

1public class ExampleRule implements Rule {
2
3    public <C extends Context> void run(C context) {
4
5        try {
6            // some rule code...
7        }
8        catch (Exception e) {
9            throw new CustomRuleException("Useful Message", e); // custom exception including the cause exception
10        }
11    } 
12}

Language: java

Name: Example

Description:

[Warning: empty required content area]

The RuleExecutionException

The Workflow Framework provides a special exception type called 

`RuleExecutionException`
 which provides special handling of exceptions differently from all others. Any 
`RuleExecutionException`
, or subclass thereof, thrown from or bubbled up through a rule back into the Workflow Engine Executor, will be handled as follows:

  • Rubix will stop the execution of the current Ruleset, but continue processing previously queued inline events
  • Rubix will not process any queued actions
  • Rubix will log the exception in an orchestration audit Event, without a stack trace
  • Rubix will mark the Event as success
  • Rubix will return a success response to the UI if the execution was triggered by a User Action
  • Rubix will create and attempt to execute a Ruleset Exception Event
    • The name of this new Ruleset Exception Event is the same as the Ruleset that threw the exception and the 
      `eventType`
       is set to 
      `EXCEPTION`
    • The 
      `RuleExecutionEvent`
       message and the 
      `cause`
       throwable (if it exists), will be available as part of the Exception Event for use within your rules
1"rulesets": [
2  {
3    "name": "CancelOrder",
4    "type": "ORDER",
5    "subtype": "CC",
6    "eventType": "NORMAL",
7    "rules": [ ... ],
8    "triggers": [ ... ],
9    "userActions": []
10  },
11  {
12    "name": "CancelOrder",
13    "type": "ORDER",
14    "subtype": "CC",
15    "eventType": "EXCEPTION",
16    "rules": [ ... ],
17    "triggers": [ ... ],
18    "userActions": []
19  }
20]

Language: json

Name: Example Ruleset and EXCEPTION Ruleset:

Description:

[Warning: empty required content area]
1{
2  "id": "8dcedfd0-a767-4066-8622-574f07cf092a",
3  "name": "ACME.custom2023.ThrowRuleExecutionExceptionRule",
4  "type": "ORCHESTRATION_AUDIT",
5  "accountId": "ACME",
6  "retailerId": "1",
7  "category": "rule",
8  "context": {
9    "sourceEvents": [
10      "e5de6a3b-0653-4df4-bd3d-ca3a83b1fcea"
11    ],
12    "entityType": "ORDER",
13    "entityId": "794",
14    "entityRef": "CC_4137",
15    "rootEntityType": "ORDER",
16    "rootEntityId": "794",
17    "rootEntityRef": "CC_4137"
18  },
19  "eventStatus": "FAILED",
20  "attributes": [
21    {
22      "name": "ruleSet",
23      "value": "TestExceptions",
24      "type": "STRING"
25    },
26    {
27      "name": "props",
28      "value": {},
29      "type": "STRING"
30    },
31    {
32      "name": "startTimer",
33      "value": 1689567378049,
34      "type": "STRING"
35    },
36    {
37      "name": "message",
38      "value": "Example of RuleExecutionException thrown from Rule",
39      "type": "STRING"
40    },
41    {
42      "name": "stopTimer",
43      "value": 1689567378059,
44      "type": "STRING"
45    }
46  ],
47  "source": null,
48  "generatedBy": "Rubix User",
49  "generatedOn": "2023-07-17T04:16:18.059+00:00"
50}

Language: json

Name: Example Orchestration Audit Event for RuleExecutionException:

Description:

[Warning: empty required content area]

All Other Exceptions

For all other exceptions thrown or bubbled through from Rules, the Workflow Framework Engine will handle these as follows:

  • Rubix will stop the current execution
  • Rubix will not process any queued actions
  • Rubix will log the exception in an orchestration audit Event, including a stack trace
  • Rubix will mark the Event as failed
  • Rubix will return an error response to the UI if the execution was triggered by a User Action
  • Rubix will not create and attempt to execute a Ruleset Exception Event

Example Orchestration Audit Event for other Exceptions:

1{
2  "id": "39ffb549-6e82-4e89-bc24-b1f2ec839e49",
3  "name": "java.lang.IllegalArgumentException",
4  "type": "ORCHESTRATION_AUDIT",
5  "accountId": "ACME",
6  "retailerId": "1",
7  "category": "exception",
8  "context": {
9    "sourceEvents": [
10      "48ef5c4e-8f29-4de0-9249-90f2c8cb5ae7"
11    ],
12    "entityType": "ORDER",
13    "entityId": "827",
14    "entityRef": "CC_5369",
15    "rootEntityType": "ORDER",
16    "rootEntityId": "827",
17    "rootEntityRef": "CC_5369"
18  },
19  "eventStatus": "FAILED",
20  "attributes": [
21    {
22      "name": "exception",
23      "value": {
24        "message": "Example of an Exception thrown from a Rule",
25        "stackTrace": [
26          {
27            "fileName": "ThrowOtherExceptionRule.java",
28            "className": "com.fluentcommerce.rule.ThrowOtherExceptionRule",
29            "lineNumber": 11,
30            "methodName": "run",
31            "nativeMethod": false,
32            "declaringClass": "com.fluentcommerce.rule.ThrowOtherExceptionRule"
33          },
34          // LOTS MORE STACKTRACE...
35          {
36            "fileName": "Thread.java",
37            "className": "java.lang.Thread",
38            "lineNumber": 750,
39            "methodName": "run",
40            "nativeMethod": false,
41            "declaringClass": "java.lang.Thread"
42          }
43        ],
44        "suppressed": [],
45        "classContext": [
46          "com.fluentcommerce.rule.ThrowOtherExceptionRule",
47          "com.fluentretail.rubix.plugin.registry.impl.BaseRuleProxyFactory$1",
48          "com.sun.proxy.$Proxy72",
49          "com.fluentretail.rubix.executor.EventExecutor",
50          "com.fluentretail.rubix.executor.EventExecutor",
51          "com.fluentretail.rubix.executor.EventExecutor",
52          "com.fluentretail.rubix.executor.EventExecutor",
53          "com.fluentretail.rubix.executor.EventExecutor$$Lambda$136/403856380",
54          "java.util.stream.MatchOps$1MatchSink",
55          "java.util.ArrayList$ArrayListSpliterator",
56          "java.util.stream.ReferencePipeline",
57          "java.util.stream.AbstractPipeline",
58          "java.util.stream.AbstractPipeline",
59          "java.util.stream.AbstractPipeline",
60          "java.util.stream.MatchOps$MatchOp",
61          "java.util.stream.MatchOps$MatchOp",
62          "java.util.stream.AbstractPipeline",
63          "java.util.stream.ReferencePipeline",
64          "com.fluentretail.rubix.executor.EventExecutor",
65          "com.fluentretail.rubix.executor.EventExecutor",
66          "com.fluentretail.rubix.executor.EventExecutor",
67          "com.fluentretail.rubix.executor.EventExecutor",
68          "com.fluentretail.rubix.executor.RubixEventHandler",
69          "com.fluentretail.rubix.executor.RubixEventHandler",
70          "org.apache.felix.ipojo.util.Callback",
71          "org.apache.felix.ipojo.handlers.event.subscriber.EventAdminSubscriberHandler",
72          "org.apache.felix.ipojo.handlers.event.subscriber.EventAdminSubscriberHandler",
73          "org.apache.felix.eventadmin.impl.handler.EventHandlerProxy",
74          "org.apache.felix.eventadmin.impl.tasks.HandlerTask",
75          "org.apache.felix.eventadmin.impl.tasks.SyncDeliverTasks",
76          "org.apache.felix.eventadmin.impl.handler.EventAdminImpl",
77          "org.apache.felix.eventadmin.impl.security.EventAdminSecurityDecorator",
78          "com.fluentretail.rubix.queue.EventActivator$1",
79          "com.fluentretail.rubix.queue.EventActivator$1$$Lambda$108/390688491",
80          "com.fluentretail.rubix.queue.impl.QueueListener",
81          "com.amazon.sqs.javamessaging.SQSSessionCallbackScheduler",
82          "java.util.concurrent.ThreadPoolExecutor",
83          "java.util.concurrent.ThreadPoolExecutor$Worker",
84          "java.lang.Thread"
85        ],
86        "detailMessage": "Example of an Exception thrown from a Rule",
87        "localizedMessage": "Example of an Exception thrown from a Rule",
88        "suppressedExceptions": []
89      },
90      "type": "OBJECT"
91    },
92    {
93      "name": "lastRule",
94      "value": "ACME.custom2023.ThrowOtherExceptionRule",
95      "type": "String"
96    },
97    {
98      "name": "lastRuleSet",
99      "value": "TestExceptions",
100      "type": "String"
101    },
102    {
103      "name": "message",
104      "value": "Example of an Exception thrown from a Rule",
105      "type": "String"
106    }
107  ],
108  "source": null,
109  "generatedBy": "Rubix User",
110  "generatedOn": "2023-07-17T04:21:53.439+00:00"
111}

Language: json

Name: Example Orchestration Audit Event for other Exceptions:

Description:

[Warning: empty required content area]
Fluent Commerce

Fluent Commerce

Copyright © 2024 Fluent Retail Pty Ltd (trading as Fluent Commerce). All rights reserved. No materials on this docs.fluentcommerce.com site may be used in any way and/or for any purpose without prior written authorisation from Fluent Commerce. Current customers and partners shall use these materials strictly in accordance with the terms and conditions of their written agreements with Fluent Commerce or its affiliates.

Fluent Logo