cutout

package module
v0.0.0-...-6de6e70 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 24, 2020 License: MIT Imports: 7 Imported by: 2

README

Cutout

Build Status License Project status GoDoc

Cutout is a package implementing the circuit breaker design pattern for consuming third-party apis. First a bit about the circuit breaker design pattern

Circuit Breaker

Imagine a very simple circuit like this one: close_circuit

Here, the switch is evidently closed, so it is safe to say that the circuit is a CLOSED one. Meaning, electricity is gonna flow through the circuit. Or circuit is working fine. So whenever there is a mishap, like excessive voltage or whatever(not that into electrical stuffs), the switch opens up(the circuit breaker does that), stoping the circuit from being a whole and thus, stopping the electricity flow causing the circuit to pause. So at this point, we can call the circuit an OPEN one. Like this: open_circuit

Enough of electricity, now into the beautiful software world. So whenever your piece of software is consuming an api, it is natural for the service you are trying to call to fail. So you keep calling it , it keeps failing, eventually its gonna cost you a lot of resource consumption, failures etc. Comes the circuit breaker design pattern. This design pattern, like the electrical example i've provided has three(yes , one extra) states:

CLOSED STATE: In this state, the service you are trying to consume is working fine, sending you responses in your desired time and all. This is the initial state.

OPEN STATE: After the service you are consuming fails upto a certain threshold you've provided, the fail_threshold which is a part of the circuit breaker, the circuit breaker reaches the open state. So instead of calling the service, the circuit breaker now goes for the fallback methods you've provided to get the responses, and to keep the work flow intact. Now, what if the service comes back on?!

HALF-OPEN STATE: So your circuit breaker is in the open state , serving the fallbacks, now to check if the service is back alive, the circuit breaker changes its state and calls the service once to check if its alive after a certain period, the health check period(another part of the circuit breaker). If the service is back on, the state changes to closed and does what the ciruit does in the closed state and if not, it changes back to the open state serving the fallbacks.

For more details, see https://martinfowler.com/bliki/CircuitBreaker.html

So, Cutout

Cutout follows the circuit breaker design pattern and helps you call your services in a safer way. Cutout comes with additional features like:

  1. Multilevel fallback functions(in case even the fallback fails)
  2. Custom BackOff function on the request level for generating backoff timeout logics
  3. Event channel to capture events like State change or failure detection
  4. Get analytical data on the circuit breaker
Installing
go get -u github.com/Anondo/cutout

Usage

Import The Package

import "github.com/Anondo/cutout"

Initiate the circuit breaker and request instances

 var (
	 cb = &cutout.CircuitBreaker{
	  	FailThreshold:     100,
	  	HealthCheckPeriod: 15 * time.Second,
	 }
	 req = cutout.Request{
	  	URL:           "http://localhost:9090",
	  	AllowedStatus: []int{http.StatusOK},
	  	Method:        http.MethodPost,
	  	TimeOut:       2 * time.Second,
	  	RequestBody:   bytes.NewBuffer([]byte(`{"name":"abcd"}`)),
	  	BackOff: func(t time.Duration) time.Duration {
		    return time.Duration(int(t/time.Second)*5) * time.Second
	      },
	 }

 )

Prepare a fallback function


func() theFallbackFunc(*cutout.Response, error) {
  // some fallback codes here...

  return &cutout.Response{
    BodyString: cachedResponse,
  }, nil

}

Call a third party service from your handler


func thehandler(w http.ResponseWriter, r *http.Request) {

	resp, err := cb.Call(&req, theFallbackFunc)

	if err != nil {
		fmt.Fprint(w, err.Error())
		return
	}

	w.WriteHeader(http.StatusOK)
	fmt.Fprint(w, resp.BodyString)
}

For more details, see the docs and examples.

Contributing

Totally open to suggestions. See the contributions guide here.

License

Cutout is licensed under the MIT License.

Documentation

Overview

