Write your first custom Rule using the Fluent Rules SDK (Hello World)
Author:
Lesley Dean
Changed on:
1 July 2024
Key Points
- A step-by-Step guide to creating your first custom Rule to log "Hello World"
Prerequisites
Steps
Overview
As with many "Hello World" code examples, we will write the simplest implementation of a Rule that produces a log saying "Hello World".
The following steps will provide a simple rule that simply produces a
`LogAction`
A
`LogAction`
`ORCHESTRATION_AUDIT`
Prerequisites
- A fully setup and functioning Fluent Account
- Setup your Development Environment
- Create a new Rules Plugin Project
- If you are using a JDK version higher than JDK 8 (e.g: JDK 17), (Optional) Update your Plugin Project for JDK 17+
Step 1: Create a new Rule class
Workflow Rules are written in Java, and implement the Rules SDK's Rule interface. Rules should be annotated with metadata to describe the Rule internally to the Workflow Engine and the Workflow Builder UI.
Rules produce actions. Actions are the outcome of a rule. There are 4 actions supported by the Rules SDK:
- SendEventAction, which produces events to be executed either inline to the current execution, separately in another workflow, or on a scheduled date and time.
- MutateAction, which creates or updates entities within the platform.
- WebhookAction, which sends an Event payload to an external endpoint.
- LogAction, which creates custom orchestration audit event.
Read more about Rule Actions here.
For this example, we will use the simplest action for our rule - LogAction.
- Create a new Java class in the folder of your Plugin Project.
`rule`
For example:`src/main/java/com/acme/rule`
- Name the class
`LogHelloWorld.java`
- Implement the interface, and implement the
`com.fluentretail.rubix.v2.rule.Rule`
method.`run`
Note: Make sure to fully qualify the name of the context parameter:`run(C context)`
- Annotate the Rule class with the annotation.
`@RuleInfo`
You will need to import`com.fluentretail.rubix.rule.meta.RuleInfo`
- Set the field of the
`name`
to "LogHelloWorld"`@RuleInfo`
- Set the field to "Say Hello to the World"
`description`
- Set the
- Inside the method, produce a
`run`
by accessing the`LogAction`
on the incoming`ActionFactory`
:`context`
`context.action().log();`
- Set the parameter to "Hello World"
`message`
- Set the parameter to "This is a log message that says Hello World"
`detailedMessage`
- Set the parameter to an empty ArrayList:
`attributes`
`new ArrayList<>()`
- Set the
1package com.fluentcommerce.rule;
2
3import com.fluentretail.rubix.rule.meta.RuleInfo;
4import com.fluentretail.rubix.v2.context.Context;
5import com.fluentretail.rubix.v2.rule.Rule;
6
7import java.util.ArrayList;
8
9@RuleInfo(name = "LogHelloWorld", description = "Say Hello to the World")
10public class LogHelloWorld implements Rule {
11 @Override
12 public <C extends Context> void run(C context) {
13 context.action().log("Hello World", "This is a log message that says Hello World", new ArrayList<>());
14 }
15}
Language: java
Name: Rule class code LogHelloWorld.java
Description:
LogHelloWorld.java Rule Class
Step 2: Create a Unit Test
Unit Tests facilitate quality assurance for your rules.
In this example, we will use the Mockito mocking framework together with JUnit to test that the rule successfully produces a LogAction with the "Hello World" message.
- Create a new JUnit Test class in the folder under your
`rule`
.`src/test/java`
For example:`src/test/java/com/acme/rule`
- Name the class
`LogHelloWorldTest.java`
- Add the following private fields to the Test class:
- Add a new private final instance of the rule under test:
`private final LogHelloWorld rule = new LogHelloWorld();`
- Add an AutoClosable to manage the mock object lifecycle:
`private AutoCloseable mocks;`
- Add a Mock for the Rule Context:
`private Context context;`
- Add a Mock for the ActionFactory:
`private ActionFactory actionFactory;`
- Add a new private final instance of the rule under test:
- Add a Test method, annotated with
`setUp`
:`@Before`
- Initialise the mocks:
`mocks = MockitoAnnotations.openMocks(this);`
- Setup the Context ActionFactory stub:
`when(context.action()).thenReturn(actionFactory);`
- Initialise the mocks:
- Add a Test method, annotated with
`tearDown`
:`@After`
- Close the mocks:
`mocks.close();`
- Close the mocks:
- Create a new Test method called :
`run_producesHelloWorldLog`
`@Test public void run_producesHelloWorldLog()`
- Call the Rule's method:
`run`
`rule.run(context);`
- Verify that a single was produced with a message equal to "Hello World":
`LogAction`
`verify(actionFactory, times(1)).log(eq("Hello World"), anyString(), anyList());`
- Call the Rule's
1package com.fluentcommerce.rule;
2
3import com.fluentretail.rubix.v2.action.ActionFactory;
4import com.fluentretail.rubix.v2.context.Context;
5import org.junit.After;
6import org.junit.Before;
7import org.junit.Test;
8import org.mockito.Mock;
9import org.mockito.MockitoAnnotations;
10
11import static org.mockito.Mockito.*;
12
13public class LogHelloWorldTest {
14
15 private final LogHelloWorld rule = new LogHelloWorld();
16 private AutoCloseable mocks;
17
18 @Mock
19 private Context context;
20
21 @Mock
22 private ActionFactory actionFactory;
23
24 @Before
25 public void setUp() throws Exception {
26 mocks = MockitoAnnotations.openMocks(this);
27
28 when(context.action()).thenReturn(actionFactory);
29 }
30
31 @After
32 public void tearDown() throws Exception {
33 mocks.close();
34 }
35
36 @Test
37 public void run_producesHelloWorldLog() {
38
39 // arrange:
40
41 // act:
42 rule.run(context);
43
44 // assert:
45 verify(actionFactory, times(1)).log(eq("Hello World"), anyString(), anyList());
46 }
47}
Language: java
Name: Test class code LogHelloWorldTest.java
Description:
LogHelloWorldTest.java Unit Test Class
Step 3: Create an Executor Test
The Rules SDK provides a TestExecutor which simulates running your rule in a workflow engine on your local development environment.
The TestExecutor provides access to the TestContext which can be used to assert certain outcomes of your rule.
- Create another new JUnit Test class in the folder under your
`rule`
.`src/test/java`
For example:`src/test/java/com/acme/rule`
- Name the class
`LogHelloWorldExecutorTest.java`
- Add the following private fields to the Test class:
- Add a new constant field to hold the Order Id:
`private static final String ORDER_ID = "123";`
- Add a new constant field to hold the Order Ref:
`private static final String ORDER_REF = "HD_123";`
- Add a private TestExecutor field:
`private TestExecutor executor;`
- Add a private Event field:
`private Event event;`
- Add a new constant field to hold the Order Id:
- Add a Test method, annotated with
`setUp`
:`@Before`
- Use the RubixEntity builder to create an instance of an Entity to simulate an Order record::
`RubixEntity entity = RubixEntity.builder()`
- Set the entityType:
`.entityType("ORDER")`
- Set the id:
`.id(ORDER_ID)`
- Set the ref:
`.ref(ORDER_REF)`
- Set the status:
`.status("BOOKED")`
- Set the type:
`.type("HD")`
- Set the flexType:
`.flexType("ORDER::HD")`
- Set the flexVersion:
`.flexVersion(1)`
- Build the instance:
`.build();`
- Set the entityType:
- Use the Event builder to create an instance of an Event to trigger the Workflow:
`event = Event.builder()`
- Set the retailerId:
`.retailerId("1")`
- Set the entityId:
`.entityId(entity.getId())`
- Set the entityRef:
`.entityRef(entity.getRef())`
- Set the entityType:
`.entityType(entity.getEntityType())`
- Set the name:
`.name("test-event")`
- Build the instance:
`.build();`
- Set the retailerId:
- Create an instance of a Ruleset, passing in the Event and the Rule name:
`RuleSet ruleSet = ruleSet(event, ruleInstance(LogHelloWorld.class.getSimpleName()));`
- Use the TestExecutor builder to create an instance of the TextExecutor:
`executor = TestExecutor.builder()`
- Set the rule:
`.rule(LogHelloWorld.class)`
- Set the ruleset:
`.ruleset(ruleSet)`
- Set the entity:
`.entity(entity)`
- Build the instance:
`.build();`
- Set the rule:
- Use the RubixEntity builder to create an instance of an Entity to simulate an Order record:
- Create a new Test method called :
`run_producesLogEvent`
`@Test public void run_producesLogEvent()`
- Call the Executor's method:
`execute`
`TestContext testContext = executor.execute(event);`
- Assert a single LogAction was produced:
`assertEquals(1, testContext.countActionsOfType(TestContext.LogAction.class));`
- Call the Executor's
1package com.fluentcommerce.rule;
2
3import com.fluentretail.api.model.RubixEntity;
4import com.fluentretail.rubix.event.Event;
5import com.fluentretail.rubix.v2.test.TestContext;
6import com.fluentretail.rubix.v2.test.TestExecutor;
7import com.fluentretail.rubix.workflow.RuleSet;
8import org.junit.Before;
9import org.junit.Test;
10
11import static com.fluentretail.rubix.test.TestUtils.ruleInstance;
12import static com.fluentretail.rubix.test.TestUtils.ruleSet;
13import static org.junit.Assert.assertEquals;
14
15public class LogHelloWorldExecutorTest {
16
17 private static final String ORDER_ID = "123";
18 private static final String ORDER_REF = "HD_123";
19
20 private TestExecutor executor;
21
22 private Event event;
23
24 @Before
25 public void setUp() {
26
27 // Setup up an instance of an Orchestrate-able Entity
28 RubixEntity entity = RubixEntity.builder()
29 .entityType("ORDER")
30 .id(ORDER_ID)
31 .ref(ORDER_REF)
32 .status("BOOKED")
33 .type("HD")
34 .flexType("ORDER::HD")
35 .flexVersion(1)
36 .build();
37
38 // Create an Event to trigger the Ruleset
39 event = Event.builder()
40 .retailerId("1")
41 .entityId(entity.getId())
42 .entityRef(entity.getRef())
43 .entityType(entity.getEntityType())
44 .name("test-event")
45 .build();
46
47 // Create an instance of a Ruleset which contains an instance of the Rule configured with the Parameters
48 RuleSet ruleSet = ruleSet(event, ruleInstance(LogHelloWorld.class.getSimpleName()));
49
50 // Set up a TestExecutor to simulate Orchestration Engine
51 executor = TestExecutor.builder()
52 .rule(LogHelloWorld.class)
53 .ruleset(ruleSet)
54 .entity(entity)
55 .build();
56
57 }
58
59 @Test
60 public void run_producesLogEvent() {
61
62 TestContext testContext = executor.execute(event);
63
64 assertEquals(1, testContext.countActionsOfType(TestContext.LogAction.class));
65 }
66}
Language: java
Name: Executor Test class code LogHelloWorldExecutorTest.java
Description:
LogHelloWorldExecutorTest Java Class
What's next?
You are now ready to Deploy and Run your rule.