workflow

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 11, 2026 License: MIT Imports: 15 Imported by: 0

README

workflow — DAG workflow execution engine

workflow runs long-running, multi-step DAG workflows on top of the task engine. An embedding application submits a workflow — a set of steps with dependency edges — and the engine drives it to completion: it dispatches each step once its parents finish, fans out steps that can run in parallel, enforces a workflow-wide deadline, and lets you revive a failed workflow or cancel one, surviving process restarts and worker crashes.

The workflow engine owns DAG orchestration and state; the task engine owns the execution of each individual step — per-attempt retry and per-attempt timeout are the task engine's job. Like the rest of tasking, this is a library embedded by other applications: it orchestrates, but does not own multi-tenancy, access control, or scheduler leader election — those belong to the embedding application (see DESIGN.md).

For the design record — the Workflow/Step state machines, the single-writer scheduler, the notify-based feedback path, the deadline/timeout model, and crash recovery — see DESIGN.md. This README covers what the package provides and how to use it.

The core idea in one paragraph

A workflow is a DAG of steps. Each step declares its parent steps; the edges form the DAG, and a step becomes runnable only when all its parents are COMPLETE. Every step, whatever its Type, is executed as a single task on the task engine (task name __EXECUTE_WORKFLOW_STEP__), so the workflow engine gets the task engine's reliability for free. The engine has three components that never call each other directly: they exchange messages over Redis and share a database. The database is the source of truth; commands to the scheduler are best-effort pokes that let it act sooner than its periodic maintenance sweep would. Lose a poke and work is delayed, never lost.

Components

Component What it does Lifecycle
Client Workflow API — define, revive, cancel, query, and delete workflows construct, then call
Scheduler The state machine — owns every Workflow/Step transition, dispatches steps Start / Stop; single-active
Step Runner The one task processor that runs a step by dispatching to your handler register with the task engine

The scheduler talks to the task engine as a client (to submit and cancel step tasks) and listens for step results over the notify pub/sub stream — it is not wired to your step handlers directly. You wire these together in your application, injecting the Redis-backed factory callbacks (common.NewRedisIPCMessage*, notify.NewConsumer). Tests inject mocks instead.

Submitting work (Client)

The Client mirrors the task engine's, including the define / submit / define-and-run split: DefineWorkflow writes the rows but does not start the workflow, SubmitWorkflow starts an already-defined one, and DefineAndRunWorkflow does both. The split lets a caller commit its own additional state (or run its own transaction) between writing the workflow and starting it.

client, err := workflow.NewClient(ctx, workflow.NewClientParams{
    Name:             "my-app",
    DefaultCreator:   "my-app",          // opaque; the notify routing key
    Persistence:      dbClient,          // db.Client
    Config:           clientConfig,      // models.WorkflowClientConfig
    Redis:            redisClient,       // goutilsRedis.Client
    IPCSenderFactory: common.NewRedisIPCMessageSend,
    KnownStepTypes:   knownStepTypes,    // map[string]bool — step Types with a registered handler
})

// Define and start in one call:
wf, err := client.DefineAndRunWorkflow(ctx, workflow.DefineWorkflowParams{
    Spec: models.NewWorkflowParameter{
        Name:     "publish-article",
        Deadline: someDeadline,          // mandatory; steps inherit it as their per-attempt timeout
        Steps: map[string]models.NewWorkflowStepParameter{
            "render":  {Type: "render-html", RetryParams: retry},
            "thumbs":  {Type: "make-thumbnails", RetryParams: retry},
            "publish": {Type: "push-cdn", RetryParams: retry,
                ParentSteps: map[string]bool{"render": true, "thumbs": true}},
        },
    },
    // Creator: &perCallCreator,          // optional; overrides DefaultCreator
}, nil /* or an active db.Database txn to join */)

// Or split the two halves — commit your own state in between:
wf, err = client.DefineWorkflow(ctx, params, nil)
// ... commit related application state ...
err = client.SubmitWorkflow(ctx, wf.ID)

// Revive a FAILED / TIMED_OUT workflow (newDeadline required when it TIMED_OUT):
err = client.ReviveWorkflow(ctx, wf.ID, &newDeadline, nil)

// Cancel:
err = client.CancelWorkflow(ctx, wf.ID, nil)