Package cutout implements the circuit breaker design pattern(see: https://martinfowler.com/bliki/CircuitBreaker.html) for calling third party api services.

Cutout comes with features like:

1. Multilevel fallback functions(in case even the fallback fails)

2. Custom BackOff function on the request level for generating backoff timeout logics

3. Event channel to capture events like State change or failure detection

Here is a basic example:

 package main

 import (
	 "bytes"
	 "fmt"
	 "log"
	 "net/http"
	 "time"

	 "github.com/Anondo/cutout"
 )

 var (
	 cb = &cutout.CircuitBreaker{
	  	FailThreshold:     100,
	 	HealthCheckPeriod: 15 * time.Second,
	 }
	 req = cutout.Request{
	  	URL:           "http://localhost:9090",
	  	AllowedStatus: []int{http.StatusOK},
	  	Method:        http.MethodPost,
	  	TimeOut:       2 * time.Second,
	  	RequestBody:   bytes.NewBuffer([]byte(`{"name":"abcd"}`)),
		BackOff: func(t time.Duration) time.Duration {
		  return time.Duration(int(t/time.Second)*5) * time.Second
	        },
	 }

	 cache = `{"name":"Mr. Test","age":69,"cgpa":4}`
 )

 func thehandler(w http.ResponseWriter, r *http.Request) {

	 resp, err := cb.Call(&req, func() (*cutout.Response, error) {
	 	 return &cutout.Response{
			 BodyString: cache,
		 }, nil
	 })

	 if err != nil {
		 fmt.Fprintf(w, err.Error())
		 return
	 }

	 w.WriteHeader(http.StatusOK)
	 fmt.Fprintf(w, resp.BodyString)
 }

 func main() {

	 http.HandleFunc("/", thehandler)

	 log.Println("Service A is running on http://localhost:8080 ...")

	 if err := http.ListenAndServe(":8080", nil); err != nil {
		 log.Fatal(err.Error())
	 }
 }

See https://github.com/Anondo/cutout/examples.

Index

Constants

View Source
const (
	StateChangeEvent = "STATE_CHANGE"
	FailureEvent     = "FAILURE"
)

Events

View Source
const (
	ClosedState   = "CLOSED"
	OpenState     = "OPEN"
	HalfOpenState = "HALF_OPEN"
)

states of the circuit breaker

Variables

This section is empty.

Functions

This section is empty.

Types

type Analytics

type Analytics struct {
	RequestSent    int             `json:"request_sent"`
	TotalFailures  int             `json:"total_failures"`
	FallbackCalls  int             `json:"fallback_calls"`
	Failures       []Failure       `json:"failures"`
	TotalCalls     int             `json:"total_calls"`
	SuccessRate    float64         `json:"success_rate"`
	FailureRate    float64         `json:"failure_rate"`
	RequestRecords []RequestRecord `json:"request_records"`
}

Analytics contains analytical informations regarding the circuit breaker

type CircuitBreaker

type CircuitBreaker struct {
	FailThreshold     int
	HealthCheckPeriod time.Duration
	// contains filtered or unexported fields
}

CircuitBreaker is the circuit breaker!!!

func NewCircuitBreaker

func NewCircuitBreaker(failThreshold int, healthCheckPeriod time.Duration) *CircuitBreaker

NewCircuitBreaker creates a new circuit breaker

func (*CircuitBreaker) Call

func (c *CircuitBreaker) Call(req *Request, fallbackFuncs ...func() (*Response, error)) (*Response, error)

Call calls an external service using the circuit breaker design

Parameters:

1. *cutout.Request -------> The request object

2. ...func()(*Response , error) -----> one or many fallback functions which must return a *cutout.Response & error instance

Example:

 resp, err := cb.Call(&req, func() (*cutout.Response, error) {
	 return &cutout.Response{
	 	 BodyString: cache,
  }, nil
 })

func (*CircuitBreaker) CallWithCustomRequest

func (c *CircuitBreaker) CallWithCustomRequest(req *http.Request, allowedStatus []int,
	fallbackFuncs ...func() (*Response, error)) (*Response, error)

CallWithCustomRequest calls an external service using the circuit breaker design with a custom request function

Parameters:

1. *http.Request -------> The request object of the built-in http package

2. []int -------> Allowed http status codes, which wont be counted as failures

3. ...func()(*Response , error) -----> one or many fallback functions which must return a *cutout.Response & error instance

Example:

 req, err := http.NewRequest(http.MethodGet, url, nil)
 if err != nil {
	return err
 }

 ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
 defer cancel()
 req = req.WithContext(ctx)
 resp, err := cb.CallWithCustomRequest(req, []int{http.StatusOK}, func() (*Response, error) {
	 return &Response{
		 BodyString: cache,
		 Response: &http.Response{
			 StatusCode: http.StatusOK,
		 },
	 }, nil
 })

func (*CircuitBreaker) FailCount

func (c *CircuitBreaker) FailCount() int

FailCount returns the count of failure

func (*CircuitBreaker) GetAnalytics

func (c *CircuitBreaker) GetAnalytics() *Analytics

GetAnalytics returns the analytics instance of the circuit breaker

func (*CircuitBreaker) InitAnalytics

func (c *CircuitBreaker) InitAnalytics()

InitAnalytics initializes the analytics instance for the circu breaker to start analyzing

func (*CircuitBreaker) InitEvent

func (c *CircuitBreaker) InitEvent(e chan string)

InitEvent initializes the circuit breaker events NOTE: the parameter must be a buffered channel

Parameters:

1. chan string ------> the event channel, must be a buffered channel so that the circuit doesn't get blocked out

Example:

 events := make(chan string, 2)

 cb.InitEvent(events)

 go func() {
	 log.Println("Waiting for events from circuit breaker...")
	 for {
		 switch <-events {
		 case cutout.StateChangeEvent:
			 log.Println("Status change occured")
			 log.Println("Current state:", cb.State())
		 case cutout.FailureEvent:
			 log.Println("Failure occured")
		 }
	 }
	 log.Println("Done with the waiting")
 }()

func (*CircuitBreaker) LastFailed

func (c *CircuitBreaker) LastFailed() *time.Time

LastFailed returns the time object of the last failure

func (*CircuitBreaker) State

func (c *CircuitBreaker) State() string

State returns the current satte of the circuit

type Failure

type Failure struct {
	Message       string    `json:"message"`
	OccurredAt    time.Time `json:"occurred_at"`
	TotalFailures int       `json:"total_failures"`
}

Failure holds the failure instance information

type Request

type Request struct {
	URL           string
	Method        string
	RequestBody   *bytes.Buffer
	Headers       map[string]string
	AllowedStatus []int
	TimeOut       time.Duration
	BackOff       func(time.Duration) time.Duration
}

Request represents the data needed to make http requests

func NewRequest

func NewRequest(url, method string, headers map[string]string,
	requestBody *bytes.Buffer, allowedStatus []int, timeout time.Duration) Request

NewRequest is the factory function for requests i.e, creates a new request

type RequestRecord

type RequestRecord struct {
	Name        string    `json:"name"`
	Method      string    `json:"method"`
	StatusCode  int       `json:"status_code"`
	StatusText  string    `json:"status_text"`
	Message     string    `json:"message"`
	RequestedAt time.Time `json:"requested_at"`
}

RequestRecord holds the information of a request incident

type Response

type Response struct {
	*http.Response
	BodyString string
}

Response represents the response data from an http service

Directories

Path Synopsis
examples
all_in_action command
simple command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL