Skip to main content

Application Processes

Use standard outcomes (AP-001)

When exiting a process, use one of the following outcomes:

  • Success

  • Fail

  • Cancel

If the above do not make sense for the exit outcome of a process, you can use your own. It should however be the responsibility of the caller process to decide what to do.

For instance, let's assume you have the RichProductSearch process and a sub process LoadSearchMenu. LoadSearchMenu should not exit with "GoToMainSearchScreen" just so that RichProductSearch can "easily" go to the right state. Instead it should clearly return Success and Fail and RichProductSearch should then decide to go to the MainSearchScreen on failure. This is to allow reusability (the LoadSearchMenu could be used in a context where returning GoToMainSearchScreen makes no sense)

Outcome Action Naming Convention (AP-002)

The outcome action should usually named EndProcessXXX where XXX is the actual outcome, for instance EndProcessSuccess.

Add Notes where necessary (AP-003)

When an action is complex or the reason for a link obscure, please add a notes to help the next developer to understand the implementation.

Do not add a note for self explanatory process (this is similar to obvious comments in code that do not add more than what the code already indicate)

Use Null action where necessary (AP-004)

Use Null action when necessary. You can sometimes add the links on the previous action rather than adding a null action just for the branching. But sometimes it makes more sense to add an explicitly null action.

In all cases, make sure to name the action sensibly. "Null Action 1" is not a good name.

Use Switch Statement (AP-005)

There is no switch statement in the Enactor Framework for Application Processers. For a small number of case, you can chain Null action with one conditional link or a single Null action with multiple links (equivalent to an if-else-if pattern). Once there are 3 or more statements, consider using RaiseValueAsOutcomeAction, especially if the condition is based on the value of a variable.

RaiseValueAsOutcomeAction

Single Null Action

Clearer

Probably never a good pattern if more than 1 or 2 conditional links

Please also note that the "Unknown" link (or the default link in the case of using the Null action) is better handled as the exception case and would normally be logged as an exception.

Try to keep the number of conditional links small. It is better to chain few null actions rather than a single one with many links.

One conditional link is the recommended number (so it represents the simple if else statement) although more conditional links might make sense in some scenario.

Keep Application Process Small (AP-007)

There's no hard limit on the number of actions in a process. However, when you reach 20-30 actions, it might be time to refactor some of the work being done in sub process. This not only help readability but also reusability as a small process doing one thing is more likely to be generic enough that it can be reused.

The toolkit will show a warning on the process if the number of actions, states, links reach the default limits.

There are some important processes in the pos that are not good example. For instance the RichProductSearch has over 130 actions... and should be refactored

  • Just Fine

InvokeSearchProducts - Just Fine

  • Getting Too Big

GetProductDetails Process - Getting Too Big

  • Too Big

RichProductSearch Process - Too Big

Keep Application Process Simple (AP-008)

Application processes are an effective mechanism for understanding the functional flow of a given use case, enabling the introduction of extensions, and promoting the reuse of processes and actions where appropriate. However, as with any design approach, it is important to maintain a proper balance to ensure that their intended purpose is preserved.

  • Too Complex
    ComplexProcess - Just Fine As more logic is incorporated into an application process over time, it can become increasingly complex, making it difficult to trace and understand the functional flow.
    The primary purpose of an application process is to present a clear, high-level view of system behavior, typically represented through a state diagram --> State -> Event -> Actions -> State -> Event -->
    When the number of actions in between two states becomes too many, the process can be difficult to follow.

  • Fragmented Logic
    When we re-use existing processes or actions, in some scenarios, the existing actions or processes may not be 100% what we need. So we end-up reusing as much as can and implement the bit that it doesn't provide even though that is one logical operation in the current functional flow that we're trying to implement. We could end-up reusing multiple processes scatterred around that was built for different purposes ultimately ending up building a process that doesn't explain what it does without having to go into each reused process or action.
    Take a look at the following process snippet for an example. It is trying to print card payment related receipts at the end of a tender. While attempting to re-use as many out of the box actions and processes as possible, it has made the process more complex to read. There are only two functional steps happenning here and they are 'Print Merchant Copy' and 'Print Customer Copy'. FragmentedLogic - Just Fine Looking at the overall process at a high-leve, the above section is only a fragment of the complete process as highlighted below. Similar to the extract portion of this, the attempts to re-use existing code has made this process harder to track. FragmentedLogicOverview - Just Fine

  • Misplaced Effort
    In some cases, the effort spent to do something in a process could be larger than the effort it would take to implement that in a java class if we're trying to write complex business logic in a process.
    Consider the following process section where it's looping through the basket items and performing some actions on each item. MisplacedEffort - Just Fine With respect to the functional flow, this is one single functional step. There are no intermediate states the process stops at within each item. Therefore, this does not add any value to the fundamental process usage. This may have been done more efficiently while keeping the process cleaner by using a cusom action.

