{"id":17968284,"url":"https://github.com/danielgerlag/workflow_rb","last_synced_at":"2025-06-13T12:08:07.820Z","repository":{"id":56898496,"uuid":"76081067","full_name":"danielgerlag/workflow_rb","owner":"danielgerlag","description":"Lightweight workflow engine for Ruby","archived":false,"fork":false,"pushed_at":"2017-03-16T03:28:26.000Z","size":43,"stargazers_count":14,"open_issues_count":0,"forks_count":2,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-05-26T19:06:59.504Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/danielgerlag.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-12-10T00:41:30.000Z","updated_at":"2022-10-19T16:35:17.000Z","dependencies_parsed_at":"2022-08-21T02:20:43.581Z","dependency_job_id":null,"html_url":"https://github.com/danielgerlag/workflow_rb","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/danielgerlag/workflow_rb","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielgerlag%2Fworkflow_rb","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielgerlag%2Fworkflow_rb/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielgerlag%2Fworkflow_rb/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielgerlag%2Fworkflow_rb/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/danielgerlag","download_url":"https://codeload.github.com/danielgerlag/workflow_rb/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielgerlag%2Fworkflow_rb/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":259642344,"owners_count":22888992,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-29T14:20:50.486Z","updated_at":"2025-06-13T12:08:07.777Z","avatar_url":"https://github.com/danielgerlag.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# WorkflowRb  [![Build Status](https://travis-ci.org/danielgerlag/workflow_rb.svg?branch=master)](https://travis-ci.org/danielgerlag/workflow_rb)\n\nWorkflowRb is a light weight workflow engine for Ruby.  It supports pluggable persistence and concurrency providers to allow for multi-node clusters.\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'workflow_rb'\n```\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install workflow_rb\n\n## Basic Concepts\n\n### Steps\n\nA workflow consists of a series of connected steps.  Each step produces an outcome value and subsequent steps are triggered by subscribing to a particular outcome of a preceeding step.  The default outcome of *nil* can be used for a basic linear workflow.\nSteps are usually defined by inheriting from the StepBody abstract class and implementing the *run* method.  They can also be created inline while defining the workflow structure.\n\nFirst we define some steps\n\n```ruby\nclass HelloWorld \u003c WorkflowRb::StepBody\n  def run(context)\n    puts 'Hello world'\n    WorkflowRb::ExecutionResult.NextStep\n  end\nend\n```\n\nThen we define the workflow structure by composing a chain of steps.\n\n```ruby\nclass HelloWorld_Workflow\n  ID = 'hello world'\n  VERSION = 1\n  DATA_CLASS = nil\n\n  def build(builder)\n    builder\n        .start_with(HelloWorld)\n        .then(GoodbyeWorld)\n  end\nend\n\n```\nThe ID and VERSION constants are used to identify the workflow definition. \n\nYou can also define your steps inline\n\n```ruby\nclass HelloWorld_Workflow\n  ID = 'hello world'\n  VERSION = 1\n  DATA_CLASS = nil\n\n  def build(builder)\n    builder\n        .start_step do |context|\n          puts 'Hello world'\n          WorkflowRb::ExecutionResult.NextStep\n        end\n        .then_step do |context|\n          puts 'Goodbye world'\n          WorkflowRb::ExecutionResult.NextStep\n        end\n  end\nend\n\n```\n\nEach running workflow is persisted to the chosen persistence provider between each step, where it can be picked up at a later point in time to continue execution.  The outcome result of your step can instruct the workflow host to defer further execution of the workflow until a future point in time or in response to an external event.\n\nThe first time a particular step within the workflow is called, the persistence_data property on the context object is *nil*.  The *ExecutionResult* produced by the *run* method can either cause the workflow to proceed to the next step by providing an outcome value, instruct the workflow to sleep for a defined period or simply not move the workflow forward.  If no outcome value is produced, then the step becomes re-entrant by setting persistence_data, so the workflow host will call this step again in the future buy will populate the persistence_data with it's previous value.\n\nFor example, this step will initially run with *nil* persistence_data and put the workflow to sleep for 1 hour, while setting the persistence_data to *'something'*.  1 hour later, the step will be called again but context.persistence_data will now contain the object constructed in the previous iteration, and will now produce an outcome value of *nil*, causing the workflow to move forward.\n\n```ruby\nclass MySleepStep \u003c WorkflowRb::StepBody\n  def run(context)\n    if context.persistence_data\n      WorkflowRb::ExecutionResult.NextStep\n    else\n      WorkflowRb::ExecutionResult.Sleep(Time.now + 3600, 'something')\n    end\n  end\nend\n```\n\n### Passing data between steps\n\nEach step is intended to be a black-box, therefore they support inputs and outputs.  These inputs and outputs can be mapped to a data class that defines the custom data relevant to each workflow instance.\n\nThe following sample shows how to define inputs and outputs on a step, it then shows how define a workflow with a typed class for internal data and how to map the inputs and outputs to attributes on the custom data class.\n\n```ruby\n# Define some steps\nclass AddNumbers \u003c WorkflowRb::StepBody\n  attr_accessor :input1\n  attr_accessor :input2\n  attr_accessor :answer\n\n  def run(context)\n    @answer = @input1 + @input2\n    WorkflowRb::ExecutionResult.NextStep\n  end\nend\n\nclass CustomMessage \u003c WorkflowRb::StepBody\n  attr_accessor :message\n\n  def run(context)\n    puts @message\n    WorkflowRb::ExecutionResult.NextStep\n  end\nend\n\n# Define a class to hold workflow data\nclass MyData\n  attr_accessor :value1\n  attr_accessor :value2\n  attr_accessor :value3\nend\n\n\n# Define a workflow to put the steps together\nclass DataSample_Workflow\n  ID = 'data-sample'\n  VERSION = 1\n  DATA_CLASS = MyData\n\n  def build(builder)\n    builder\n        .start_with(AddNumbers)\n          .input(:input1) {|data| data.value1}\n          .input(:input2) {|data| data.value2}\n          .output(:value3) {|step| step.answer}\n        .then(CustomMessage)\n          .input(:message) {|data| \"The answer is #{data.value3}\"}\n  end\nend\n\n```\n\n### Multiple outcomes / forking\n\nA workflow can take a different path depending on the outcomes of preceeding steps.  The following example shows a process where first a random number of 0 or 1 is generated and is the outcome of the first step.  Then, depending on the outcome value, the workflow will either fork to (TaskA + TaskB) or (TaskC + TaskD)\n\n```ruby\nclass RandomStep \u003c WorkflowRb::StepBody\n  def run(context)\n    WorkflowRb::ExecutionResult.Outcome(rand(2))\n  end\nend\n\nclass ForkSample_Workflow\n  ID = 'fork-sample'\n  VERSION = 1\n  DATA_CLASS = nil\n\n  def build(builder)\n    step1 = builder.start_with(RandomStep)\n    step1.when(0)\n      .then(TaskA)\n      .then(TaskB)\n\n    step1.when(1)\n      .then(TaskC)\n      .then(TaskD)\n\n  end\nend\n```\n\n### Events\n\nA workflow can also wait for an external event before proceeding.  In the following example, the workflow will wait for an event called *\"my-event\"* with a key of *0*.  Once an external source has fired this event, the workflow will wake up and continue processing, passing the data generated by the event onto the next step.\n\n```ruby\nclass EventSample_Workflow\n  ID = 'event-sample'\n  VERSION = 1\n  DATA_CLASS = MyData\n\n  def build(builder)\n    builder\n        .start_with(HelloWorld)\n          .wait_for('my-event', '0')\n          .output(:my_value) { |step| step.event_data }\n        .then(CustomMessage)\n          .input(:message) {|data| \"The event data is #{data.my_value}\"}\n  end\nend\n\n...\n# External events are published via the host\n# All workflows that have subscribed to my-event 0, will be passed \"hello\"\nhost.publish_event('my-event', '0', 'hello')\n```\n\n### Host\n\nThe workflow host is the service responsible for executing workflows.  It does this by polling the persistence provider for workflow instances that are ready to run, executes them and then passes them back to the persistence provider to by stored for the next time they are run.  It is also responsible for publishing events to any workflows that may be waiting on one.\n\n\n#### Usage\n\nWhen your application starts, instantiate a *WorkflowHost*, register your workflow definitions and start it  \n\n\n```ruby\nhost = WorkflowRb::WorkflowHost.new\n\n# register our workflows with the host\nhost.register_workflow(HelloWorld_Workflow)\n\n# start the host\nhost.start\n\n# start a new workflow\nhost.start_workflow('hello world', 1)\n\n```\n\n\n### Persistence\n\nSince workflows are typically long running processes, they will need to be persisted to storage between steps.\nThere are several persistence providers available as seperate gems.\n\n* MemoryPersistenceProvider *(Default provider, for demo and testing purposes)*\n* [MongoDB](workflow_rb-mongo)\n* [ActiveRecord](workflow_rb-db)\n\n### Multi-node clusters\n\nBy default, the WorkflowHost service will run as a single node using the built-in queue and locking providers for a single node configuration.  Should you wish to run a multi-node cluster, you will need to configure an external queueing mechanism and a distributed lock manager to co-ordinate the cluster.  These are the providers that are currently available.\n\n#### Queue Providers\n\n* SingleNodeQueueProvider *(Default built-in provider)*\n* RabbitMQ *(coming soon...)*\n* Apache ZooKeeper *(coming soon...)*\n* 0MQ *(coming soon...)*\n\n#### Distributed lock managers\n\n* SingleNodeLockProvider *(Default built-in provider)*\n* Redis Redlock *(coming soon...)*\n* Apache ZooKeeper *(coming soon...)*\n\n\n## Samples\n\n[Hello World](samples/sample01.rb)\n\n[Multiple outcomes](samples/sample06.rb)\n\n[Passing Data](samples/sample02.rb)\n\n[Events](samples/sample04.rb)\n\n[Deferred execution \u0026 re-entrant steps](samples/sample05.rb)\n\n## Authors\n\n* **Daniel Gerlag** - *Initial work*\n\n## Ports\n\n[.NET](https://github.com/danielgerlag/workflow-core)\n\n[Node.js](https://github.com/danielgerlag/workflow-es)\n\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanielgerlag%2Fworkflow_rb","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdanielgerlag%2Fworkflow_rb","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanielgerlag%2Fworkflow_rb/lists"}