Up-front step-Type validation. KnownStepTypes is the set of step Types that have a registered handler (the same registration the Step Runner holds). DefineWorkflow rejects — with a BadInputError, before writing any rows — a workflow containing a step whose Type has no handler, turning a mid-run failure into a fail-fast at submission. The DAG itself (self-dependency, unknown parents, cycles) is validated at the same point.

Error contract (inspect with errors.As):

  • goutils.BadInputError / goutils.ValidationError — the spec was rejected (bad DAG, or an unregistered step Type); nothing was written.
  • models.PersistenceError — the workflow rows were not created; nothing happened.
  • models.IPCMessageQueueError — the workflow rows were created (or, for revive/cancel, the workflow exists), but the scheduler poke failed. The workflow is not lost: the scheduler's maintenance sweep drives a still-PENDING workflow, and DefineAndRunWorkflow still returns the created entry. (Same contract for SubmitWorkflow, ReviveWorkflow, CancelWorkflow.)

Every submission operation is "write rows, then poke the scheduler" — the Client only ever writes definition data and enqueues a scheduler event; it never mutates the live state of a workflow in flight. That is the scheduler's exclusive job, so there is a single writer of state.

Querying work (Client)

The same client reads and deletes workflows, so an application never has to reach into the db package for ordinary lifecycle work:

// List — the filter is db.WorkflowQueryFilter, reused as-is
limit := 50
workflows, err := client.ListWorkflows(ctx, db.WorkflowQueryFilter{
    CommonListEntryQueryFilter: db.CommonListEntryQueryFilter{Limit: &limit},
    TargetNames:                []string{"publish-article"},
    TargetStates:               []models.WorkflowStateENUM{models.WorkflowStateFailed},
}, nil /* or an active db.Database txn to join */)

// Get one workflow and its steps (topologically sorted)
wf, err := client.GetWorkflow(ctx, workflowID, nil)
steps, err := client.ListWorkflowSteps(ctx, workflowID, nil)

// Get one step, and how it was actually executed
step, history, err := client.GetWorkflowStep(ctx, steps[0].ID, nil)

// Delete — only once the workflow is terminal
err = client.DeleteWorkflow(ctx, workflowID, nil)

Creator scoping. Every read and delete above is scoped to the client's DefaultCreator. ListWorkflows fills in filters.Creators with it when the caller leaves that field empty; setting it explicitly overrides the scope. The single-entry methods (GetWorkflow, ListWorkflowSteps, GetWorkflowStep, DeleteWorkflow, and ReviveWorkflow / CancelWorkflow) report another creator's workflow as goutils.NotFoundError — indistinguishable from one that does not exist, so the client never discloses a workflow it does not own. A client constructed without a DefaultCreator has no scope to apply, so a ListWorkflows that does not carry an explicit Creators is rejected with a goutils.BadInputError rather than silently listing every creator's workflows.

Use db.Database directly if you genuinely need an unscoped, cross-creator view.

Step execution history. GetWorkflowStep answers "how was this step actually processed?". Alongside the step it returns []models.WorkflowStepExecution — one entry per task that ran the step (its first run, plus one per revive), each carrying that task's own execution attempts:

step, history, err := client.GetWorkflowStep(ctx, stepID, nil)
for _, attempt := range history {
    attempt.Task.State                    // how that attempt ended
    for _, exec := range attempt.Executions {
        exec.TerminalState, exec.ErrorMessage
    }
}

Entries are ordered newest task first, so history[0] is the most recent attempt; each entry's Executions run oldest-first. A step that was never dispatched yields an empty history.

Those tasks always carry the reserved creator models.WorkflowExecutionTaskCreator, so they are deliberately invisible to your own task.Client. GetWorkflowStep is the sanctioned way to see them — owning the step is what authorizes the read — so you never need a second task.Client built under the engine's own creator.

Deleting. Only a terminal workflow (COMPLETE / CANCELLED) may be deleted — cancel a live one first, and note that FAILED and TIMED_OUT are not terminal here, because they are revivable. The refusal is a goutils.ConsistencyError. Deleting a workflow cascades away its steps and reaps the tasks (and their execution history) that ran them — a workflow-owned task never outlives its workflow.

Running steps (Step Runner + your handlers)

Implement models.WorkflowStepProcessor for each step Type, then register them all with one Step Runner and hand that runner to the task engine as the processor for the reserved task name models.WorkflowExecutionTaskName. Every workflow step — regardless of Type — runs as a task of that one name; the runner loads the step, reads its Type, and dispatches to your handler:

