service_decorators

package module
v0.9.1 Latest Latest
Warning

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

Go to latest
Published: Dec 22, 2018 License: Apache-2.0 Imports: 9 Imported by: 2

README

Simplify microservice development

What’s the most complicated part of implementing a microservice except the core business logic?

You might think it must be RPC end point part, which makes your business logic as a real service can be accessed from the network.

But, this is not true. By leveraging the opensource RPC packages, such as, apache thrift, gRPC, this part could be extremely easy except defining your service interface with some IDL.

The following codes are about starting a service server based on thrift.

transportFactory := thrift.NewTFramedTransportFactory(thrift.NewTTransportFactory())
protocolFactory := thrift.NewTBinaryProtocolFactoryDefault()
serverTransport, err := thrift.NewTServerSocket(NetworkAddr)
if err != nil {
	os.Exit(1)
}
processor := calculator_thrift.NewCalculatorProcessor(&calculatorHandler{})
server := thrift.NewTSimpleServer4(processor, serverTransport, transportFactory, protocolFactory)
server.Serve()

As we all known, to build a robust and maintainable service is not an easy task. There are a lot of tricky work as the following:

image

Most of them are much complicated than RPC, in some cases they are even complicated than your core business logic.

For example, the code section of rate limit

...
bucket := make(chan struct{}, tokenBucketSize)
//fill the bucket firstly
for j := 0; j < tokenBucketSize; j++ {
	bucket <- struct{}{}
}
go func() {
	for _ = range time.Tick(interval) {
		for i := 0; i < numOfReqs; i++ {
			bucket <- struct{}{}
			sleepTime := interval / time.Duration(numOfReqs)
			time.Sleep(time.Nanosecond * sleepTime)
		}
	}
}()
...

select {
	case <-dec.tokenBucket:
		return executeBusinessLog(req),nil
	default:
		return errors.New("beyond the rate limit")
}

...

Just as the following diagram shows normally what you have to do is much more than what you want to do when implementing a microservice.

image

The goal of the project is to reduce your work on what you have to do and let you only focus on what you want to do.

All these common functions (e.g. rate limit, circuit break, metric) are encapsulated in the components, and these common logics can be injected in your service implementation transparently by leveraging decorator pattern.

image

image

The following is an example of adding rate limit, circuit break and metrics functions with the prebuilt decorators.

if rateLimitDec, err = service_decorators.CreateRateLimitDecorator(time.Millisecond*1, 100); err != nil {
		return nil, err
	}

	if circuitBreakDec, err = service_decorators.CreateCircuitBreakDecorator().
		WithTimeout(time.Millisecond * 100).
		WithMaxCurrentRequests(1000).
		WithTimeoutFallbackFunction(addFallback).
		WithBeyondMaxConcurrencyFallbackFunction(addFallback).
		Build(); err != nil {
		return nil, err
	}

	gmet := g_met.CreateGMetInstanceByDefault("g_met_config/gmet_config.xml")
	if metricDec, err = service_decorators.CreateMetricDecorator(gmet).
		NeedsRecordingTimeSpent().Build(); err != nil {
		return nil, err
	}
	decFn := rateLimitDec.Decorate(circuitBreakDec.Decorate(metricDec.Decorate(innerFn)))
  ...

Refer to the example: https://github.com/easierway/service_decorators_example/blob/master/example_service_test.go

Not only for building service

You definitely can use this project in the cases besides building a service. For example, when invoking a remote service, you have to consider on fault-over and metrics. In this case, you can leverage the decorators to simplify your code as following:

func originalFunction(a int, b int) (int, error) {
	return a + b, nil
}

func Example() {
	// Encapsulate the original function as service_decorators.serviceFunc method signature
	type encapsulatedReq struct {
		a int
		b int
	}

	encapsulatedFn := func(req Request) (Response, error) {
		innerReq, ok := req.(encapsulatedReq)
		if !ok {
			return nil, errors.New("invalid parameters")
		}
		return originalFunction(innerReq.a, innerReq.b)
	}

	// Add the logics with decorators
	// 1. Create the decorators
	var (
		retryDec        *RetryDecorator
		circuitBreakDec *CircuitBreakDecorator
		metricDec       *MetricDecorator
		err             error
	)

	if retryDec, err = CreateRetryDecorator(3 /*max retry times*/, time.Second*1,
		time.Second*1, retriableChecker); err != nil {
		panic(err)
	}

	if circuitBreakDec, err = CreateCircuitBreakDecorator().
		WithTimeout(time.Millisecond * 100).
		WithMaxCurrentRequests(1000).
		Build(); err != nil {
		panic(err)
	}

	gmet := g_met.CreateGMetInstanceByDefault("g_met_config/gmet_config.xml")
	if metricDec, err = CreateMetricDecorator(gmet).
		NeedsRecordingTimeSpent().Build(); err != nil {
		panic(err)
	}

	// 2. decorate the encapsulted function with decorators
	// be careful of the order of the decorators
	decFn := circuitBreakDec.Decorate(metricDec.Decorate(retryDec.Decorator(encapsulatedFn)))
	ret, err := decFn(encapsulatedReq{1, 2})
	fmt.Println(ret, err)
	//Output: 3 <nil>
}

