Each task runs independently of other tasks, and each task should only access its own data and methods. This independence of tasks works well for most solutions.
In some cases, it makes sense to allow tasks to work together. As some examples:
- A task may be logically a part of another task, and may need to pass data to its parent for the parent to complete.
- One task may need to know about the state of another task before continuing.
- It can be useful to make tasks visible to additional processes that can then interact with the tasks.
These scenarios are achieved through a publish and subscribe mechanism in which a task publishes a message and subscribers, which may be other tasks or groups, then decide how to deal with the message.
Defining a subscription
There are two parts to a subscription: defining what messages to publish to which processes, and then responding to the message within the group or task that receives the message.
The response to the message is described in a later section.
There are three different types of definition of what should be published: group subscriptions, owner connections and task links.
Group subscriptions are defined on the Links field of the group, using links with a reference with a prefix of "publish:". The part of the reference after the publish prefix describes what messages to publish, and the link group specifies to which group the messages should be published (i.e. what groups are subscribing to the message).
The part of the reference after the publish: prefix has the form processType/topic, which defines the combination of processType and topic that should be sent to the group. Either of these can be blank or set to "*" to indicate all process types or all topics. If there is no slash, the reference is assumed to be a process type.
By default, group rules only apply to messages published by the group itself. If you want to apply the rule to messages published by tasks in the group, add a suffix of "+". For example, you would use a reference of "publish:custom_task/complete+" to create a rule to create a subscription to all "complete" messages for tasks with a process type of "custom_task".
Links, including publish: links, are automatically applied to sub groups. You can unapply a link to a sub group using the ** Do not copy links from parent group ** option on the child.
Group subscriptions define how messages published by this group, or by tasks within it, are forwarded to other groups. Tasks also publish messages to their own group. When responding to the messages, you can distinguish between these using the publicationMode, which is set to "group" for the task messages always published to a group, and to "rule" for task messages sent to another group using a group subscription. See following sections for more details.
Owner connection subscriptions are defined on the links field of the connection group of the owner connection of the task or group, which typically identifies the source of the task. These work in the same way as other group subscriptions.
Task link subscriptions are created automatically by linking tasks using the Links field on the task. The links field lists other tasks (or groups), and gives each link a link type. Each of these tasks will receive messages every time the task publishes one. Also, tasks that link to this task will receive a message.
Task link fields and group link fields are both use to specify subscriptions, but the usage and syntax is different. Task links identify "interested parties", and the link references used to specify what interest the publishing task has in the linked task. Group links are a way of creating broader subscription rules, indicating to which other groups messages should be pushed.
Publishing
Data is published from a task using the Set step type and setting the publish property, either to the name of a topic or to true to indicate that the event type should be used as the topic. The publish can be used at the same time as setting other data, or as a separate step. The publish takes place asynchronously, and other processes may respond to the message after (or before) the task process execution has completed.
The call to the subscriber always follows the same format. It has an action of @consume, and a data parameter which contains the subscription message in the "message" property.
{
"message": {
"messageIdentifier": "unique-message-identifier",
"reference": "task.node.reference",
"processIdentifier": "task-process-identifier",
"role": "role",
"processType": "process_type_reference",
"publicationMode": "publicationMode",
"topic", "topic",
"status": "status",
"data": {.. data ..}
}
}
messageIdentifier | Unique identifier for this message. |
reference | Node reference of the task. |
processIdentifier | Task's process identifier. |
role | Task's role within the process, defaults to "owner". |
processType | The task's process type reference. |
publicationMode |
The reason the message was published. This can be:
The publication mode may seem the wrong way around to the subscriber. For example, a message from a child will have a publication mode of "parent". Remember that the publication mode is the publisher's view of the reason, not the subscriber's. |
topic |
The topic of the publication. This is taken from one of three sources:
|
status |
The current status of the task.
|
data |
The data associated with the publish.
|
Responding to messages
The @consume action can do anything. In many cases it will make sense to use the Evaluate step type to interpret the incoming message and route it accordingly. The @consume action should check processType, topic and publicationType to decide what to do. As a suggestion, these values can be meaningfully concatenated as processType.publicationMode.topic, e.g. "raise_order.owner.orderSent" would identify the orderSent topic from the raise_order processes which have as their owner the connection associated with this group, or "project_task.-wbs.finished" to indicate that a project task lower in the work breakdown structure has finished.
The message is passed in to the consumer as the message property of the data parameter, i.e. the message is obtained by:
JSON.parse(application.get('request').getString('data')).message;
A process may receive messages that it cannot process. These should be ignored and no error should be raised.
Subscribers can use the task reference to call back to the task, or pass the process identifier and role to a process module which can (assuming it is trusted) then call back to the task.
Processes do not have to respond to messages sent to them. The @consume action is invoked with a "?" suffix, which means that the call will not fail if the subscriber does not have an @consume action.
Instead of having a single "@consume" step, most tasks will a call to action "consume.*", which means "run all the actions that start with "consume.". Those actions typically use evaluate to determine whether to respond to the messages.
The snippet below shows a typical @consume action, and then an evaluate that runs actions "something" if the publication mode is "group" and the topic is "something".
[
{
"name": "@consume",
"script": "call",
"config": {
"callAction": "consume.*",
"call": "%{param}",
"callParams": true
},
"next": false
},
{
"name": "consume.something",
"script": "evaluate",
"config": {
"data": "%{param:data.message}",
"script": [
"data.publicationMode == 'group' && data.topic == 'something' ? ",
"'run' : 'doNotRun'"
],
"attributeReference": "state"
},
"next": {
"run": "something",
"*": false
}
}
]
Triggers
The publication method allows another task or group to respond to an event. In some cases, it is useful to be able to directly trigger a response on a task or group. For example, it is a common requirement for a group of tasks to be sent or to be cancelled together. This is typically managed by the group, in which the group must then trigger each of the tasks to perform an action. Because the individual tasks may have different process types and may need different processing, only the tasks know exactly what is required to respond.
Rather than use an almost identical mechanism, triggers are achieved using the same conventions as publish. The only difference is using a publication mode of "trigger".
The trigger is supported by the Worker Script trigger() method. The example below shows how a group (the context) could trigger a cancel on a task.
var worker = new Worker(context);
worker.trigger(task,'cancel');