Identifying in Advance

The first step in addressing any issue is recognizing that it exists. Identifying problems early is essential to minimize the potential repercussions associated with them.

There are few basic steps that we can take to identify these scenarios in advance.

  • Use your Own Judgement
    During implementation, if a developer determines that the required functionality can be more effectively achieved within a Java class than an application process, it is an indication that the logic may be better handled outside the process layer.
  • Follow the Flow
    When implementing any process, see if you can follow the functional flow by looking at the process. If not, there's probably something we can do to make it better.
  • Number of Actions between two States
    Count the number of java action calls between two states or process calls. There is no exact correct number, but if it's more than 3, then we should evaluate if there is a better way to do this.
  • Number of Total Components in a Process
    Count the total number of actions and states within a process. If this exceeds 20, evaluate whether it represents a single functional flow or if we could refactore these to sub processes.
  • The Cyclomatic Complexity Evaluate the number of possible routes within a process. If it exceeds 10, consider whether the design can be simplified or improved. This is not a strict limit, but rather a trigger for review.

Refactor Processes

The first obvious thing to do is see if we could benefit from refactoring the process while maintaining the process readability.

┌──────────────────────────┐
│ Process too complex │
└──────────────┬───────────┘


┌─────────────────────────────┐
│ Can part of this be a │
│ reusable sub-process? │
└──────────────┬──────────────┘

┌───────┴───────┐
│ │
Yes No
│ │
▼ ▼
┌─────────────┐ ┌──────────────────────┐
│ Refactor │ │ Alternative Solutions│
└─────────────┘ └──────────────────────┘

Merge Actions

In cases where there are multiple consecutive actions in a process, the best option could be to implement a single action combining them. In most cases, use of multiple consecutive actions is done on the same set of data to create or transform data. This could be one single unit of operations that qualifies to be an action of it's own.

Take the following section of the process as an example where 4 consecutive actions are called one after the other. ActionChain - Just Fine

  • Action 1 - Checks if carrier bags are enabled in the current location
  • Action 2 - Retrieves the Transaction from the TransactionHandler
  • Action 3 - Custom Action that updates transaction additional data using an input
  • Action 4 - Assigns a variable using data in the basket and input

All of these actions together achieve one single functional operation where it decides whether the carrier bag prompt should be displayed and if so prepares the data required for that prompt.

Instead of using these 4 actions, we can implement all of these in one single action with a more meaningful Action name such as 'ValidateAndPrepareShoppingBagData' which achieves all 4 of these by merging the inputs and outputs of all 4 actions and writing the logic in java.
ActionChainFixed - Just Fine

Arrange Flow Left to Right (AP-009)

There isn't one style of writing a process and there isn't only one correct way of arranging the actions.

Here're some advice to write process to help readability:

  • Overall flow should be left to right. Try to avoid going in a loop (unless you implement a loop)

  • Try to group the outcome (either at the top or to the right of the process)

  • Don't squeeze a new action in a random place just because you don't want to re-arrange the other actions

The following process (TotalPressed) is overall layed out from left to right. There is however a bit too much up and down and even a backtracking towards the end

Below is the GetProductDetails process in its current form (on the left) and after refactoring (to arrange it more left to right). The most visible improvement is that it becomes clearer that the CallEnableAdditionalProductDetailSearchHook action is always called, no matter the type of product (sku, style or otherwise). It wasn't clear on the original version. Some might prefer the more "compact" version but keep in mind to keep the process as readable as possible.

Current

Refactored

Only show the relevant Style (AP-010)

The following component can be shown for each action and state:

  • Detail

    • Rarely used. Most of the time, the name of the action should match the name of the process or the java action being called. So the detail would be the same as the title of the action/state
  • Events

    • Should almost always show for states. Exception: the only event is StateEntered
  • Outcome

    • Should almost always used. Exception: only one outcome. Even when there is only 2 outcomes such as Success and Fail (which are rendered green and back respectively), it is useful to show the outcome
  • Input

    • Only show if there is one or more input
  • Output

    • Only show if there is one or more output
  • Privileges

    • Only show if there is a privilege
  • State Data

    • Only show if the state declares state data

