Skip to content

Remove reliance on key order in "do" #875

Closed
@matthias-pichler

Description

@matthias-pichler

What would you like to be added:

I propose to:

  1. only use the continue FlowDirective to continue a loop
  2. make then required in all tasks
  3. add a start field to the workflow that denotes the first task to execute
document:
  dsl: '1.0.0-alpha1'
  namespace: test
  name: order-pet
  version: '1.0.0'
  title: Order Pet - 1.0.0
  summary: >
  # Order Pet - 1.0.0
  ## Table of Contents
  - [Description](#description)
  - [Requirements](#requirements)
  ## Description
  A sample workflow used to process an hypothetic pet order using the [PetStore API](https://petstore.swagger.io/)
  ## Requirements
  ### Secrets
   - my-oauth2-secret
use:
  authentications:
    petStoreOAuth2:
      oauth2: my-oauth2-secret
  functions:
    getAvailablePets:
      call: openapi
      with:
        document:
          uri: https://petstore.swagger.io/v2/swagger.json
        operation: findByStatus
        parameters:
          status: available
  secrets:
  - my-oauth2-secret
start: getAvailablePets
do:
  getAvailablePets:
    call: getAvailablePets
    output:
      from: "$input + { availablePets: [.[] | select(.category.name == "dog" and (.tags[] | .breed == $input.order.breed))] }"
    then: submitMatchesByMail
  submitMatchesByMail:
    call: http
    with:
      method: post
      endpoint:
        uri: https://fake.smtp.service.com/email/send
        authentication: petStoreOAuth2
      body:
        from: noreply@fake.petstore.com
        to: ${ .order.client.email }
        subject: Candidates for Adoption
        body: >
        Hello ${ .order.client.preferredDisplayName }!

        Following your interest to adopt a dog, here is a list of candidates that you might be interested in:

        ${ .pets | map("-\(.name)") | join("\n") }

        Please do not hesistate to contact us at info@fake.petstore.com if your have questions.

        Hope to hear from you soon!

        ----------------------------------------------------------------------------------------------
        DO NOT REPLY
        ----------------------------------------------------------------------------------------------
    then: end

Why is this needed:

Currently the task execution order is determined by the order in which the tasks appear in the "do" field.
This is very convenient for writing small workflows.
I however see two mayor drawbacks with this approach:

  1. similar problems as with a "switch fallthrough". If one forgets a "then" field the execution might resume at a task that is different from the one I expected.

Another interesting case is that the then field is allowed top level in switch tasks. So The way I understand it, this would execute the processElectronicOrder task when the default case is reached because it sets a continue clause.

document:
  dsl: '1.0.0-alpha1'
  namespace: test
  name: sample-workflow
  version: '0.1.0'
do:
  processOrder:
    switch:
      case1:
        when: .orderType == "electronic"
        then: processElectronicOrder
      case2:
        when: .orderType == "physical"
        then: processPhysicalOrder
      default:
        then: continue
  processElectronicOrder:
    execute:
      sequentially:
        validatePayment: {...}
        fulfillOrder: {...}
    then: exit
  processPhysicalOrder:
    execute:
      sequentially:
        checkInventory: {...}
        packItems: {...}
        scheduleShipping: {...}
    then: exit
  handleUnknownOrderType:
    execute:
      sequentially:
        logWarning: {...}
        notifyAdmin: {...}
  1. reliance on object keys forces implementers to alway process/store definitions as text blobs or strings.
    Some problems this causes:
  • One cannot store the definition as a JSON object in SQL or NoSQL databases as they usually do not preserve Object key order. Thus one cannot use built-in filtering in databases
  • When building an API to create workflows the definition has to be accepted as a string or file blob then parsed and validated with the JSON schema and then the parsed object cannot be used because key order is not preserved. This might look something like this in TypeScript:
function parse(input: string): IDefinition {
  const obj: Workflow = JSON.parse(input);

  validate(obj);

  // parse document as map
  const map: Map<"do", Map<string, Task>> = yaml.parse(input, {
    mapAsMap: true,
  });
  // get task names in order of appearance
  const taskNames = Array.from(map.get("do")?.keys() ?? []);
  const tasks = new Map<string, Task>();
  taskNames.forEach((name) => {
    // get task as object instead of Map
    const task = obj.do[name];
    if (task) {
      tasks.set(name, task);
    }
  });

  return { ...obj, do: tasks, text: input };
}

and this only works because Map keys preserve order of insertion, assuming that yaml never changes the implementation that would change the insertion order.

  1. when providing a UI to build/visualize workflows the logic to create a valid and correct workflow definition might become incredible complex because on has to be precise about the ordering of keys, handle exit and fallthrough cases etc...

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: specChanges in the Specification

    Type

    No type

    Projects

    Status

    Done

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions