An example of current limiting Middleware
func TestRateClientLimit(t *testing.T) { b := ratelimit.NewBucketWithRate(float64(limit), int64(limit)) //... c := client.NewClient( // set the selector client.Selector(s), // add the breaker wrapper client.Wrap(NewClientWrapper(b, false)), ) //... }
- Create Middleware - NewBucketWithRate()
- Convert to Wrapper - NewClientWrapper(NewBucketWithRate())
- Convert to Option - Wrap(NewClientWrapper(NewBucketWithRate()))
- Convert to Options and create client - newclient (client. Wrap (newclientwrapper (newbucketwithrate()))
Create Middleware
b := ratelimit.NewBucketWithRate(float64(limit), int64(limit))
Middleware to Wrapper
type Wrapper func(Client) Client type clientWrapper struct { fn func() error client.Client } func limit(b *ratelimit.Bucket, wait bool, errId string) func() error { return func() error { if wait { time.Sleep(b.Take(1)) } else if b.TakeAvailable(1) == 0 { return errors.New(errId, "too many request", 429) } return nil } } func NewClientWrapper(b *ratelimit.Bucket, wait bool) client.Wrapper { fn := limit(b, wait, "go.micro.client") return func(c client.Client) client.Client { return &clientWrapper{fn, c} } }
The return value of limit() is a function of type func() error, which is a middleware for current limiting.
The input parameter and return value of NewClientWrapper() are both of client.Wrapper type, which is a wrapper function (decorator mode). Add a function type field to the input type object (in this case, flow limiting Middleware), and then return it.
Wrapper to Option
type Options struct { //... Wrappers []Wrapper //... } type Option func(*Options) func Wrap(w Wrapper) Option { return func(o *Options) { o.Wrappers = append(o.Wrappers, w) } }
The key is to understand Option, which is function type func(*Options).
The Wrap() function converts the Wrapper type to Option type. Here Option is a closure, which has a local variable W. which Option it calls will add this w to the [] Wrapper slice of Options. This is equivalent to setting Options with this Option. This may be the reason why it is called Option.
Create Client with Option and wrap Client with Wrapper
func NewClient(opt ...Option) Client { return newRpcClient(opt...) } func newRpcClient(opt ...Option) Client { opts := newOptions(opt...) // Creating Options with Option rc := &rpcClient{ // Creating rpcClient with Options once: sync.Once{}, opts: opts, pool: newPool(opts.PoolSize, opts.PoolTTL), seq: 0, } c := Client(rc) //rpcClient to Client for i := len(opts.Wrappers); i > 0; i-- { c = opts.Wrappers[i-1](c) //Wrapper wrapper Clinet } return c } func newOptions(options ...Option) Options { opts := Options{ //New Optoins //... } for _, o := range options { o(&opts) //Set Options with Option } //... return opts }
The call chain is as follows
client.NewClient(opt ...Option) Client client.newRpcClient(opt ...Option) Client client.newOptions(options ...Option) Options rc := &rpcClient{ opts: opts, } c := Client(rc) c = opts.Wrappers[i-1](c)
Options go through the call chain of Client. Newclient() - > Client. Newrpcclient() - > Client. Newoptions(), create options in newOption(), set options with the Option passed in, return Options. newRpcClient() to get options, use it to create a Client, and finally wrap the Clinet with the Wrapper in options.
If there are multiple wrappers, the Client will be packaged layer by layer to form a middleware onion model.
+-------------------------------------+ | middleware1 | | +---------------------------+ | | | middleware2 | | | | +-----------------+ | | | | | middleware3 | | | | | | +---------+ | | | | | | | | | | | | | | | Client | | | | | | | +---------+ | | | | | +-----------------+ | | | +---------------------------+ | +-------------------------------------+