Skip to content

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
In the above example you see we do a simple calculation based on 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