Categorygithub.com/ccremer/go-command-pipeline
modulepackage
0.20.0
Repository: https://github.com/ccremer/go-command-pipeline.git
Documentation: pkg.go.dev

# README

go-command-pipeline

Go version Version Go Reference Go Report Card Codecov

Small Go utility that executes business actions (functions) in a pipeline. This utility is for you if you think that the business logic is distracted by Go's error handling with if err != nil all over the place.

Usage

import (
    "context"
    pipeline "github.com/ccremer/go-command-pipeline"
)

type Data struct {
    context.Context
    Number int
}

func main() {
	data := &Data{context.Background(), 0} // define arbitrary data to pass around in the steps.
	p := pipeline.NewPipeline[*Data]()
	// define business steps neatly in one place:
	p.WithSteps(
		p.NewStep("define random number", defineNumber),
		p.NewStep("print number", printNumber),
	)
	err := p.RunWithContext(data)
	if err != nil {
		log.Fatal(result)
	}
}

func defineNumber(ctx *Data) error {
	ctx.Number = 10
	return nil
}

// Let's assume this is a business function that can fail.
// You can enable "automatic" fail-on-first-error pipelines by having more small functions that return errors.
func printNumber(ctx *Data) error {
	_, err := fmt.Println(ctx.Number)
	return err
}

See more usage in the examples dir

Who is it for

This utility is interesting for you if you have many business functions that are executed sequentially, each with their own error handling. Do you grow tired of the tedious error handling in Go when all you do is passing the error "up" in the stack in over 90% of the cases, only to log it at the root? This utility helps you focus on the business logic by dividing each failure-prone action into small steps since pipeline aborts on first error.

Consider the following prose example:

func Persist(data Data) error {
    err := database.prepareTransaction()
    if err != nil {
        return err
    }
    err = database.executeQuery("SOME QUERY", data)
    if err != nil {
        return err
    }
    err = database.commit()
    return err
}

We have tons of if err != nil that bloats the function with more error handling than actual interesting business logic.

It could be simplified to something like this:

func Persist(data *Data) error {
    p := pipeline.NewPipeline[*Data]()
    p.WithSteps(
        p.NewStep("prepareTransaction", prepareTransaction()),
        p.NewStep("executeQuery", executeQuery()),
        p.NewStep("commitTransaction", commit()),
    )
    return p.RunWithContext(data)
}

func executeQuery() pipeline.ActionFunc[*Data] {
	return func(data *Data) error {
		err := database.executeQuery("SOME QUERY", data)
		return err
	)
}
...

While it seems to add more lines in order to set up a pipeline, it makes it very easily understandable what Persist() does without all the error handling. Plus, each small step might get easier to unit test.

# Packages

No description provided by the author

# Functions

And returns a Predicate that does logical AND of the given predicates.
Bool returns a Predicate that simply returns v when evaluated.
BoolPtr returns a Predicate that returns *v when evaluated.
LoadFromContext returns the value from the given context with the given key.
LoadFromContextOrDefault is similar to MustLoadFromContext, except it returns the given default value if the key doesn't exist.
MustLoadFromContext is similar to LoadFromContext, except it doesn't return a bool to indicate whether the key exists.
MutableContext adds a map to the given context that can be used to store mutable values in the context.
NewDependencyRecorder returns a new instance of DependencyRecorder.
NewFanOutStep creates a pipeline step that runs nested pipelines in their own Go routines.
NewPipeline returns a new Pipeline instance.
NewStep returns a new Step with given name and action.
NewStepIf is syntactic sugar for NewStep with Step.When.
NewWorkerPoolStep creates a pipeline step that runs nested pipelines in a thread pool.
Not returns a Predicate that evaluates, but then negates the given Predicate.
Or returns a Predicate that does logical OR of the given predicates.
StoreInContext adds the given key and value to ctx.
SupplierFromSlice returns a Supplier that accepts the given slice of Pipeline and iterates over it to feed the channel.

# Structs

DependencyError is an error that indicates which steps did not satisfy dependency requirements.
DependencyRecorder is a Recorder and DependencyResolver that tracks each Step executed and can be used to query if certain steps are in the Records.
Options configures the given Pipeline with a behaviour-altering settings.
Pipeline holds and runs intermediate actions, called "steps".
Step is an intermediary action and part of a Pipeline.

# Interfaces

DependencyResolver provides means to query if a pipeline Step is satisfied as a dependency for another Step.
Recorder Records the steps executed in a pipeline.
Result is the object that is returned after each step and after running a pipeline.

# Type aliases

ActionFunc is the func that contains your business logic.
ErrorHandler is a func that gets called when a step's ActionFunc has finished with an error.
Listener is a simple func that listens to Pipeline events.
ParallelResultHandler is a callback that provides a Result map and expect a single, combined Result object.
Predicate is a function that expects 'true' if an ActionFunc should run.
Supplier is a function that spawns Pipeline for consumption.