Show only the relevant style but don't hide any relevant style either (for instance if an action requires a privilege, it can be confusing if it is not visible at first glance)

Example 1

For Process outcome, we usually only show the name of the action as the outcome itself usually match

Example 2

The following shows an example of a process that doesn't conform to the recommendations above and how it could be refactored

Before

After

  1. Outcome is not necessary if there's only one

  2. Action name should describe the actual action

  3. Privileges does not need to be shown if empty

  • flow more left to right

  • One null action removed

  • Actions renamed

Allowed and Authorises Privileges (AP-011)

When creating a user privilege, the common pattern is to have two privileges:

  • Allowed - Is the user allowed to execute this action. Usually, the function will not be visible if the user is not allowed to perform it.

  • Authorised - If the user is allowed to perform an action, he/she might not be authorised to do so directly and would need to ask another user to authorise it.

Based on the above, the current user must have at least the Allowed privilege and then, the current user or another user need to authorise it.

Example

<core:message key="enactor.pos.VoidDispatchAllowed.Name">Void Dispatch Allowed</core:message>
<core:message key="enactor.pos.VoidDispatchAllowed.Description">Void Dispatches are allowed</core:message>
<core:message key="enactor.pos.AuthorisesVoidDispatch.Name">Authorises Void Dispatch</core:message>
<core:message key="enactor.pos.AuthorisesVoidDispatch.Description">The user can authorise void dispatches</core:message>

Make sure to also have the privileges defined in the resource message file (both the Name and the Description) so that role application maintenance shows a human readable message instead of the id.

A helper application process already exists to implement this pattern: use the Pos/SignOn/AuthorisePosFunction. The key inputs are

  • enactor.pos.AllowedPrivilege - The string representation of the Allowed privilege, e.g. enactor.pos.VoidDispatchAllowed
  • enactor.pos.AuthorisesPrivilege - the string representation of the Authorised privilege, e.g. enactor.pos.AuthorisesVoidDispatch

Register Privileges in Message Resources (AP-012)

When adding a new privilege in an Application Process, the privilege also needs to be registered in the relevant message resources so that the User Role Maintenance application can display the User Friendly message.

Two messages are required:

  • <Privilege>.Name - This is the short name of the privilege, usually the last part of the privilege id but with spaces between the words

  • <Privilege>.Description - This is the long description which explains the purpose of the privilege. It needs to be a sentence with a verb.

Example

<core:message key="enactor.pos.VoidDispatchAllowed.Name">Void Dispatch Allowed</core:message>
<core:message key="enactor.pos.VoidDispatchAllowed.Description">Void Dispatches are allowed</core:message>

Handle All Outcomes that can be returned by a Process (AP-013)

When an application process A is calling an application process B, if the application process B can return the following outcomes: Success, Fail, then the application process A must handle both outcomes.

Only Handle Outcomes that can be returned by a Process (AP-014)

When an application process A is calling an application process B, if the application process B can only return the following outcomes: Success, Cancel, then the application process A must not handle a non-existent outcome. It should only handle Success and Cancel (and it must handle Success and Cancel).

Application Processes should be Registered in the Packages.xml (AP-015)

When creating a new application process, please ensure it is registered to the Packages.xml of the project.

Application Process Extension Name should relate to the caller process (AP-016)

The name of an extension should not be about how the extension is being used in the specific scenario being implemented but about the generic purpose of the extension.

For instance, if an extension is required at the start of EndTransaction process for the purpose of validating if a voucher in the basket has been redeemed already, the extension should be named ValidateEndTransaction, not ValidateVoucher. The reason is that the extension might be used for something else in the future and therefore the name should be generic.

"Stop Linking" outcome when calling an extension should not be explicitly handled (AP-017)

When extensions were first implemented, the standard outcome that we supported was Success, Fail, and StopLinking. The purpose of StopLinking is to stop the execution chain of extensions (the use case is some Processing Handler where the execution needs to stop after the first successful extension execution). However, it was not possible to return the actual outcome of the extension alongside it.

The correct pattern is to return the actual outcome of the extension (e.g. Success, Fail, NotFound...) with a flag to indicate to break the chain (i.e. Stop Linking).

