Fluent Commerce Logo
Docs
Sign In

Write your first custom Rule using the Fluent Rules SDK (Hello World)

How-to Guide

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"

Steps

Step arrow right iconOverview

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

`LogAction`
 creates an 
`ORCHESTRATION_AUDIT`
 Event which can be retrieved using the Event API.


Step arrow right iconPrerequisites


Step arrow right iconStep 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 
    `rule`
     folder of your Plugin Project.

    For example: 
    `src/main/java/com/acme/rule`


  • Name the class 
    `LogHelloWorld.java`
  • Implement the 
    `com.fluentretail.rubix.v2.rule.Rule`
     interface, and implement the 
    `run`
     method.

    Note: Make sure to fully qualify the name of the context parameter: 
    `run(C context)`


  • Annotate the Rule class with the 
    `@RuleInfo`
     annotation.

    You will need to import 
    `com.fluentretail.rubix.rule.meta.RuleInfo`

    • Set the 
      `name`
       field of the 
      `@RuleInfo`
       to "LogHelloWorld"
    • Set the 
      `description`
       field to "Say Hello to the World"

  • Inside the 
    `run`
     method, produce a 
    `LogAction`
     by accessing the 
    `ActionFactory`
     on the incoming 
    `context`
    :

    `context.action().log();`


    • Set the 
      `message`
       parameter to "Hello World"
    • Set the 
      `detailedMessage`
       parameter to "This is a log message that says Hello World"
    • Set the 
      `attributes`
       parameter to an empty ArrayList: 
      `new ArrayList<>()`


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 arrow right iconStep 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 
    `rule`
     folder under your 
    `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 Test 
    `setUp`
     method, annotated with 
    `@Before`
    :
    • Initialise the mocks:

      `mocks = MockitoAnnotations.openMocks(this);`


    • Setup the Context ActionFactory stub:

      `when(context.action()).thenReturn(actionFactory);`


  • Add a Test 
    `tearDown`
     method, annotated with 
    `@After`
    :
    • Close the mocks: 
      `mocks.close();`


  • Create a new Test method called 
    `run_producesHelloWorldLog`
    :

    `@Test public void run_producesHelloWorldLog()`


    • Call the Rule's 
      `run`
       method: 
      `rule.run(context);`
    • Verify that a single 
      `LogAction`
       was produced with a message equal to "Hello World":

      `verify(actionFactory, times(1)).log(eq("Hello World"), anyString(), anyList());`


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 arrow right iconStep 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 
    `rule`
     folder under your 
    `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 Test 
    `setUp`
     method, annotated with 
    `@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();`


    • 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();`


    • 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();`


  • Create a new Test method called 
    `run_producesLogEvent`
    :

    `@Test public void run_producesLogEvent()`


    • Call the Executor's 
      `execute`
       method:

      `TestContext testContext = executor.execute(event);`


    • Assert a single LogAction was produced:

      `assertEquals(1, testContext.countActionsOfType(TestContext.LogAction.class));`


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


Step arrow right iconWhat's next?

You are now ready to Deploy and Run your rule.

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

Fluent Logo