Decorators

Decorators List
  1. Rate Limit Decorator
  2. Circuit Break Decorator
  3. Advanced Circuit Break Decorator
  4. Metric Decorator
  5. Retry Decorator
  6. Chaos Engineering Decorator
ChaosEngineeringDecorator
What is Chaos Engineering?

According to the principles of chaos engineering, chaos engineering is “the discipline of experimenting on a distributed system in order to build confidence in the system’s capability to withstand turbulent conditions in production.”

Why is Chaos Engineering?

You learn how to fix the things that often break.
You don’t learn how to fix the things that rarely break.
If something hurts, do it more often! For today’s large scale distributed system, normally it is impossible to simulate all the cases (including the failure modes) in a nonproductive environment. Chaos engineering is about experimenting with continuous low level of breakage to make sure the system can handle the big things.

What can ChaosEngineeringDecorator help in your chaos engineering practice?

By ChaosEngineeringDecorator you can inject the failures (such as, slow response, error response) into the distributed system under control. This would help you to test and improve the resilience of your system. image

How to ChaosEngineeringDecorator?
Configurations

The following is the configuration about ChaosEngineeringDecorator.

{
	 "IsToInjectChaos" : true, // Is it to start chaos injection, if it is false, all chaos injects (chaos function, slow response) will be stopped
	 "AdditionalResponseTime" : 500, // Inject additional time spent (milseconds) to simulate slow response.
	 "ChaosRate" : 40 // The proportion of the chaos response, the range is 0-100
 }

The configuration is stored in the storage that can be accessed with "ConfigStorage" interface.

Inject Slow Response
// Example_ChaosEngineeringDecorator_InjectError is the example for slow response injection.
// To run the example, put the following configuration into Consul KV storage with the key "ChaosExample"
// {
//   "IsToInjectChaos" : true,
//   "AdditionalResponseTime" : 100, // response time will be increased 100ms
//   "ChaosRate" : 10
// }
func Example_ChaosEngineeringDecorator_InjectSlowResponse() {
	serviceFn := func(req Request) (Response, error) {
		return "Service is invoked", nil
	}
	ErrorInjectionFn := func(req Request) (Response, error) {
		return "Error Injection", errors.New("Failed to process.")
	}
	storage, err := CreateConsulConfigStorage(&api.Config{})
	if err != nil {
		fmt.Println("You might need to start Cousul server.", err)
		return
	}
	chaosDec, err := CreateChaosEngineeringDecorator(storage, "ChaosExample",
		ErrorInjectionFn, 10*time.Millisecond)
	if err != nil {
		fmt.Println("You might need to start Cousul server.", err)
		return
	}
	decFn := chaosDec.Decorate(serviceFn)
	for i := 0; i < 10; i++ {
		tStart := time.Now()
		ret, _ := decFn("")
		fmt.Printf("Output is %s. Time escaped: %f ms\n", ret,
			time.Since(tStart).Seconds()*1000)
	}

	//You have 10% probability to get slow response.

}
Inject Error Response
// Example_ChaosEngineeringDecorator_InjectError is the example for error injection.
// To run the example, put the following configuration into Consul KV storage with the key "ChaosExample"
// {
//   "IsToInjectChaos" : true,
//   "AdditionalResponseTime" : 0,
//   "ChaosRate" : 10
// }
func Example_ChaosEngineeringDecorator_InjectError() {
	serviceFn := func(req Request) (Response, error) {
		return "Service is invoked", nil
	}
	ErrorInjectionFn := func(req Request) (Response, error) {
		return "Error Injection", errors.New("Failed to process.")
	}
	storage, err := CreateConsulConfigStorage(&api.Config{})
	if err != nil {
		fmt.Println("You might need to start Cousul server.", err)
		return
	}
	chaosDec, err := CreateChaosEngineeringDecorator(storage, "ChaosExample",
		ErrorInjectionFn, 10*time.Millisecond)
	if err != nil {
		fmt.Println("You might need to start Cousul server.", err)
		return
	}
	decFn := chaosDec.Decorate(serviceFn)
	for i := 0; i < 10; i++ {
		ret, _ := decFn("")
		fmt.Printf("Output is %s\n", ret)
	}
	//You have 10% probability to get "Error Injection"
}

