Author:
Fluent Commerce
Changed on:
30 June 2024
This section provides Rules SDK reference and guidelines for Writing Rules.
Author:
Fluent Commerce
Changed on:
30 June 2024
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.
`SendEventAction`
`MutateAction`
`WebhookAction`
`LogAction`
TheÂ
`SendEventAction`
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.
`SendEventAction`
There are various purposes for sending an Event:
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]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]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]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]TheÂ
`MutateAction`
`MutateAction`
When you want to create or update an entity within the Fluent Platform, you will need to use aÂ
`MutateAction`
First, you will need to construct a Mutation object. You should add your mutation query GraphQL file to theÂ
`graphql`
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]TheÂ
`WebhookAction`
Read more on Webhooks, and how to build the Webhook receiver in the Integration section.
`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]TheÂ
`LogAction`
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.
`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]Author:
Fluent Commerce
Changed on:
30 June 2024
The `run`
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`
`Context`
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`
`context.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.
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:
`return`
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`
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`
`context.getEntity()`
It only contains the primary information (the common generic fields) of an Orchestrateable Entity:
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.
The next stage within theÂ
`run`
Let's say that for example, you only wished to continue the action of this rule if theÂ
`totalPrice`
`threshold`
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]Typically, a Rule will produce an output action. See Rule Actions here.
Some typical examples of output actions include:
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.
The final stage of theÂ
`run`
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]Author:
Fluent Commerce
Changed on:
30 June 2024
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.
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:
`RuleExecutionException`
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 Workflow Framework provides a special exception type calledÂ
`RuleExecutionException`
`RuleExecutionException`
`eventType`
`EXCEPTION`
`RuleExecutionEvent`
`cause`
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]For all other exceptions thrown or bubbled through from Rules, the Workflow Framework Engine will handle these as follows:
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]Author:
Fluent Commerce
Changed on:
30 June 2024
This page describes the best practice testing approaches for rules and plugins.
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:
You should always include both types of testing for your rules.
The first type of tests that should be written is Mocked Unit Tests. These tests are ideal for TDD.
`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`
It provides the following methods:
`rule(Class.class)`
`ruleset(RuleSet ruleSet)`
`entity(Entity entity)`
`validateWorkflow(Event event)`
`execute(Event event)`
`TestContext`
TheÂ
`TestContext`
`action()`
`api()`
`count*()`
`get*()`
`scanAndValidateAllRules()`
TheÂ
`scanAndValidateAllRules()`
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.
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.
Author:
Fluent Commerce
Changed on:
30 June 2024
This page discusses 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.
Modern IDEs such as IntelliJ provide great tooling for debugging running code.
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.
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.