serradura / u-case

Represent use cases in a simple and powerful way while writing modular, expressive and sequentially logical code.
https://rubygems.org/gems/u-case
MIT License
527 stars 33 forks source link

QUESTION: Navigating a flow #117

Closed joshRpowell closed 3 years ago

joshRpowell commented 3 years ago

@serradura fantastic tool! I'm experimenting while still acclimating to all of the features offered. But it appears we have a good fit for it in our project.

Our application has a decision engine where a decision == a flow. in the diagram below we have a decision with various rules == cases. On Success we persist the decision result. On Failure we move on to the next rule or ultimately move on to the next decision.

The chart below is how we have it modeled. The Failure path goes from one rule to the next if a given rule evaluates false. However, when I check Flow.call.success?, the flow stops if the first rule is a failure and does not move on to the next rule.

Screen Shot 2020-12-07 at 11 01 53 AM

We get this to work by inverting the Failure/Success. In other words, a rule's success path navigates to the next rule and so on. While we can accommodate, it seems like we're missing something in our understanding of the DSL.

All of the examples show a chain of Success. Our use case is one that needs to stop at the rule in the flow where the Success occurs and continue through other rules if a rule returns Failure.

We feel like we're missing something obvious. We would be glad to contribute to the documentation once we're able to implement. Any clarity you can provide would be most helpful.

serradura commented 3 years ago

@joshRpowell thank you for asking and I'm so sorry for the long time to answer you.

This year, I'll work hard on the project documentation, and would be great to have more people helping me to do this. (I'll list in an issue all of the features that need documentation).

But let's talk about your question.

Some time ago, I had the same need that you shared and I used pure Ruby to help me to handle this kind of workflow. Because yes, the u-case was made to create a flow with one direction (a sequence of successes). So what I did. I created a module to wraps different kinds of flows, and I used pure Ruby to orchestrate them. e.g.

class A1 < Micro::Case; end
class B1 < Micro::Case; end
class C1 < Micro::Case; end

class A2 < Micro::Case; end
class B2 < Micro::Case; end

class Persit < Micro::Case; end

module PerformWorkflow
  Rule1 = Micro::Cases.flow([A1, B1, C1])
  Rule2 = Micro::Cases.flow([A2, B2])

  class Rule3 < Micro::Case; end

  class Persist < Micro::Case; end

  def self.start(params)
    result1 = Rule1.call(params)

    return result1.then(Persist) if result1.success?

    result2 = Rule2.call(params)

    return result2.then(Persist) if result2.success?

    result3 = Rule3.call(params)

    return result3.then(Persist) if result3.success?
  end
end

You could also create private methods to improve the public method understandability. e.g.

module PerformWorkflow
  extend self

  # ...

  def start(params)
    call(Rule1, params) ||
    call(Rule2, params) ||
    call(Rule3, params)
  end

  private

    def call(rule, params)
      result = rule.call(params)

      return result.then(Persist) if result.success?
    end
end

Because of the Ruby readability, I don't feel the need to create a DSL to solve this kind of issue. What do you think about all of this?

joshRpowell commented 3 years ago

@serradura Thanks so much for responding. Fantastic suggestion! This approach should align well with our goals.

I'll circle up with the team, experiment some and report back.

PS. Docs are solid. I'll keep 👀 open for the issue where help is needed. 👋