StackStorm / community

Async conversation about ideas, planning, roadmap, issues, RFCs, etc around StackStorm
https://stackstorm.com/
Apache License 2.0
8 stars 3 forks source link

Discuss ways to improve usability of Orquestra #35

Open timfong888 opened 4 years ago

timfong888 commented 4 years ago

Hi, I had the following surface as I am digging deeper into the overall DX.

While it is just so far one opinion, it's a good example of something I've noted as well. While I agree we should not be using Mistral, the point isn't returning to Mistral, it is the comparative verbosity in Orquestra:

Mistral:

task_name:
    action: pack_name.mistral_example
    publish:
        value_one: <% task(task_name).result.value_one %>
        value_two: <% task(task_name).result.value_two %>
        value_three: <% task(task_name).result.value_three %>
        value_four: <% task(task_name).result.value_four %>
    on-success:
        - branch_task_a: <% $.value_one %>
        - branch_task_b: <% not $.value_one and $.value_two and $.value_three %>
        - branch_task_c: <% not $.value_one and not $.value_two and $.value_three %>
        - branch_task_d: <% not $.value_one and $.value_two and not $.value_three %>
        - no_branch_task: <% not $.value_one and not $.value_two and not $.value_three %>

Orquesta:

task_name:
    action: pack_name.workflow_name
    next:
      - when: <% succeeded() and result().value_one %>
        publish:
          - value_one: <% result().value_one %>
          - value_two: <% result().value_two %>
          - value_three: <% result().value_three %>
          - value_four: <% result().value_four %>
        do: branch_task_a
      - when: <% succeeded() and not result().value_one and result().value_two and result().value_three%>
        publish:
          - value_one: <% result().value_one %>
          - value_two: <% result().value_two %>
          - value_three: <% result().value_three %>
          - value_four: <% result().value_four %>
        do: branch_task_b
      - when: <% succeeded() and not result().value_one and not result().value_two and result().value_three %>
        publish:
          - value_one: <% result().value_one %>
          - value_two: <% result().value_two %>
          - value_three: <% result().value_three %>
          - value_four: <% result().value_four %>
        do: branch_task_c
      - when: <% succeeded() and not result().value_one and result().value_two and not result().value_three %>
        publish:
          - value_one: <% result().value_one %>
          - value_two: <% result().value_two %>
          - value_three: <% result().value_three %>
          - value_four: <% result().value_four %>
        do: branch_task_d
      - when: <% succeeded() and not result().value_one and not result().value_two and not result().value_three %>
        publish:
          - value_one: <% result().value_one %>
          - value_two: <% result().value_two %>
          - value_three: <% result().value_three %>
          - value_four: <% result().value_four %>
        do: no_branch_task

I am concerned about this impacting adoption, as well as introducing brittleness into the workflow creation process.

I would like to open the discussion around:

  1. Is there an openness to address the DX of creating workflows?
  2. Are there examples of more concise workflow DSL we can consider as inspiration for re-design? (I can share some)
  3. How can we assess the "lift" involved with this undertaking?
  4. How do we want to evaluate the relative impact on the success of the project?

With regards to (4): I am evaluating the LF creating a course specifically around IT Automation/DevSecOps based on Stack Storm. However, while looking through existing code, I am increasingly concerned with the path to "hello world", first commit (ideally packs), and Proof of Concept.

I believe the adoption to a set of developers will be greatly impacted by their initial DX. We need to get that flywheel right.

Related to this would be a discussion on what are the design principles as true north for development and who are those personas.

m4dcoder commented 4 years ago

@timfong888 Thank you for your input here.

The design decision on moving the publish from the task spec into the task transition spec has to do with the fact that a lot of the output of an action execution differs depending on the final state (i.e. the output of an action execution that succeeded differs than that of failure). We didn't want to do something black and white as publish vs publish-on-error. Therefore, we move the publish to the task transition where users can define the publish based on transition condition.

We are open to address the DX that you expressed here. There's a similar issue opened at https://github.com/StackStorm/orquesta/issues/116. We haven't come up with a solution for it yet. If you have some inspiration on redesign you can share, please feel free to do so. The amount of "lift" depends on the solution for addressing the DX here.

I think you're spot on with 4. This is a challenge for the project. I'll let the rest of the team comment on 4.

emptywee commented 4 years ago

@m4dcoder had already posted before on how this should be done in orquesta. And it stems from a different approach to branching/splitting and task transitioning.

So, basically what was suggested (and what I am doing now migrating from Mistral to Orquesta) is:

task_name:
    action: pack_name.workflow_name
    next:
      - when: <% succeeded() %>
        publish:
          - value_one: <% result().value_one %>
          - value_two: <% result().value_two %>
          - value_three: <% result().value_three %>
          - value_four: <% result().value_four %>
        do:
          - branching

branching:
    action: core.noop
    next:
      - when: <% succeeded() and cxt().value_one %>
        do: branch_task_a
      - when: <% succeeded() and not cxt().value_one and cxt().value_two and cxt().value_three%>
        do: branch_task_b
      - when: <% succeeded() and not cxt().value_one and not cxt().value_two and result().value_three %>
        do: branch_task_c
      - when: <% succeeded() and not cxt().value_one and cxt().value_two and not cxt().value_three %>
        do: branch_task_d
      - when: <% succeeded() and not cxt().value_one and not cxt().value_two and not cxt().value_three %>
        do: no_branch_task

I think you can even drop succeeded() to save space :)

timfong888 commented 4 years ago

Thanks @m4dcoder -- I don't have an explicit recommendation -- I know state-machine management is hard and have seen it done using imperative languages that look nice for inspiration. Happy to share those repos if interested.

I'm primarily doing ground work in DX: want to talk to people about their experience and they would want to see. And my litmus test so far has been, how easy is it for me to write a tutorial for a newbie.

@emptywee : I think that migration of Mistral syntax will solve much of it. There may not need to be much more needed potentially.

But after looking at how other tech finds adoption, I go back to the Rails metaphor which came up at the last Kubecon: regardless of one's opinion of RoR, its adoption is the lesson I draw from. They managed a fine line of "automagik" along with syntactic sugar based on Ruby -- and while there are drawbacks in terms of over-abstraction, the adoption value is my key takeaway, and simplicity is not mutually exclusive to power and access to what goes on under the hood.

So thanks everyone!

m4dcoder commented 4 years ago

@timfong888 Yeah, I am always looking for inspiration to improve design. Please share those repos.

On your example, I want to clarify that using current version of Orquesta, you can simplify as such to what @emptywee suggested (cleaner version below). It may not be obvious, but the WF definition is flexible enough to define a new task that doesn't involve executing an action and can be used for branching purposes. In the example below, if the first task succeeded, proceed with publishing the variables and then transition to another task which will evaluate the values and route accordingly.

task_name:
    action: pack_name.workflow_name
    next:
      - when: <% succeeded() %>
        publish:
          - value_one: <% result().value_one %>
          - value_two: <% result().value_two %>
          - value_three: <% result().value_three %>
          - value_four: <% result().value_four %>
        do: decision_node
decision_node:
   next:
     - when: <% ctx().value_one %>
       do: branch_task_a
     - when: <% not ctx().value_one and ctx().value_two and ctx().value_three %>
       do: branch_task_b
     - when: <% not ctx().value_one and not ctx().value_two and ctx().value_three %>
       do: branch_task_c
     - when: <% not ctx().value_one and ctx().value_two and not ctx().value_three %>
       do: branch_task_d
     - when: <% not ctx().value_one and not ctx().value_two and not ctx().value_three %>
       do: no_branch_task