type renderHandler struct{}

func (h renderHandler) ProcessWorkflowStep(
    ctx context.Context, wf models.Workflow, step models.WorkflowStep,
) error {
    // do the work; honor ctx (it carries the step's deadline)
    return nil // or an error → the step FAILS
}

runner, err := workflow.NewRunWorkflowStepTaskProcessor(dbClient, map[string]models.WorkflowStepProcessor{
    "render-html":     renderHandler{},
    "make-thumbnails": thumbnailHandler{},
    "push-cdn":        cdnHandler{},
})

// Hand the runner to the task engine as the processor for the reserved workflow task name. Task
// processors are supplied declaratively (no registration call): declare the runner as the Processor
// on a TaskDefinition named models.WorkflowExecutionTaskName, inside the queue that serves it —
// alongside any ordinary tasks on that queue.
taskQueues := []models.TaskQueueConfig{
    {
        Name:           "workflow-queue",
        Workers:        4,
        BufferRequests: 16,
        SupportedTasks: []models.TaskDefinition{
            {
                TaskName:  models.WorkflowExecutionTaskName,
                Retry:     models.RetryParam{InitialDelaySec: 5, MaxRetries: 3},
                Processor: runner,
            },
        },
    },
}
// ... then hand the same taskQueues to the receiver's TaskReceiverConfig (which runs the step
// tasks) and the task scheduler's TaskSchedulerConfig (which routes them to "workflow-queue").

The runner never talks to the workflow scheduler about results. When a step's task reaches a terminal state the task engine writes an audit event, notify broadcasts it, and the scheduler — subscribed on the engine's creator channel — turns it into the step's outcome. The runner and the task engine remain unaware of workflow semantics.

Because step tasks are ordinary tasks, declare models.WorkflowExecutionTaskName under a task execution queue in your TaskSchedulerConfig.TaskQueues, and have a receiver serve that queue, exactly as for any other task.

Running the scheduler

Exactly one scheduler runs against a given database (it is the sole writer of workflow state). It needs a task client to dispatch step tasks and a notify consumer to receive their results:

scheduler, err := workflow.NewWorkflowScheduler(ctx, workflow.NewWorkflowSchedulerParams{
    Persistence:           dbClient,
    TaskClient:            taskClient,        // task.Client — dispatches / cancels step tasks
    Config:                schedulerConfig,   // models.WorkflowSchedulerConfig
    Redis:                 redisClient,
    IPCReceiverFactory:    common.NewRedisIPCMessageReceive,
    IPCSenderFactory:      common.NewRedisIPCMessageSend,
    NotifyConsumerFactory: notify.NewConsumer,
})
if err := scheduler.Start(ctx); err != nil { /* ... */ }
defer scheduler.Stop(ctx)

Start recovers any messages stranded in the scheduler queue buffer, subscribes for step feedback, and starts the serial event consumer and the maintenance timer.

Required deployment wiring — the notify producer must emit creator channels. The scheduler receives step results by subscribing to notify:creator:<engine-creator>. That channel is only populated when the notify producer running against the same database is configured with EmitCreator: true (models.NotificationProducerConfig). If it is false, the scheduler's fast-path feedback subscription receives nothing: the engine stays correct — the maintenance sweep reconciles every step against its task's persisted state — but every step outcome is delayed by up to one maintenance interval, on every step, silently (no error is logged anywhere). Treat EmitCreator: true as a hard requirement for the feedback fast path, not an optimization. See DESIGN.md "Task Engine → Workflow Scheduler (feedback)".

Reliability model (what you can rely on)

  • Self-healing progress. The DAG is advanced by IPC pokes and a periodic maintenance sweep that re-scans the DB for anything a lost poke would strand (a workflow to start, a step to dispatch, a deadline to enforce). A dropped message delays progress; it never loses it.
  • Single writer of state. Only the scheduler mutates workflow/step state, and it does so single-threaded off one serial queue — so there are no intra-engine state races to reason about.
  • Steps inherit the task engine's guarantees. Per-step retry (per the step's RetryParams) and per-step timeout are enforced by the task engine; crash-safe reliable queues, idempotent handlers, and poison-message quarantine all come from there.
  • No automatic workflow-level retry. A step that exhausts its task-level retries fails, and its workflow moves to FAILED. Advancing past that is a deliberate, user-initiated revive — the workflow engine never re-runs a failed step on its own.
  • Deadlines are workflow-wide. A step's deadline is derived from the workflow deadline (it is not authored per step); when the deadline passes, in-flight and not-yet-run steps are timed out and the workflow becomes TIMED_OUT. Reviving a TIMED_OUT workflow requires a new deadline.

