Skip to content

flow_core 集成问题 #6

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
dnnta opened this issue Jun 18, 2020 · 8 comments
Open

flow_core 集成问题 #6

dnnta opened this issue Jun 18, 2020 · 8 comments

Comments

@dnnta
Copy link

dnnta commented Jun 18, 2020

您好,咨询下 flow_core 集成已有业务系统的问题。 现有业务系统是通过 FSM 来进行简单状态管理的,现在想集成这个Gem 满足知会等功能。

但是文档里提到

审批工作流的特点是流程绑定一个表单...

翻看了源码,work_flow, instance, task 基本都关联了 form,且必须 form_filled 后才能 finish。现需要集成的效果是,现有的业务系统只需要在表单 save 时启动这个工作流实例,接收审核结果就行。因此 work_flow 只需要关联已存在的业务 model 就行。比如我们现有的采购流程,希望订单创建保存后,启动 work_flow,关联这个订单ID就行。

demo 里的工作流感觉有点怪,感觉是为了 work_flow 而创建了一个业务 form,而不是先有业务,再去跑的工作流。(之前基本没搞过工作流,这个感觉错了勿怪==~)。

该怎么集成呢,是把业务 model 转 FormKit:Form,适配 flow_kit, 还是对照 flow_kit 按照自己的业务重新撸一份呢

@jasl
Copy link
Member

jasl commented Jun 18, 2020

你的需求其实也是典型场景,类似 Jira Redmine 之类的工作流都是这样用的。

是这样,其实早年的 Dummy 是演示了一个请假流程,使用一个写死的请假记录的模型,然后工作流关联,后来感觉这个用例不够直观,就给换掉了,换成现在的审批流程。

改版前的最后一个提交是 https://github.com/rails-engine/flow_core/tree/b6404c8bdb18a41de260c07001c1e44bbd9103e6
(注:那个版本还没引入 Pipeline API)

当时那个版本的代码放到现在应该依然可用(Workflowon_ 系列钩子函数去掉了,因为 Instance 现在支持 STI),简单讲一下:

首先有一个请假记录模型 app/models/leave.rb

然后接入 Workflow,有两种做法:

创建 LeaveInstance 也就是请假的工作流实例类型,设置跟请假记录的关联(记得添加 migration add_references :flow_core_instances, :leaves)

class LeaveInstance < FlowCore::Instance
  belongs_to :leave
end

class LeaveWorkflow < FlowCore::Workflow
  belongs_to :leave

  private

    def on_build_instance(instance)
      # 这里是创建流程实例的 hook 方法,你可以在这里根据业务需要任意定制
      instance.leave = leave
    end

    def instance_class
      LeaveInstance
    end
end

即可,如果你需要 Pipeline 方便编辑流程,那么多加一个

class LeavePipeline < FlowCore::Pipeline
  belongs_to :leave

  private

    def on_build_workflow(workflow)
      workflow.leave = leave
    end

    def workflow_class
      LeaveWorkflow
    end
end

就可以了,这样好处是扩展代码容易管理,流程有类型容易过滤,但是繁琐一点

另一种偷懒,你不为请假去定义流程的子类,你可以把流程要关联的请假记录的 id 塞进 Instancepayload 中去,初始化流程的代码类似这样去写

FlowCore::Workflow.create_instance payload: { leave_id: @leave.id }