Documentation

Overview

Package service_decorators is to simplify your work on building microservices. The common functions for the microservices (such as, Circuit break, Rate limit, Metric...) have be encapsulated in the reusable components(decorators). To build a service is to decorate the core business logic with the common decorators, so you can only focus on the core business logic. @Auth chaocai2001@icloud.com @Created on 2018-6

Example
// Encapsulate the original function as service_decorators.serviceFunc method signature
type encapsulatedReq struct {
	a int
	b int
}

encapsulatedFn := func(req Request) (Response, error) {
	innerReq, ok := req.(encapsulatedReq)
	if !ok {
		return nil, errors.New("invalid parameters")
	}
	return originalFunction(innerReq.a, innerReq.b)
}

// Add the logics with decorators
// 1. Create the decorators
var (
	retryDec        *RetryDecorator
	circuitBreakDec *CircuitBreakDecorator
	metricDec       *MetricDecorator
	err             error
)

if retryDec, err = CreateRetryDecorator(3 /*max retry times*/, time.Second*1,
	time.Second*1, retriableChecker); err != nil {
	panic(err)
}

if circuitBreakDec, err = CreateCircuitBreakDecorator().
	WithTimeout(time.Millisecond * 100).
	WithMaxCurrentRequests(1000).
	Build(); err != nil {
	panic(err)
}

gmet := g_met.CreateGMetInstanceByDefault("g_met_config/gmet_config.xml")
if metricDec, err = CreateMetricDecorator(gmet).
	NeedsRecordingTimeSpent().Build(); err != nil {
	panic(err)
}

// 2. decorate the encapsulted function with decorators
// be careful of the order of the decorators
decFn := circuitBreakDec.Decorate(metricDec.Decorate(retryDec.Decorate(encapsulatedFn)))
ret, err := decFn(encapsulatedReq{1, 2})
fmt.Println(ret, err)
Output:

3 <nil>

Index

Examples

Constants

View Source
const (
	// TimeSpent is the metric item name of the time spent
	TimeSpent = "time_spent"
	// OccurredError is metric item name of the occurred error
	OccurredError = "occurred_error"
)

Variables

View Source
var ErrorBeyondRateLimit = errors.New("current request rate is beyond the limit")

ErrorBeyondRateLimit occurred when current request rate is beyond the limit

View Source
var ErrorCircuitBreakTimeout = errors.New("the invoking is timeout")

ErrorCircuitBreakTimeout happens when invoking is timeout

View Source
var ErrorCircuitBreakTooManyConcurrentRequests = errors.New("the concurrency is beyond the limit")

ErrorCircuitBreakTooManyConcurrentRequests happens when the number of the concurrent requests beyonds the setting

View Source
var ErrorRateLimitDecoratorConfig = errors.New("rate limit configuration is wrong")

ErrorRateLimitDecoratorConfig occurred when the configurations are invalid

Functions

This section is empty.

Types

type AdvancedCircuitBreakDecorator

type AdvancedCircuitBreakDecorator struct {
	ErrorCounter       int64
	ErrorDistinguisher ErrorDistinguisherFn

	ErrorFrequencyThreshold     int64
	LastError                   error
	ResetIntervalOfErrorCounter time.Duration
	BackendRetryInterval        time.Duration

	FallbackFn ServiceFallbackFunc
	// contains filtered or unexported fields
}

AdvancedCircuitBreakDecorator is the advanced CircuitBeakDecorator, which can support complex circuit break stragtegy. For circuitBreak states transition, please refer to https://github.com/easierway/service_decorators/blob/master/doc_pics/circuit_breaker_states_transtion.png

1. Failure frequency will cause circuit breaker state to open state -- Failure frequency involves ErrorCount and ResetIntervalOfErrorCount

They are used to count the errors occurred in the time window.

-- ErrorDistinguisher is to decide what kind of errors would be counted with ErrrorCounter. -- ErrorCounter is to count the continous occurred errors in the time window -- ResetIntervalOfErrorCount is the interval of resetting error counter to 0

  1. When circuit breaker is in open state, the requests will be processed by fallback function (FallbackFn)
  2. BackendRetryInterval is to check if the backend service is health/recovered. The time interval since the last time of backend service invoked is beyond BackendRetryInterval even the circuit breaker is in close state, current request will be passed to backend service. If the request be processed successfully or no counted errors happen (decided by ErrorDistinguisher) the circuit break will switch to close state

