Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
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
- Host: GitHub
- URL: https://github.com/twitchtv/circuitgen
- Owner: twitchtv
- License: apache-2.0
- Created: 2019-06-04T18:37:47.000Z (over 5 years ago)
- Default Branch: master
- Last Pushed: 2023-04-27T15:20:44.000Z (over 1 year ago)
- Last Synced: 2024-06-20T15:03:27.462Z (7 months ago)
- Topics: circuit-breaker, circuit-breaker-pattern, code-generation, go, hystrix
- Language: Go
- Size: 46.9 KB
- Stars: 23
- Watchers: 7
- Forks: 7
- Open Issues: 5
-
Metadata Files:
- Readme: README.md
- License: LICENSE
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 valueExample
```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 EDITpackage 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 errorerr := 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 errorerr := 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!