Configuration

  • models.WorkflowClientConfig — the workflow scheduler's queue name (the client's only IPC target). No retry settings: step retry is a per-step property carried in the spec.
  • models.WorkflowSchedulerConfig — maintenance interval (≥ 10s) and the scheduler queue name. This is a dedicated IPC queue, distinct from the task scheduler's queue.

The workflow scheduler dispatches step tasks through the task client, so it has no execution-queue mappings of its own — step routing lives in the task engine's TaskSchedulerConfig (route __EXECUTE_WORKFLOW_STEP__ to a queue there).

Notifications

Every workflow/step state change is recorded as a durable, creator-tagged SystemEventAudit row in the same transaction as the change. The notify package turns those rows into a best-effort Redis pub/sub stream. This package produces the events; it does not broadcast them. (The scheduler is itself a notify subscriber — that is how step results reach it.)

Not yet supported

Per-step (as opposed to workflow-wide, derived) deadlines, and any automatic workflow-level retry, are out of scope by design. Multi-tenancy isolation and scheduler leader election, as elsewhere in tasking, belong to the embedding application — see DESIGN.md.

Documentation

Overview

Package workflow - DAG workflow engine layered on the task engine

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Client

type Client interface {
	/*
		DefineWorkflow define (but do NOT start) a workflow.

		The workflow row (born PENDING) and its step rows (born DEFINED) are written in a database
		transaction; the scheduler is NOT poked. The caller is responsible for calling
		SubmitWorkflow afterwards to actually start it. This split lets a caller commit its own
		additional state between the define and the submit (state-before-poke). Before any DB write
		the workflow spec is validated as a DAG and every step Type is checked against the client's
		registered step-type set. On error, inspect the returned error with `errors.As`: a
		`models.PersistenceError` means the database operation failed and no workflow was created; a
		`goutils.ValidationError`/`BadInputError` means the spec (or an unknown step Type) was
		rejected before any DB work.

			@param ctx context.Context - execution context
			@param params DefineWorkflowParams - the workflow definition parameters
			@param activeDBClient db.Database - an existing open database transaction to continue in
			@return the newly defined workflow entry
	*/
	DefineWorkflow(
		ctx context.Context, params DefineWorkflowParams, activeDBClient db.Database,
	) (models.Workflow, error)

	/*
		SubmitWorkflow start an already-defined workflow by poking the scheduler.

		This is the submit-only half of DefineAndRunWorkflow: it emits a Process Workflow event for
		a workflow whose rows have already been persisted (via DefineWorkflow). It runs no database
		work. A failed submit `errors.As` a `models.IPCMessageQueueError`: the workflow rows still
		exist and the scheduler's maintenance sweep will eventually drive the still-PENDING workflow,
		so a caller following state-before-poke may treat a submit failure as a lost poke rather than
		a lost workflow.

			@param ctx context.Context - execution context
			@param workflowID string - ID of the already-defined workflow to start
	*/
	SubmitWorkflow(ctx context.Context, workflowID string) error

	/*
		DefineAndRunWorkflow define and start a workflow.

		The combined convenience wrapper: it calls DefineWorkflow, then SubmitWorkflow. The submit
		happens after the rows are persisted and is not part of that transaction. On error, inspect
		the returned error with `errors.As`: a `models.PersistenceError` means the database operation
		failed and no workflow was created; a `models.IPCMessageQueueError` means the workflow was
		created but could not be submitted to the scheduler (the returned entry is still valid).

			@param ctx context.Context - execution context
			@param params DefineWorkflowParams - the workflow definition parameters
			@param activeDBClient db.Database - an existing open database transaction to continue in
			@return the newly defined workflow entry
	*/
	DefineAndRunWorkflow(
		ctx context.Context, params DefineWorkflowParams, activeDBClient db.Database,
	) (models.Workflow, error)

	/*
		ReviveWorkflow request the scheduler revive a FAILED / TIMED_OUT workflow.

		The workflow is read in a database transaction to confirm it exists, then a Revive Failed
		Workflow event is emitted. The client does NOT check the workflow state or the newDeadline
		rule ("required iff TIMED_OUT") - the scheduler's revive handler owns those against live
		state, preserving the single-writer invariant. On error, inspect the returned error with
		`errors.As`: a `models.PersistenceError` means the workflow could not be read; a
		`models.IPCMessageQueueError` means the revive request could not be submitted to the
		scheduler.

			@param ctx context.Context - execution context
			@param workflowID string - ID of the workflow to revive
			@param newDeadline *time.Time - optional new workflow deadline (required by the scheduler
			for a TIMED_OUT workflow)
			@param activeDBClient db.Database - an existing open database transaction to continue in
	*/
	ReviveWorkflow(
		ctx context.Context, workflowID string, newDeadline *time.Time, activeDBClient db.Database,
	) error

	/*
		CancelWorkflow request the scheduler cancel a workflow.

		The workflow is read in a database transaction to confirm it exists, then a Cancel Workflow
		event is emitted. On error, inspect the returned error with `errors.As`: a
		`models.PersistenceError` means the workflow could not be read; a
		`models.IPCMessageQueueError` means the cancel request could not be submitted to the
		scheduler.

			@param ctx context.Context - execution context
			@param workflowID string - ID of the workflow to cancel
			@param activeDBClient db.Database - an existing open database transaction to continue in
	*/
	CancelWorkflow(ctx context.Context, workflowID string, activeDBClient db.Database) error

	/*
		ListWorkflows list workflows belonging to this client's creator.

		The query is creator-scoped (see the Client doc): when `filters.Creators` is empty it is
		filled in with the client's DefaultCreator; when the caller sets it explicitly that set is
		used verbatim. A client with neither a DefaultCreator nor an explicit `filters.Creators` has
		no scope to apply and the call is rejected with a `goutils.BadInputError` rather than
		silently listing every creator's workflows. A failed read `errors.As` a
		`models.PersistenceError`.

			@param ctx context.Context - execution context
			@param filters db.WorkflowQueryFilter - query filtering conditions
			@param activeDBClient db.Database - an existing open database transaction to continue in
			@return the matching workflow entries
	*/
	ListWorkflows(
		ctx context.Context, filters db.WorkflowQueryFilter, activeDBClient db.Database,
	) ([]models.Workflow, error)

	/*
		GetWorkflow fetch one workflow.

		Creator-scoped (see the Client doc): a workflow belonging to another creator `errors.As` a
		`goutils.NotFoundError`, indistinguishable from a workflow which does not exist. Any other
		read failure `errors.As` a `models.PersistenceError`.

			@param ctx context.Context - execution context
			@param workflowID string - workflow ID
			@param activeDBClient db.Database - an existing open database transaction to continue in
			@return the workflow entry
	*/
	GetWorkflow(
		ctx context.Context, workflowID string, activeDBClient db.Database,
	) (models.Workflow, error)

	/*
		ListWorkflowSteps list the steps of a workflow.

		The parent workflow is read first and creator-scoped, so a workflow belonging to another
		creator `errors.As` a `goutils.NotFoundError` and its steps are never returned. The steps
		come back topologically sorted (alphabetical among steps at the same depth), the order the
		persistence layer defines.

			@param ctx context.Context - execution context
			@param workflowID string - the parent workflow ID
			@param activeDBClient db.Database - an existing open database transaction to continue in
			@return the workflow's steps
	*/
	ListWorkflowSteps(
		ctx context.Context, workflowID string, activeDBClient db.Database,
	) ([]models.WorkflowStep, error)

	/*
		GetWorkflowStep fetch one workflow step along with how it was executed.

		Creator-scoped against the step's own creator (see the Client doc): a step belonging to
		another creator `errors.As` a `goutils.NotFoundError`, and nothing about its execution is
		read. Any other read failure `errors.As` a `models.PersistenceError`.

		The returned history holds one entry per task which ran the step - its first run plus one
		per user-initiated revive - each carrying that task's own execution attempts. Ownership of
		the step is what authorizes reading those tasks, which belong to the engine's reserved
		creator and are therefore invisible to an application's own task.Client. Entries are ordered
		newest task first, so `[0]` is the most recent attempt; each entry's executions are ordered
		oldest first. A step which has never been dispatched yields an empty history.

			@param ctx context.Context - execution context
			@param stepID string - workflow step ID
			@param activeDBClient db.Database - an existing open database transaction to continue in
			@return the workflow step entry, and its execution history
	*/
	GetWorkflowStep(
		ctx context.Context, stepID string, activeDBClient db.Database,
	) (models.WorkflowStep, []models.WorkflowStepExecution, error)

	/*
		DeleteWorkflow delete a workflow and reap the tasks that executed its steps.

		Creator-scoped (see the Client doc). Only a terminal workflow (COMPLETE / CANCELLED) may be
		deleted; a live one must be cancelled first, and the refusal `errors.As` a
		`goutils.ConsistencyError`. A workflow belonging to another creator `errors.As` a
		`goutils.NotFoundError` - reported before its state is ever considered. Deleting a workflow
		cascades away its steps and reaps the tasks (and task execution history) which ran them.

			@param ctx context.Context - execution context
			@param workflowID string - workflow ID
			@param activeDBClient db.Database - an existing open database transaction to continue in
	*/
	DeleteWorkflow(ctx context.Context, workflowID string, activeDBClient db.Database) error
}

