Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/twitchtv/circuitgen


https://github.com/twitchtv/circuitgen

circuit-breaker circuit-breaker-pattern code-generation go hystrix

Last synced: about 1 month ago
JSON representation

Awesome Lists containing this project

README

        

# circuitgen

circuitgen generates a circuit wrapper around an interface or struct that encapsulates calling [circuits](https://github.com/cep21/circuit).
A wrapper struct matching the interface or struct method set is generated, and each method call that is context-aware and returning an error is wrapped
by a circuit. These wrapper structs have no outside Go dependencies besides the interface or struct's dependencies.

It's important to provide a `IsBadRequest` to [not count user errors](https://github.com/cep21/circuit#not-counting-user-error-as-a-fault) against the circuit. A bad request is not counted as a success or failure in the circuit, so it does not affect opening or closing the circuit.
For example, a spike in HTTP 4xx errors (ex. Validation errors) should not open the circuit.

An optional `ShouldSkipError` can be provided so that the call is counted as successful even if there is a non-nil error.
For example, DynamoDB responses that return ConditionalCheckedFailException (CCFE) should be counted as successful requests. In the scenario where CCFE is counted as a bad request, if the client is getting CCFE a majority of the time, and the circuit opens (ex. spike of timeouts), then the circuit will prolong closing the circuit until the circuit happens to make a request that doesn't return CCFE.

## Method Wrapping Requirements

When deciding if making a circuit wrapper is right for your interface or struct, consider that methods will only be wrapped if:
* The method accepts a context as the first argument
* The method returns an error as the last value

Example
```go
type Publisher interface {
// Method is wrapped
Publish(ctx context.Context, message string) error
// Method is *not* wrapped
Close() error
}
```

# Installation

```bash
go get github.com/twitchtv/circuitgen
```

# Usage

```bash
circuitgen --pkg --name --out [--alias ] [--circuit-major-version ]
```

Add `./vendor/` to package path if the dependency is vendored; when using Go modules this is unnecessary.

Set the `circuit-major-version` flag if using Go modules and major version 3 or later. This makes the wrappers import the same version as the rest of your code.

## Example

Generating the DynamoDB client into the wrappers directory with circuits aliased as "DynamoDB"

```bash
circuitgen --pkg github.com/aws/aws-sdk-go/service/dynamodb/dynamodbiface --name DynamoDBAPI --alias DynamoDB --out internal/wrappers --circuit-major-version 3
```

This generates a circuit wrapper that satifies the `github.com/aws/aws-sdk-go/service/dynamodb/dynamodbiface.DynamoDBAPI` interface.

```go
// Code generated by circuitgen tool. DO NOT EDIT

package wrappers

import (
"context"

"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbiface"
"github.com/cep21/circuit/v3"
)

// CircuitWrapperDynamoDBConfig contains configuration for CircuitWrapperDynamoDB. All fields are optional
type CircuitWrapperDynamoDBConfig struct {
// ShouldSkipError determines whether an error should be skipped and have the circuit
// track the call as successful. This takes precedence over IsBadRequest
ShouldSkipError func(error) bool

// IsBadRequest is an optional bad request checker. It is useful to not count user errors as faults
IsBadRequest func(error) bool

// Prefix is prepended to all circuit names
Prefix string

// Defaults are used for all created circuits. Per-circuit configs override this
Defaults circuit.Config

// CircuitBatchGetItemPagesWithContext is the configuration used for the BatchGetItemPagesWithContext circuit. This overrides values set by Defaults
CircuitBatchGetItemPagesWithContext circuit.Config
// CircuitBatchGetItemWithContext is the configuration used for the BatchGetItemWithContext circuit. This overrides values set by Defaults
CircuitBatchGetItemWithContext circuit.Config

// ... Rest omitted
}

// CircuitWrapperDynamoDB is a circuit wrapper for dynamodbiface.DynamoDBAPI
type CircuitWrapperDynamoDB struct {
dynamodbiface.DynamoDBAPI

// ShouldSkipError determines whether an error should be skipped and have the circuit
// track the call as successful. This takes precedence over IsBadRequest
ShouldSkipError func(error) bool

// IsBadRequest checks whether to count a user error against the circuit. It is recommended to set this
IsBadRequest func(error) bool

// CircuitBatchGetItemPagesWithContext is the circuit for method BatchGetItemPagesWithContext
CircuitBatchGetItemPagesWithContext *circuit.Circuit
// CircuitBatchGetItemWithContext is the circuit for method BatchGetItemWithContext
CircuitBatchGetItemWithContext *circuit.Circuit

// ... Rest omitted
}

// NewCircuitWrapperDynamoDB creates a new circuit wrapper and initializes circuits
func NewCircuitWrapperDynamoDB(
manager *circuit.Manager,
embedded dynamodbiface.DynamoDBAPI,
conf CircuitWrapperDynamoDBConfig,
) (*CircuitWrapperDynamoDB, error) {
if conf.ShouldSkipError == nil {
conf.ShouldSkipError = func(err error) bool {
return false
}
}

if conf.IsBadRequest == nil {
conf.IsBadRequest = func(err error) bool {
return false
}
}

w := &CircuitWrapperDynamoDB{
DynamoDBAPI: embedded,
ShouldSkipError: conf.ShouldSkipError,
IsBadRequest: conf.IsBadRequest,
}

var err error

w.CircuitBatchGetItemPagesWithContext, err = manager.CreateCircuit(conf.Prefix+"DynamoDB.BatchGetItemPagesWithContext", conf.CircuitBatchGetItemPagesWithContext, conf.Defaults)
if err != nil {
return nil, err
}

w.CircuitBatchGetItemWithContext, err = manager.CreateCircuit(conf.Prefix+"DynamoDB.BatchGetItemWithContext", conf.CircuitBatchGetItemWithContext, conf.Defaults)
if err != nil {
return nil, err
}

// ... Rest omitted

return w, nil
}

// BatchGetItemPagesWithContext calls the embedded dynamodbiface.DynamoDBAPI's method BatchGetItemPagesWithContext with CircuitBatchGetItemPagesWithContext
func (w *CircuitWrapperDynamoDB) BatchGetItemPagesWithContext(ctx context.Context, p1 *dynamodb.BatchGetItemInput, p2 func(*dynamodb.BatchGetItemOutput, bool) bool, p3 ...request.Option) error {
var skippedErr error

err := w.CircuitBatchGetItemPagesWithContext.Run(ctx, func(ctx context.Context) error {
err := w.DynamoDBAPI.BatchGetItemPagesWithContext(ctx, p1, p2, p3...)

if w.ShouldSkipError(err) {
skippedErr = err
return nil
}

if w.IsBadRequest(err) {
return &circuit.SimpleBadRequest{Err: err}
}

return err
})

if skippedErr != nil {
err = skippedErr
}

if berr, ok := err.(*circuit.SimpleBadRequest); ok {
err = berr.Err
}

return err
}

// BatchGetItemWithContext calls the embedded dynamodbiface.DynamoDBAPI's method BatchGetItemWithContext with CircuitBatchGetItemWithContext
func (w *CircuitWrapperDynamoDB) BatchGetItemWithContext(ctx context.Context, p1 *dynamodb.BatchGetItemInput, p2 ...request.Option) (*dynamodb.BatchGetItemOutput, error) {
var r0 *dynamodb.BatchGetItemOutput
var skippedErr error

err := w.CircuitBatchGetItemWithContext.Run(ctx, func(ctx context.Context) error {
var err error
r0, err = w.DynamoDBAPI.BatchGetItemWithContext(ctx, p1, p2...)

if w.ShouldSkipError(err) {
skippedErr = err
return nil
}

if w.IsBadRequest(err) {
return &circuit.SimpleBadRequest{Err: err}
}

return err
})

if skippedErr != nil {
err = skippedErr
}

if berr, ok := err.(*circuit.SimpleBadRequest); ok {
err = berr.Err
}

return r0, err
}

// ... Rest of methods omitted

var _ dynamodbiface.DynamoDBAPI = (*CircuitWrapperDynamoDB)(nil)
```

The wrapper can be used like such

```go
func createWrappedClient() (dynamodbiface.DynamoDBAPI, error) {
m := &circuit.Manager{} // Simplest manager

// Create embedded client
sess := session.Must(session.NewSession(&aws.Config{}))
client := dynamodb.New(sess)

// Create circuit wrapped client
wrappedClient, err := wrappers.NewCircuitWrapperDynamoDB(m, client, wrappers.CircuitWrapperDynamoDBConfig{
// Custom check to skip errors to not count against the circuit. For DynamoDB specifically, ConditionalCheckFailedException
// errors are considered successful requests
ShouldSkipError: func(err error) bool {
aerr, ok := err.(awserr.Error)
return ok && aerr.Code() == dynamodb.ErrCodeConditionalCheckFailedException
},
// Custom check for bad request. This is important to not count user errors as faults.
// See https://github.com/cep21/circuit#not-counting-user-error-as-a-fault
IsBadRequest: func(err error) bool {
rerr, ok := err.(awserr.RequestFailure)
if ok {
return rerr.StatusCode() >= 400 && rerr.StatusCode() < 500
}
return false
},
// Override defaults for the GetItemWithContext circuit
CircuitGetItemWithContext: circuit.Config{
Execution: circuit.ExecutionConfig{
Timeout: 200 * time.Millisecond, // Override default timeout
},
},
})
if err != nil {
return nil, err
}

return wrappedClient, nil
}
```

# Development

Go version 1.12 or beyond is recommended for development.

Run `make test` to run Go tests.

# License

This library is licensed under the Apache 2.0 License.

# Contributing

Any pull requests are extremely welcome! If you run into problems or have questions, please raise a github issue!