circuitgen
circuitgen generates a circuit wrapper around an interface or struct that encapsulates calling circuits.
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 against the circuit.
For example, a spike in HTTP 4xx errors (ex. DynamoDB ConditionalCheckedFailException) should not open the circuit.
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
type Publisher interface {
// Method is wrapped
Publish(ctx context.Context, message string) error
// Method is *not* wrapped
Close() error
}
Installation
go get github.com/twitchtv/circuitgen
Usage
circuitgen --pkg <package path> --name <type name> --out <output path> [--alias <alias>]
Add ./vendor/
to package path if the dependency is vendored.
Example
Generating the DynamoDB client into the wrappers directory with circuits aliased as "DynamoDB"
circuitgen --pkg github.com/aws/aws-sdk-go/service/dynamodb/dynamodbiface --name DynamoDBAPI --alias DynamoDB --out internal/wrappers
This generates a circuit wrapper that satifies the github.com/aws/aws-sdk-go/service/dynamodb/dynamodbiface.DynamoDBAPI
interface.
// 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"
)
// CircuitWrapperDynamoDBConfig contains configuration for CircuitWrapperDynamoDB. All fields are optional
type CircuitWrapperDynamoDBConfig struct {
// 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
// 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.IsBadRequest == nil {
conf.IsBadRequest = func(err error) bool {
return false
}
}
w := &CircuitWrapperDynamoDB{
DynamoDBAPI: embedded,
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 {
err := w.CircuitBatchGetItemPagesWithContext.Run(ctx, func(ctx context.Context) error {
err := w.DynamoDBAPI.BatchGetItemPagesWithContext(ctx, p1, p2, p3...)
if w.IsBadRequest(err) {
return &circuit.SimpleBadRequest{Err: err}
}
return err
})
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
err := w.CircuitBatchGetItemWithContext.Run(ctx, func(ctx context.Context) error {
var err error
r0, err = w.DynamoDBAPI.BatchGetItemWithContext(ctx, p1, p2...)
if w.IsBadRequest(err) {
return &circuit.SimpleBadRequest{Err: err}
}
return err
})
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
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 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!