Workflow Basics
Workflows are used to orchestrate the lifecycle of a Product Subscription and process the user or systems intent and
apply that to the service. As mentioned above a Subscription is created, then modified N
number of times, after
which it is terminated. During it's life a Subscription may also be validated on a regular basis to check whether
there is any drift between the state captured in the Orchestrator and actual state on the system. This workflow is
slightly different compared to the workflows that process intent and apply that to a system, as it does not modify
the system.
Four types of workflows are defined, three lifecycle related ones to
create, modify and terminate subscriptions, and a fourth one to validate
subscriptions against the OSS and BSS. The decorators
@create_workflow
, @modify_workflow
, @terminate_workflow
, and
@validate_workflow
are used to define the different types of workflow,
and the @step
decorator is used to define workflow steps that can be
used in any type of workflow.
Workflow Architecture - Passing information from one step to the next
Information between workflow steps is passed using State
, which is
nothing more than a collection of key/value pairs, in Python represented
by a Dict
, with string keys and arbitrary values. Between steps the
State
is serialized to JSON and stored in the database. The step
decorator is used to turn a function into a workflow step, all arguments
to the step function will automatically be initialised with the value
from the matching key in the State
. In turn the step function will
return a Dict
of new and/or modified key/value pairs that will be
merged into the State
to be consumed by the next step. The
serialization and deserialization between JSON and the indicated Python
types is done automatically. That is why it is important to correctly
type the step function parameters.
Example
Given this function, when a user correctly makes use of the step decorator it is very easy to extract variables and make a calculation. It creates readable code, that is easy to understand and reason about. Furthermore the variables become available in the step in their correct type according to the domain model. Logic errors due wrong type interpretation are much less prone to happen.
Bad use of the step decorator
@step("A Bad example of using input params")
def my_ugly_step(state: State) -> State:
variable_1 = int(state["variable_1"])
variable_2 = str(state["varialble_2"])
subscription = SubscriptionModel.from_subscription_id(state["subscription_id"])
if variable_1 > 42:
subscription.product_block_model.variable_1 = -1
subscription.product_block_model.variable_2 = "Infinity"
else:
subscription.product_block_model.variable_1 = variable_1
subscription.product_block_model.variable_2 = variable_2
state["subscription"] = subscription
return state
variable_1
. When computing with even more
variables, you van imagine how unreadable the function will be. Now consider the next example.
Good use of the step decorator
@step("Good use of the input params functionality")
def my_beautiful_step(variable_1: int, variable_2: str, subscription: SubscriptionModel) -> State:
if variable_1 > 42:
subscription.product_block_model.variable_1 = -1
subscription.product_block_model.variable_2 = "Infinity"
else:
subscription.product_block_model.variable_1 = variable_1
subscription.product_block_model.variable_2 = variable_2
return state | {"subscriotion": subscription}
As you can see the Orchestrator the orchestrator helps you a lot to condense the logic in your function. The @step
decorator does the following:
- Loads the previous steps state from the database.
- Inspects the step functions signature
- Finds the arguments in the state and injects them as function arguments to the step function
- It casts them to the correct type by using the type hints of the step function.
- Finally it updates the state of the workflow and persists all model changes to the database upon reaching the
return
of the step function.
Forms
The input form is where a user can enter the details for a subscription on a certain product at the start of the workflow, or can enter additional information during the workflow. The input forms are dynamically generated in the backend and use Pydantic to define the type of the input fields. This also allows for the definition of input validations. Input forms are (optionally) used by all types of workflows to gather and validate user input. It is possible to have more than one input form, with the ability to navigate back and forth between the forms, until the last input form is submitted, and the first (or next) step of the workflow is started. This allows for on-the-fly generation of input forms, where the content of the following form(s) depend on the input of the previous form(s). For example, when creating a core link between two nodes, a first input form could ask to choose two nodes from a list of active nodes, and the second form will present two lists with ports on these two nodes to choose from.
Best Practices for writing workflows
While developing a new product, the workflows can be written in any order. For those that use a test-driven development style probably will start with the validate workflow. But in general people start with the create workflow as it helps to discuss the product model (the information involved) and the workflows (the procedures involved) with the stakeholders to get the requirements clear. Once the minimal viable create workflow is implemented, the validate workflow can be written to ensure that all information is administered correctly in all touched OSS and BSS and is not changed again by hand because human workflows were not correctly adapted yet. Then after the terminate workflow is written, the complete lifecycle of the product can be tested. Even when the modify is not implemented, a change to a subscription can be carried out by terminating the subscription and creating it again with the modified input. Finally, the modify workflow is implemented to allow changes to a subscription with minimal or no impact to the customer.Form Magic
As mentioned before, forms are dynamically created from the backend. This means, little to no frontend coding is
needed to make complex wizard like input forms available to the user. When selecting an action in the UI. The first
thing the frontend does is make an api call to load a form from the backend. The resulting JSONschema
is parsed
and the correct widgets are loaded in the frontend. Upon submit this is posted to the backend that does all
validation and signals to the user if there are any errors. The following forms are supported:
- Multiselect
- Drop-down
- Text field (restricted)
- Number (float and dec)
- Radio