spiff-arena/SpiffWorkflow/doc/bpmn/multiinstance.rst

144 lines
5.4 KiB
ReStructuredText

MultiInstance Tasks
===================
BPMN Model
----------
We'll be using the following files from `spiff-example-cli <https://github.com/sartography/spiff-example-cli>`_.
- `multiinstance <https://github.com/sartography/spiff-example-cli/blob/main/bpmn/tutorial/top_level_multi.bpmn>`_ workflow
- `call activity multi <https://github.com/sartography/spiff-example-cli/blob/main/bpmn/tutorial/call_activity_multi.bpmn>`_ workflow
- `product_prices <https://github.com/sartography/spiff-example-cli/blob/main/bpmn/tutorial/product_prices.dmn>`_ DMN table
- `shipping_costs <https://github.com/sartography/spiff-example-cli/blob/main/bpmntutorial//shipping_costs.dmn>`_ DMN table
Loop Task
^^^^^^^^^
Suppose we want our customer to be able to select more than one product.
We'll run our 'Select and Customize Product' Call Activity as a Loop Task.
First we'll update the Call Activity's model to ask the customer if they would like to continue shopping.
.. figure:: figures/multiinstance/call_activity_multi.png
:scale: 30%
:align: center
Selecting more than one product
We've also added a *postScript* to the user task. Spiffworkflow provides extensions that allow scripts to be
run before and after tasks. It is often the case that data needs to be manipulated before and after a task.
We could add regular Script Tasks before and after, but diagrams quickly become cluttered with scripts, and
these extensions are intended to alleviate that.
We use a *postScript* to add the current product to a list of products.
.. code:: python
products.append({
'product_name': product_name,
'product_quantity': product_quantity,
'product_color': product_color,
'product_size': product_size,
'product_style': product_style,
'product_price': product_price,
})
We'll use a *preScript* on the first User Task (Select Product and Quantity) to initialize these variables to
:code:`None` each time we execute the task.
Loop Tasks run either a specified number of times or until a completion condition is met. Since we can't
know in advance how many products the customer will select, we'll add :code:`continue_shopping == 'Y'` as a
completion condition. We'll re-run this Call Activity as long as the customer indicates they want to choose
another product. We'll also set up the list of products that we plan on appending to.
We also added a postscript to this activity to delete the customization values so that we won't have to
look at them for the remainder of the workflow.
.. figure:: figures/multiinstance/loop_task.png
:scale: 30%
:align: center
Call Activity with Loop
We also needed to update our Script Task and the Instructions of the Review Order Task to handle an array
of products rather than a single product.
Here is our new script
.. code:: python
order_total = sum([ p['product_quantity'] * p['product_price'] for p in products ]) + shipping_cost
And our order summary
.. code:: python
Order Summary
{% for product in products %}
{{ product['product_name'] }}
Quantity: {{ product['product_quantity'] }}
Price: {{ product['product_price'] }}
{% endfor %}
Shipping Cost: {{ shipping_cost }}
Order Total: {{ order_total }}
Parallel MultiInstance
^^^^^^^^^^^^^^^^^^^^^^
We'll also update our 'Retrieve Product' Task and 'Product Not Available' flows to
accommodate multiple products. We can use a Parallel MultiInstance for this, since
it does not matter what order our Employee retrieves the products in.
.. figure:: figures/multiinstance/multiinstance_task_configuration.png
:scale: 30%
:align: center
MultiInstance Task configuration
We've specified :code:`products` as our Input Collection and :code:`product` as our Input Item. The
Input Collection should be an existing collection. We'll create a task instance for each element of
the collection, and copy the value into the Input Item; this is how we'll access the data of the
element.
.. :code::
Item: {{product['product_quantity']}} of {{product['product_name']}}
We also specified :code:`availability` as our Output Collection. Since this variable does not exist,
SpiffWorkflow will automatically create it. You can use an existing variable as an Output Collection;
in this case, its contents will be updated with new values. The Output Item will be copied out of the
child task into the Output Collection.
The 'Retrieve Product' task creates :code:`product_available` from the form input.
Since our input is a list, our output will also be a list. It is possible to generate different output
types if you create the output collections before referring to them.
We have to update our gateway condition to handle the list:
.. figure:: figures/multiinstance/availability_flow.png
:scale: 60%
:align: center
Gateway Condition
Sequential MultiInstance
^^^^^^^^^^^^^^^^^^^^^^^^
SpiffWorkflow also supports Sequential MultiInstance Tasks for collections, or if the loopCardinality
is known in advance, although we have not added an example of this to our workflow. Their configuraiton
is almost idenitcal to the configuration for Parallel MultiInstance Tasks.
Running The Model
^^^^^^^^^^^^^^^^^
If you have set up our example repository, this model can be run with the following command:
.. code-block:: console
./spiff-bpmn-runner.py -p order_product \
-d bpmn/tutorial/product_prices.dmn bpmn/tutorial/shipping_costs.dmn \
-b bpmn/tutorial/top_level_multi.bpmn bpmn/tutorial/call_activity_multi.bpmn