func CreateAdvancedCircuitBreakDecorator

func CreateAdvancedCircuitBreakDecorator(
	errorFrequencyThreshold int64,
	resetIntervalOfErrorCounter time.Duration,
	backendRetryInterval time.Duration,
	errorDistinguisher ErrorDistinguisherFn,
	fallbackFn ServiceFallbackFunc) *AdvancedCircuitBreakDecorator

func (*AdvancedCircuitBreakDecorator) Decorate

func (dec *AdvancedCircuitBreakDecorator) Decorate(innerFn ServiceFunc) ServiceFunc

type ChaosEngineeringConfig

type ChaosEngineeringConfig struct {
	IsToInjectChaos        bool `json:"IsToInjectChaos"`        //Is it to start chaos injection, if it is false, all chaos injects (chaos function, slow response) will be stopped.
	AdditionalResponseTime int  `json:"AdditionalResponseTime"` //Inject additional time spent to simulate slow response.
	ChaosRate              int  `json:"ChaosRate"`              //The proportion of the chaos response, the range is 0-100
}

ChaosEngineeringConfig is the configuration.

type ChaosEngineeringDecorator

type ChaosEngineeringDecorator struct {
	// contains filtered or unexported fields
}

ChaosEngineeringDecorator is to inject the failure for Chaos Engineering

func CreateChaosEngineeringDecorator

func CreateChaosEngineeringDecorator(configStorage ConfigStorage, configName string,
	chaosResponseFn ServiceFunc,
	refreshInterval time.Duration) (*ChaosEngineeringDecorator, error)

CreateChaosEngineeringDecorator is to create a CChaosEngineeringDecorator configStore: the storage is used to store the chaos configurations configName: the config name in the storage chaosResponseFn: the function is to inject the failure for chaos engineering

func (*ChaosEngineeringDecorator) Decorate

func (dec *ChaosEngineeringDecorator) Decorate(innerFn ServiceFunc) ServiceFunc

Decorate function is to add chaos engineering logic to the function

type CircuitBreakDecorator

type CircuitBreakDecorator struct {
	// CircuitBreakDecoratorConfig
	Config *CircuitBreakDecoratorConfig
	// contains filtered or unexported fields
}

CircuitBreakDecorator provides the circuit break, fallback, concurrency control

func (*CircuitBreakDecorator) Decorate

func (dec *CircuitBreakDecorator) Decorate(innerFn ServiceFunc) ServiceFunc

Decorate is to add the circuit break/concurrency control logic to the function

type CircuitBreakDecoratorConfig

type CircuitBreakDecoratorConfig struct {
	// contains filtered or unexported fields
}

CircuitBreakDecoratorConfig includes the settings of CircuitBreakDecorator

func CreateCircuitBreakDecorator

func CreateCircuitBreakDecorator() *CircuitBreakDecoratorConfig

CreateCircuitBreakDecorator is the helper method of creating CircuitBreakDecorator. The settings can be defined by WithXX method chain

func (*CircuitBreakDecoratorConfig) Build

Build will create CircuitBreakDecorator with the settings defined by WithXX method chain

func (*CircuitBreakDecoratorConfig) WithBeyondMaxConcurrencyFallbackFunction

func (config *CircuitBreakDecoratorConfig) WithBeyondMaxConcurrencyFallbackFunction(
	fallbackFn ServiceFallbackFunc) *CircuitBreakDecoratorConfig

WithBeyondMaxConcurrencyFallbackFunction sets the fallback method for beyonding max concurrency error

func (*CircuitBreakDecoratorConfig) WithMaxCurrentRequests

func (config *CircuitBreakDecoratorConfig) WithMaxCurrentRequests(maxCurReq int) *CircuitBreakDecoratorConfig

WithMaxCurrentRequests sets max concurrency

func (*CircuitBreakDecoratorConfig) WithTimeout

WithTimeout sets the method execution timeout

func (*CircuitBreakDecoratorConfig) WithTimeoutFallbackFunction

func (config *CircuitBreakDecoratorConfig) WithTimeoutFallbackFunction(
	fallbackFn ServiceFallbackFunc) *CircuitBreakDecoratorConfig

WithTimeoutFallbackFunction sets the fallback method for timeout error

type ConfigStorage

type ConfigStorage interface {
	Get(name string) ([]byte, error)
}

ConfigStorage is to store the configuration

type ConsulConfigStorage

type ConsulConfigStorage struct {
	// contains filtered or unexported fields
}

