Overview
Author:
Fluent Commerce
Changed on:
30 June 2024
Overview
This section provides Rules SDK reference and guidelines for Writing Rules.
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`
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`
`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`
Using 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]WebhookAction
TheÂ
`WebhookAction`
Read more on Webhooks, and how to build the Webhook receiver in the Integration section.
Using the `WebhookAction`
`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`
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`
`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`
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`
`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`
- 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  ActionFactory to produce the Action
`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.
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  statement to immediately exist the Rule but continue processing the Ruleset.
`return`
- 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`
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`
`context.getEntity()`
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`
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]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`
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]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`
`RuleExecutionException`
- 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  is set toÂ
`eventType`
`EXCEPTION`
- The  message and theÂ
`RuleExecutionEvent`
 throwable (if it exists), will be available as part of the Exception Event for use within your rules`cause`
- The name of this new Ruleset Exception Event is the same as the Ruleset that threw the exception and theÂ
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]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`
Â
`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`
It provides the following methods:
- : load a rule
`rule(Class.class)`
- : adds a ruleset to the workflow
`ruleset(RuleSet ruleSet)`
- : adds an entity to the workflow
`entity(Entity entity)`
- : validates the workflow with the given event
`validateWorkflow(Event event)`
- : executes the workflow with the given event
`execute(Event event)`
The `TestContext`
`TestContext`
TheÂ
`TestContext`
- : Provides the action interfaces of the fluent retail API client.
`action()`
- : Provides the API interfaces of the fluent retail API client.
`api()`
- : Provides access to the number of events or actions that have been executed in the context.
`count*()`
- : Provides access to the entities, rules, events, and properties.
`get*()`
The `scanAndValidateAllRules()`
 Method
`scanAndValidateAllRules()`
TheÂ
`scanAndValidateAllRules()`
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.
Prerequisites
Steps
Debugging 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.