Client workflow engine client - the user-facing submission / user-mutation API. Every submission operation is "write DB rows (definition only), then poke the scheduler": the Client never performs a workflow state transition (the scheduler is the single writer of live state), it only writes the initial DEFINED/PENDING definition rows and enqueues scheduler events. Alongside those it serves the workflow-facing reads and deletes, which touch no scheduler state at all. See workflow/DESIGN.md "Workflow Client". Mirrors the task engine's task.Client, including the define / submit / define-and-run split.

Creator scoping

Every read and delete on this interface is scoped to the client's creator: the per-call override where one is offered, otherwise the client's DefaultCreator. A workflow (or step) belonging to another creator is reported as not-found rather than as a permission failure, so the client never discloses the existence of one it does not own. `db.Database` remains the unscoped escape hatch for an application that genuinely needs to see across creators.

GetWorkflowStep is the one deliberate reach outside that scope: it returns the tasks which executed a step, and those always carry the engine's reserved models.WorkflowExecutionTaskCreator. Owning the step is what authorizes reading them, so an application never needs a task.Client built under the engine's own creator to find out how its step was processed.

func NewClient

func NewClient(
	parentCtx context.Context, params NewClientParams,
) (Client, error)

NewClient define new workflow client

@param parentCtx context.Context - the parent execution context
@param params NewClientParams - parameters of the new client
@returns the new workflow client

