luno / workflow

An Event Driven Workflow framework, written in Go, that supports durable, robust, and idempotent state changes with timeouts, callbacks, scheduled triggers, and await calls. Compatible with Kafka and Reflex out of the box.
BSD 3-Clause "New" or "Revised" License
95 stars 6 forks source link
durable eda eventdrivenarchitecture go golang idempotent kafka reflex state-machine statemachine tdd workflow workflow-automation workflow-engine workflows
Workflow Logo

Workflow

Workflow is an event driven workflow that allows for robust, durable, and scalable sequential business logic to be executed in a deterministic manner.


Features


Installation

To start using workflow you will need to add the workflow module to your project. You can do this by running:

go get github.com/luno/workflow

Adapters

Some adapters dont come with the core workflow module such as kafkastreamer, reflexstreamer, sqlstore, and sqltimeout. If you wish to use these you need to add them individually based on your needs or build out your own adapter.

Kafka

go get github.com/luno/workflow/adapters/kafkastreamer

Reflex

go get github.com/luno/workflow/adapters/reflexstreamer

SQL Store

go get github.com/luno/workflow/adapters/sqlstore

SQL Timeout

go get github.com/luno/workflow/adapters/sqltimeout

Usage

Step 1: Define the workflow

package usage

import (
    "context"

    "github.com/luno/workflow"
)

type Step int

func (s Step) String() string {
    switch s {
    case StepOne:
        return "One"
    case StepTwo:
        return "Two"
    case StepThree:
        return "Three"
    default:
        return "Unknown"
    }
}

const (
    StepUnknown Step = 0
    StepOne Step = 1
    StepTwo Step = 2
    StepThree Step = 3
)

type MyType struct {
    Field string
}

func Workflow() *workflow.Workflow[MyType, Step] {
    b := workflow.NewBuilder[MyType, Step]("my workflow name")

    b.AddStep(StepOne, func(ctx context.Context, r *workflow.Run[MyType, Step]) (Step, error) {
        r.Object.Field = "Hello,"
        return StepTwo, nil
    }, StepTwo)

    b.AddStep(StepTwo, func(ctx context.Context, r *workflow.Run[MyType, Step]) (Step, error) {
        r.Object.Field += " world!"
        return StepThree, nil
    }, StepThree)

    return b.Build(...)
}
---
title: The above defined workflow creates the below Directed Acyclic Graph
---
stateDiagram-v2
    direction LR

    [*]-->One
    One-->Two
    Two-->Three
    Three-->[*]

Step 2: Run the workflow

wf := usage.Workflow()

ctx := context.Background()
wf.Run(ctx)

Stop: To stop all processes and wait for them to shut down correctly call

wf.Stop()

Step 3: Trigger the workflow

foreignID := "82347982374982374"
runID, err := wf.Trigger(ctx, foreignID, StepOne)
if err != nil {
    ...
}

Awaiting results: If appropriate and desired you can wait for the workflow to complete. Using context timeout (cancellation) is advised.

foreignID := "82347982374982374"
runID, err := wf.Trigger(ctx, foreignID, StepOne)
if err != nil {
    ...
}

ctx, cancel := context.WithTimeout(ctx, 10 * time.Second)
defer cancel()

record, err := wf.Await(ctx, foreignID, runID, StepThree)
if err != nil {
    ...
}

Detailed examples

Head on over to ./_examples to get familiar with callbacks, timeouts, testing, connectors and more about the syntax in depth 😊


Workflow's RunState

RunState is the state of a workflow run and can only exist in one state at any given time. RunState is a finite state machine and allows for control over the workflow run. A workflow run is every instance of a triggered workflow.

---
title: Diagram the run states of a workflow
---
stateDiagram-v2
    direction LR

    Initiated-->Running

    Running-->Completed
    Running-->Paused

    Paused-->Running

    Running --> Cancelled
    Paused --> Cancelled

    state Finished {
        Completed --> RequestedDataDeleted
        Cancelled --> RequestedDataDeleted

        DataDeleted-->RequestedDataDeleted
        RequestedDataDeleted-->DataDeleted
    }

Configuration Options

This package provides several options to configure the behavior of the workflow process. You can use these options to customize the instance count, polling frequency, error handling, lag settings, and more. Each option is defined as a function that takes a pointer to an options struct and modifies it accordingly. Below is a description of each available option:

ParallelCount

func ParallelCount(instances int) Option

PollingFrequency

func PollingFrequency(d time.Duration) Option

ErrBackOff

func ErrBackOff(d time.Duration) Option
func LagAlert(d time.Duration) Option
func ConsumeLag(d time.Duration) Option
func PauseAfterErrCount(count int) Option

Glossary

Term Description
Builder A struct type that facilitates the construction of workflows. It provides methods for adding steps, callbacks, timeouts, and connecting workflows.
Callback A method in the workflow API that can be used to trigger a callback function for a specified status. It passes data from a reader to the specified callback function.
Consumer A component that consumes events from an event stream. In this context, it refers to the background consumer goroutines launched by the workflow.
EventStreamer An interface representing a stream for workflow events. It includes methods for producing and consuming events.
Graph A representation of the workflow's structure, showing the relationships between different statuses and transitions.
Producer A component that produces events to an event stream. It is responsible for sending events to the stream.
Record Is the wire format and representation of a run that can be stored and retrieved. The RecordStore is used for storing and retrieving records.
RecordStore An interface representing a store for Record(s). It defines the methods needed for storing and retrieving records. The RecordStore's underlying technology must support transactions in order to prevent dual-writes.
RoleScheduler An interface representing a scheduler for roles in the workflow. It is responsible for coordinating the execution of different roles.
Topic A method that generates a topic for producing events in the event streamer based on the workflow name and status.
Trigger A method in the workflow API that initiates a workflow for a specified foreignID and starting status. It returns a runID and allows for additional configuration options.
WireFormat A format used for serializing and deserializing data for communication between workflow components. It refers to the wire format of the WireRecord.
WireRecord A struct representing a record with additional metadata used for communication between workflow components. It can be marshaled to a wire format for storage and transmission.