Use SkipDefault outcome in extension to skip the default Enactor behaviour (AP-018)

If the default Enactor behaviour needs to be overridden/skipped, the convention is to:

  • add a process extension and call it just before where the default Enactor behaviour is implemented

  • Make the extension return the "SkipDefault" outcome (the extension chain will most likely need to be stopped as well, using StopLinking, see above)

  • When calling the process extension, the "SkipDefault" outcome should be linked to after the Enactor default behaviour.

See for instance: Pos/Void/PostTransactionVoidProcessItems

Register Generic Message Resources (AP-019)

When there are Message resource used by so many processes, instead of registering them against each individual process, register the Message Resource in Package.xml Where the Message Resource file exist.

Extension Point should be unique (AP-020)

When adding an extension point to multiple processes, it should be a unique extension in each of the process (alternatively, passing an "Operation" parameter)

Replacing a java action with a call process and an Extension (AP-021)

When a java class needs to be overridden for a customer, the best practice is to replace the action call by a process call. A new Application process therefore needs to be implemented. In that new application process, an extension point should be added so that the default behaviour can be replaced. The extension point success outcome should go to the java class action and the outcome should be mirrored between the extension point and the java class.

For instance, if this was the process before the refactoring:

Then it should be changed to:

For reference, the following was done:

  • Click on the action and change the Type property to Call Process

  • Enter a new process id for the Execute Process ID property.

With the implementation of the new UpdatePromotionSavings process as follow:

  • The outcomes of the process match the outcome of the action

  • The extension point is called right at the start of the process and only goes to the default behaviour if the outcome is Success (which is the case when there is no implementation of the extension, or if the extension decides that it wants the default behaviour)

Enactor Out-Of-the-Box Behaviour should not be done directly as an extension (AP-022)

If an out-of-the-box behaviour needs to be refactored as an extension, so that a customer can override the default behaviour, then then best practice is to extract the out-of-the-box behaviour into a sub process. In that sub process, an extension point can then be added at the start of the process. The convention is to use a "SkipDefault" outcome when the extension implementation returns to indicate that the out-of-the-box behaviour should be skipped. Additional outcomes can also be specified as a return outcome of the extension and those outcomes should be linked to the same outcome of the process.

See above AP-021 for the pattern applied on a single java class action. The same would apply if it is a group of actions, or even several states with call processes.

Calling a Background process using ExecuteAndWait (AP-023)

When calling an endpoint, it is important to do so in a background thread so that the UI is not locked while the service call is in progress. This pattern can be achieved by using the ExecuteAndWait process that wraps the service call (or any application process that would execute slow code). Do not reimplement the same logic in your process.

Reference: CommonUI/ExecuteAndWait in Common UI Base Implementation project

Example: Pos/Devices/UndockDevice in POS project

This process is available in the Palette, under "Common Processes"

Refactoring a Process Hook to a Process Extension (AP-024)

When a process already contains a Process Hook (this is an old method to plug in customisation using overriding processes with Process Set), we shouldn't add an extension point to do the same thing. Instead, the Call Process Hook should be converted to a Call Process Extension.

For instance, the following ValidatePostVoidExternal process hook (taken from Pos/Void/PostTransactionVoidMain application process):

should be converted to an extension point

The Outcomes, Inputs, and Outputs are identical, but it's an extension call instead of a process call.

For backward compatibility, the original Process hook needs to be registered as an extension for the new extension point. For the example above, the following would need to be added to the Packages.xml

<core:packageExtension>
<core:extensionId>Validate Post Void Transaction</core:extensionId>
<core:extensionPoint>ValidatePostVoidTransaction</core:extensionPoint>
<core:applyBeforePackages/>
<core:extensionOverrides/>
<core:extensionType></core:extensionType>
<core:extensionUrl>Pos/Void/External/ValidatePostVoidExternal</core:extensionUrl>
</core:packageExtension>

Use UIGetViewDataAction explicitly to get view data instead of Process input (AP-025)

The input data of an application process can either be explicitly specified from the caller or mapped automatically from the view. This has been the standard practices for common data such as User, Location, Pos Terminal, Transaction Handler. However, it should not be used for other data that one might expect to be retrieved from the view. Instead you should be using UIGetViewDataAction.

The following show the variable myData being automatically retrieved from the view. This pattern should not be used.

Instead, the example below shows how UIGetViewDataAction action is used to retrieve the variable from the view. This pattern is the recommended approach.