type DefineWorkflowParams

type DefineWorkflowParams struct {
	// Spec the workflow specification (name, deadline, metadata, and the DAG of steps).
	Spec models.NewWorkflowParameter `validate:"required"`
	// Creator optional per-workflow creator override; nil uses the client's DefaultCreator. The
	// resolved value must be non-empty or the define fails validation downstream (the Workflow
	// entry's Creator is `validate:"required"`).
	Creator *string
}

DefineWorkflowParams the per-workflow parameters for the workflow definition entry points. It carries the workflow spec and an optional creator override.

type NewClientParams

type NewClientParams struct {
	// Name of the client - also the IPC sender identity stamped on scheduler pokes.
	Name string `validate:"required"`
	// DefaultCreator opaque creator identity stamped on workflows submitted through this client when
	// the define call does not provide a per-workflow override. tasking never interprets it; it is
	// the routing key for the workflow's audit events and notifications.
	DefaultCreator string
	// Persistence persistence client
	Persistence db.Client `validate:"required"`
	// Config workflow client config
	Config models.WorkflowClientConfig `validate:"required"`
	// Redis REDIS client
	Redis goutilsRedis.Client `validate:"required"`
	// IPCSenderFactory factory function to define Redis based IPC message senders
	IPCSenderFactory task.IPCMsgSenderFactoryCB `validate:"required"`
	// KnownStepTypes the set of step Types with a registered handler (the same registration the Step
	// Runner holds). DefineWorkflow rejects, up front, a workflow containing a step whose Type is not
	// in this set - a fail-fast validation far friendlier than a mid-run failure. Must be non-empty.
	KnownStepTypes map[string]bool `validate:"required,gt=0"`
}

NewClientParams init parameters for a workflow client

type NewWorkflowSchedulerParams