ConsulConfigStorage the configuration storage with Consul KV

func CreateConsulConfigStorage

func CreateConsulConfigStorage(consulConfig *api.Config) (*ConsulConfigStorage, error)

CreateConsulConfigStorage is to create a ConsulConfigStorage

func (*ConsulConfigStorage) Get

func (storage *ConsulConfigStorage) Get(name string) ([]byte, error)

Get is to get the configuration

func (*ConsulConfigStorage) Set

func (storage *ConsulConfigStorage) Set(name string, value []byte) error

Set is to set the configuration

type Decorator

type Decorator interface {
	// Decorate function is to introdoce decorator's the functions
	Decorate(ServiceFunc) ServiceFunc
}

Decorator is the interface of the decorators.

type ErrorClassifier

type ErrorClassifier func(err error) (string, bool)

ErrorClassifier is to decide the type/class of the error the return values are string: the type/class bool: if the error needs to be put into the metrics

type ErrorDistinguisherFn

type ErrorDistinguisherFn func(err error) bool

ErrorDistinguisherFn is to decide if the error should be counted.

type MetricDecorator

type MetricDecorator struct {
	// contains filtered or unexported fields
}

MetricDecorator is to introduce the metrics of the service. It is based on GMet (https://github.com/easierway/g_met)

func (*MetricDecorator) Decorate

func (dec *MetricDecorator) Decorate(innerFn ServiceFunc) ServiceFunc

Decorate is to add the metrics logic to the inner service function

type MetricDecoratorConfig

type MetricDecoratorConfig struct {
	// contains filtered or unexported fields
}

MetricDecoratorConfig is the configuration MetricDecorator

func CreateMetricDecorator

func CreateMetricDecorator(gmetInstance g_met.GMet) *MetricDecoratorConfig

CreateMetricDecorator is the helper method of creating CreateMetricDecorator instance. The settings can be defined by WithXX method chain

func (*MetricDecoratorConfig) Build

func (config *MetricDecoratorConfig) Build() (*MetricDecorator, error)

Build is to create a CreateMetricDecorator instance according to the settings

func (*MetricDecoratorConfig) NeedsRecordingTimeSpent

func (config *MetricDecoratorConfig) NeedsRecordingTimeSpent() *MetricDecoratorConfig

NeedsRecordingTimeSpent is to turn on the time spent metrics

func (*MetricDecoratorConfig) WithErrorClassifier

func (config *MetricDecoratorConfig) WithErrorClassifier(errClassifier ErrorClassifier) *MetricDecoratorConfig

WithErrorClassifier is to set the ErrorClassifier

type RateLimitDecorator

type RateLimitDecorator struct {
	// contains filtered or unexported fields
}

RateLimitDecorator provides the rate limit control RateLimitDecoratorConfig is the rate limit Configurations Rate = NumOfRequests / Interval

func CreateRateLimitDecorator

func CreateRateLimitDecorator(interval time.Duration, numOfReqs int, tokenBucketSize int) (*RateLimitDecorator, error)

CreateRateLimitDecorator is to create a RateLimitDecorator

func (*RateLimitDecorator) Decorate

func (dec *RateLimitDecorator) Decorate(innerFn ServiceFunc) ServiceFunc

Decorate function is to add request rate limit logic to the function

type Request

type Request interface{}

Request is the interface of the service request.

type Response

type Response interface{}

Response is the interface of the service response.

type RetryDecorator

type RetryDecorator struct {
	// contains filtered or unexported fields
}

RetryDecorator is to add the retry logic to the decorated method.

func CreateRetryDecorator

func CreateRetryDecorator(maxRetryTimes int, retryInterval time.Duration,
	intervalIncrement time.Duration,
	retriableChecker func(err error) bool) (*RetryDecorator, error)

CreateRetryDecorator is to create RetryDecorator according to the settings maxRetryTimes : max retry times retryInterval, intervalIncrement : the sleep time before next retrying is retryInterval + (retry times - 1) * intervalIncrement retriableChecker : the function to check wether the error is retriable

func (*RetryDecorator) Decorate

func (dec *RetryDecorator) Decorate(innerFn ServiceFunc) ServiceFunc

Decorator function is to add the retry logic to the decorated method

type ServiceFallbackFunc

type ServiceFallbackFunc func(req Request, err error) (Response, error)

ServiceFallbackFunc is the fallback function definition

type ServiceFunc

type ServiceFunc func(req Request) (Response, error)

ServiceFunc is the service function definition. To leverage the prebuilt decorators, the service function signature should follow it.

Jump to

Keyboard shortcuts

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