Skip to content

ccremer/go-command-pipeline

Repository files navigation

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.

About

Go utility that executes for-each-func, fail-on-first-error

Resources

License

Stars

Watchers

Forks

Packages

No packages published