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

Actions are the single output of a .

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

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

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 a new 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 is to a in a different . These events cannot be executed on the same execution thread, and should be sent out of , to be processed on a separate .

Using the `SendEventAction`

There are various purposes for sending an :

  • 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 to move your to the next , you will need a to send an for the same context as the current , and name the to be triggered.

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

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}        
Notify Another Workflow

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

In the following example, let's assume that the current if for an , and it needs to notify the 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}
Notify Another Retailer

When sending an to another retailer, you need to ensure you set the new Retailer Id to a value different to the current , 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}
Schedule an Event for Later

Should you require some behaviour to at a later time, you can schedule an 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}

MutateAction

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

Using the `MutateAction`

When you want to create or update an within the Fluent Platform, you will need to use a `MutateAction` from a to execute against the API.

First, you will need to construct a Mutation object. You should add your mutation query file to the `graphql` folder in your project, and perform a Maven build to generate the relevant mutation object.

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

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}

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

LogAction

The `LogAction` provides the ability to add a custom .

The additional will be associated to the same context as the parent , 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 context, simply use the Log 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}

The run Method

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}

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

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}

The TestExecutor

The `TestExecutor` allows developers to set up a mock for unit testing of specific rules and simulates the 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 . This validates the accuracy of the 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 to your Sandbox .

Once uploaded, you will want to test your in a . In many cases, it may be best to test your in an isolated first, where you can it directly with a specific , and assess the results via the Audit Events. This should include positive and negative test scenarios. For example, if you 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 in isolation, you should include in within the intended as part of the 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 Audit Events. You can easily query these for any context via the API, or via the .

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 data. Check the data. Are there any missing or incorrectly formatted fields? Are your rules robust enough to handle missing or invalid data values on all 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 , then a specific , and then hopefully a specific line of code. As mentioned above, if your rules are large and complex, identifying the problem 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 Framework Engine ensures that all Exceptions thrown out of Rules are recorded within the 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 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 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}

The RuleExecutionException

The 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 back into the 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]
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}

All Other Exceptions

For all other exceptions thrown or bubbled through from Rules, the 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 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}
Fluent Commerce

Fluent Commerce