type NewWorkflowSchedulerParams struct {
	// Persistence persistence client
	Persistence db.Client `validate:"required"`
	// TaskClient task engine client, used to dispatch (and later cancel) step execution tasks
	TaskClient task.Client `validate:"required"`
	// Config workflow scheduler config
	Config models.WorkflowSchedulerConfig `validate:"required"`
	// Redis REDIS client
	Redis goutilsRedis.Client `validate:"required"`
	// IPCReceiverFactory factory function to define Redis based IPC message receivers
	IPCReceiverFactory task.IPCMsgReceiverFactoryCB `validate:"required"`
	// IPCSenderFactory factory function to define Redis based IPC message senders
	IPCSenderFactory task.IPCMsgSenderFactoryCB `validate:"required"`
	// NotifyConsumerFactory factory function to define the notification consumer the scheduler
	// subscribes task-engine feedback with. Neither this factory nor Redis is retained after
	// construction - the resulting Consumer is.
	NotifyConsumerFactory NotifyConsumerFactoryCB `validate:"required"`
	// OnFatal, when set, is invoked (at most once) when the processing goroutine hits an
	// unrecoverable fault instead of terminating the process. reporter identifies the faulting
	// thread; err carries the cause (with code position in its chain); timestamp is when it was
	// detected. When nil, the default logs and calls log.Fatal, preserving prior behavior. The
	// goroutine exits after the callback runs.
	OnFatal models.OnFatalCB
}

NewWorkflowSchedulerParams init parameters for a workflow scheduler

type NotifyConsumerFactoryCB

type NotifyConsumerFactoryCB func(
	parentCtx context.Context, params notify.NewConsumerParams,
) (notify.Consumer, error)

NotifyConsumerFactoryCB factory function signature for defining the scheduler's notification consumer. Defaults to notify.NewConsumer; overridable for tests.

type RunWorkflowStepTaskProcessor

type RunWorkflowStepTaskProcessor struct {
	goutils.Component
	// contains filtered or unexported fields
}

RunWorkflowStepTaskProcessor the Step Runner: the single models.TaskExecutionProcessor the workflow engine registers with the task engine under models.WorkflowExecutionTaskName (__EXECUTE_WORKFLOW_STEP__). Every workflow step, regardless of its Type, runs as a task of that one name executed by this one processor. The Runner loads the step referenced by the task's Parameters, derives its parent workflow, and dispatches to the WorkflowStepProcessor registered for the step's Type. See workflow/DESIGN.md "Step Execution".

func NewRunWorkflowStepTaskProcessor

func NewRunWorkflowStepTaskProcessor(
	persistence db.Client,
	handlers map[string]models.WorkflowStepProcessor,
) (*RunWorkflowStepTaskProcessor, error)

NewRunWorkflowStepTaskProcessor define a new workflow Step Runner.

@param persistence db.Client - persistence client
@param handlers map[string]models.WorkflowStepProcessor - step Type to processor mapping this
    Runner supports. Fixed at construction; must contain no nil processors.
@returns new Step Runner

func (*RunWorkflowStepTaskProcessor) ProcessTaskExecution

func (r *RunWorkflowStepTaskProcessor) ProcessTaskExecution(
	ctx context.Context, taskEntry models.Task, _ models.TaskExecution,
) error

ProcessTaskExecution execute one workflow step. This implements models.TaskExecutionProcessor; its return value becomes the task's terminal state. The Runner never reports to the workflow scheduler - feedback flows entirely through the notify path.

@param ctx context.Context - execution context
@param taskEntry models.Task - the __EXECUTE_WORKFLOW_STEP__ task, whose Parameters carry the
    workflow step ID
@param executeEntry models.TaskExecution - the task execution instance (unused: the Runner
    keys entirely off the task Parameters)

type Scheduler

type Scheduler interface {
	/*
		Start the scheduler processing units

			@param ctx context.Context - execution context
	*/
	Start(ctx context.Context) error

	/*
		Stop the workflow scheduler processing units

			@param ctx context.Context - execution context
	*/
	Stop(ctx context.Context) error
}

Scheduler workflow scheduler: the single point of truth and the only mutator of workflow / step state. See workflow/DESIGN.md "Workflow Scheduler".

func NewWorkflowScheduler

func NewWorkflowScheduler(
	parentCtx context.Context,
	params NewWorkflowSchedulerParams,
) (Scheduler, error)

NewWorkflowScheduler define a new workflow scheduler

@param parentCtx context.Context - the parent execution context
@param params NewWorkflowSchedulerParams - parameters of the workflow scheduler
@returns new workflow scheduler

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL