Skip to content

codegen for easy adoption of resiliency patterns

License

Notifications You must be signed in to change notification settings

clear-street/reinforcer

Repository files navigation

reinforcer

Tests Coverage Status Go Report Card GitHub tag (latest SemVer pre-release) License: MIT

Reinforcer is a code generation tool that automates middleware injection in a proxy service that fronts your delegate implementation, this aids in building more resilient code as you can use common resiliency patterns in the middlewares such as circuit breakers, retrying, timeouts and others.

NOTE: While version is < 1.0.0 the APIs might dramatically change between minor versions, any breaking changes will be enumerated here starting with version 0.7.0 and forward.

Install

Releases

Visit the releases page for pre-built binaries for OS X, Linux and Windows.

Docker

Use the Docker Image:

docker pull clear-street/reinforcer

Homebrew

Install through Homebrew

brew tap clear-street/reinforcer && brew install reinforcer

Upgrading

brew upgrade clear-street/reinforcer/reinforcer

Usage

CLI

Generate reinforced code for all exported interfaces and structs:

reinforcer --src=./service.go --targetall --outputdir=./reinforced

Generate reinforced code using regex:

reinforcer --src=./service.go --target='.*Service' --outputdir=./reinforced

Generate reinforced code using an exact match:

reinforcer --src=./service.go --target=MyService --outputdir=./reinforced

For more options:

reinforcer --help
Reinforcer is a CLI tool that generates code from interfaces that
will automatically inject middleware. Middlewares provide resiliency constructs
such as circuit breaker, retries, timeouts, etc.

Usage:
  reinforcer [flags]

Flags:
      --config string      config file (default is $HOME/.reinforcer.yaml)
  -d, --debug              enables debug logs
  -h, --help               help for reinforcer
  -i, --ignorenoret        ignores methods that don't return anything (they won't be wrapped in the middleware). By default they'll be wrapped in a middleware and if the middleware emits an error the call will panic.
  -p, --outpkg string      name of generated package (default "reinforced")
  -o, --outputdir string   directory to write the generated code to (default "./reinforced")
  -q, --silent             disables logging. Mutually exclusive with the debug flag.
  -s, --src strings        source files to scan for the target interface or struct. If unspecified the file pointed by the env variable GOFILE will be used.
  -k, --srcpkg strings     source packages to scan for the target interface or struct.
  -t, --target strings     name of target type or regex to match interface or struct names with
  -a, --targetall          codegen for all exported interfaces/structs discovered. This option is mutually exclusive with the target option.
  -v, --version            show reinforcer's version

Using Reinforced Code

  1. Describe the target that you want to generate code for:
type Client interface {
	DoOperation(ctx context.Context, arg string) error
}

Or from a struct:

type Client struct {	
}

func (c *Client) DoOperation(ctx context.Context, arg string) error {
    // ...
    return nil
}
  1. Generate the reinforcer code:
reinforcer --debug --src='./client.go' --target=Client --outputdir=./reinforced
  1. Create the runner/middleware factory with the middlewares you want to inject into the generated code:
r := runner.NewFactory(
    metrics.NewMiddleware(...),
    circuitbreaker.NewMiddleware(...),
    bulkhead.NewMiddleware(...),
    retry.NewMiddleware(...),
    timeout.NewMiddleware(...),
)
  1. Optionally create your predicate for errors that shouldn't be retried
// shouldRetryErrPredicate is a predicate that ignores the "NotFound" errors emited by the DoOperation in Client. All other errors
// are eligible for the middlewares.
shouldRetryErrPredicate := func(method string, err error) bool {
    if method == reinforced.ClientMethods.DoOperation && errors.Is(client.NotFound, err) {
        return false
    }
    return true
}
  1. Wrap the "real"/unrealiable implementation in the generated code:
c := client.NewClient(...)

// reinforcedClient implements the target interface so it can now be used in lieau of any place where the unreliable
// client was used
reinforcedClient := reinforced.NewClient(c, r, reinforced.WithRetryableErrorPredicate(shouldRetryErrPredicate))

A complete example is here