然后可以在 Leave 模型中增加外键 belongs_to :instance (类似 app/models/leave.rb#L6 这样)

如果你还是需要审批意见的话,不需要动态表单这样复杂的话,你可以修改我 Dummy 里的 HumanTask,就让他关联一个你定义死的 AttachedForm (指代审批意见表单或者答复表单),就可以了

这样渲染流程步骤的页面的时候,审批数据(就是请假记录)直接从关联中读取即可

我觉得你也可以这样理解,每一个 HumanTask 都是一个独立的 CRUD 型的任务记录(类似一条需要处理的消息),工作流的作用是根据流程所处的步骤去生成 HumanTask 记录,不知道这样类比你能否理解

@jasl
Copy link
Member

jasl commented Jun 18, 2020

这时候 HumanTask 的代码就比较简单了,类似

class HumanTask < FlowCore::ApplicationRecord
  include FlowCore::TaskExecutable

  belongs_to :workflow, class_name: "FlowCore::Workflow"
  belongs_to :instance, class_name: "FlowCore::Instance", autosave: true

  # 请假记录
  belongs_to :leave, validate: true, autosave: true
  # 对请假的审批意见
  has_one :leave_approval, validate: true, autosave: true

  # 这里配合定义关联时的 `validate: true, autosave: true` 做界面的时候可以用嵌套表单去编辑原始请假记录(如果有需要的话)和审批意见,
  # 并且会做验证,如果关联的记录数据有错,依然不让保存
  accpets_nested_attributes :leave
  accpets_nested_attributes :leave_approval

  belongs_to :assignable, polymorphic: true

  enum status: {
    unassigned: "unassigned",
    assigned: "assigned",
    form_filled: "form_filled", # 可选状态了
    finished: "finished"
  }

  before_validation on: :create do
    if task
      self.workflow = task.workflow
      self.instance = task.instance
    end
  end

  after_create do
    create_leave_approval! # 创建审批意见记录
  end

  def can_finish?
    form_filled? && leave.valid? && (!leave_approval || leave_approval.valid?)
  end

  def can_assign?
    unassigned?
  end

  def can_fill_form?
    assigned? || form_filled?
  end

  def assign!(assignee)
    return unless can_assign?

    update! assignee: assignee, status: :assigned, assigned_at: Time.zone.now
  end

  # 提交完表单保存后,手动在控制器调用下,或者去掉这个状态也可以的
  def fill_form!
    return unless can_fill_form?

    self.status = :form_filled
    self.form_filled_at = Time.zone.now

    save
  end

  def finish!
    return unless can_finish?

    self.status = :finished
    self.finished_at = Time.zone.now

    save!
  end
end

这里因为你流程和审批意见表单都是物理模型了,就没必要存到 InstanceTaskpayload 去了,不过要注意一个是,Dummy 里演示的基于 mruby 的 ArcGuard 设计是从 Taskpayload 里读数据的然后做判断的,你如果觉得对你有用,那么还是要在 finish! 方法里把用于分支判断的字段回写到 Taskpayload 里去

@jasl
Copy link
Member

jasl commented Jun 18, 2020

我接下来翻新 Dummy 的时候 用 任务管理 这个场景做一个类似你需求的例子吧,不知道我回复的你能否理解?

@dnnta
Copy link
Author

dnnta commented Jun 18, 2020

回答的很 nice,非常感谢。

@jasl
Copy link
Member

jasl commented Jun 22, 2020

周末更新了一下,去掉了 TransitionCallback 想了一些场景,发现没啥价值,这样还能少理解一个概念。

做通知任务处理人,直接在 HumanTask#assign! 里加一个 assignee.notifications.create 就好了,这需求简单的要死。

没推到rubygems上,master 已经做了,应该不会影响到现在版本的使用。

@dnnta
Copy link
Author

dnnta commented Jun 22, 2020

为你的勤奋点赞!

已经用你之前的指点在尝试了,现在有个问题是如果有多种工作流的话,HumanTask可能也要做个多态,不然不同类型的工作流的状态变化逻辑都在一个类处理好像行不通。或者应该是在 TransitionCallback 里处理把。

@jasl
Copy link
Member

jasl commented Jun 22, 2020

为你的勤奋点赞!

已经用你之前的指点在尝试了,现在有个问题是如果有多种工作流的话,HumanTask可能也要做个多态,不然不同类型的工作流的状态变化逻辑都在一个类处理好像行不通。或者应该是在 TransitionCallback 里处理把。

我觉得你可以在 HumanTask 上做 STI,然后不同工作流用 HumanTask 的子类,偷懒可以这样修改 HumanTrigger,Dummy 的例子 app/models/flow_kit/transition_triggers/human_task.rb#L35 这里,简单修改成:

def on_task_enable(task)
  transaction do
    assignee =
      case configuration.assign_to
      when Configuration::ASSIGN_TO_ENUM[:candidate]
        assignee_candidates.order("random()").first&.assignable
      when Configuration::ASSIGN_TO_ENUM[:instance_creator]
        task.instance&.creator
      else
        raise "Invalid `assign_to` value - #{configuration.assign_to}"
      end

    # 这里根据工作流的类型来决定创建哪种任务
    human_task_type = 
      case task.workflow
      when ApprovalWorkflow
        "ApprovalHumanTask"
      when BusinessWorkflow
        "BusinessHumanTask"
      else
        raise "Unhandled workflow type - #{task.workflow.class}"
      end

    # `type: human_task_type` 这里利用 STI
    human_task = task_class.create! type: human_task_type, task: task, attached_form: attached_form, form_override: form_override, status: :unassigned
    human_task.assign! assignee
  end
end

如果任务区别特别大的话,也可以做不同的模型,为区别很大的任务模型写对应的 Trigger,或者偷懒上边的这个写法应该也可以适用。

@dnnta
Copy link
Author

dnnta commented Jun 22, 2020

正是这样做的👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

2 participants