在请求处理过程中,应用程序会接受和处理请求,然后返回响应结果。在该过程中,还存在一些通用的功能,例如:鉴权、监控、链路追踪。众多 RPC 框架会提供称之为 Middleware 或者 Interceptor 等概念,以可插拔的方式来支持上述谈到的众多功能。以 gRPC 为例,工作原理如图:
其服务端的接口如下所示:
func UnaryServerInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error)
func StreamServerInterceptor (srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error
可以看到,接口明确定义了输入参数,输出结果。如果我们要自己实现一个组件,需要支持使用者传入特定的配置,有没有什么办法可以做到呢?
答案是肯定的。
Higher-order function
在了解具体的解决方案之前,需要先了解一个概念叫Higher-order function(高阶函数)
高阶函数是指至少支持以下特定之一的函数:
- 将一个或多个函数作为参数(即过程参数),
- 返回函数作为其结果
第二点,正是需要的特性。以限流的 interceptor 为例,支持传入自定义的限流器。此时就需要定义一个以限流器为参数的高阶函数,然后返回的函数是框架需要的 Interceptor,并在 Interceptor 函数内使用传入的限流器判断是否需要限流。按照接口限流的 Interceptor 具体实现为:
type Limiter interface {
Limit(key string) bool
}
func UnaryServerInterceptor(limiter Limiter) grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
if limiter.Limit(info.FullMethod) {
return nil, status.Errorf(codes.ResourceExhausted, "%s is rejected by grpc_ratelimit middleware, please retry later.", info.FullMethod)
}
return handler(ctx, req)
}
}
...
目前传入的参数是固定的,可以这么来实现。更进一步,如果使用比较复杂,除了当前已经确定的参数,可以预期以后会增加更多的参数。也就要求当前设计的接口需要有很好的扩展性。还有办法么?
答案同样是肯定的。
Functional Options
没有意外,利用的就是高阶函数的第一点,该编程模式有一个特定的名称:Functional Options。
首先为传入的参数定义结构体
type options struct {
byMethod bool
byUser bool
byClientIP bool
}
其次,再定义一个函数类型:
type Option func(*Options)
再次,定义修改配置的一组函数
func ByMethod(m bool) Option {
return func(o *options) {
o.byMethod = m
}
}
func ByUser(u bool) Option {
return func(o *options) {
o.byUser = u
}
}
func ByClientIP(c bool) Option {
return func(o *options) {
o.byClientIP = c
}
}
最后,修改提供的 Interceptor 为:
func UnaryServerInterceptor(limiter Limiter, opts ...Option) grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
default := options {
byMethod: true,
byUser: false,
byClientIP: false,
}
for _, opt := range opts {
opt(&default)
}
...
return handler(ctx, req)
}
}
如是,你就拥有了一个具有扩展性、支持自定义参数的 Interceptor。
最后
做个总结,谈个观点:
- 高阶函数,并不是属于哪一个特定的编程语言。其他语言如C++,同样支持类似的特性。
- 作为架构师需要了解实现细节么,答案是需要。否则,在特定环境下,拿什么来支撑设计所谓的扩展性呢。
本文作者 : cyningsun
本文地址 : https://www.cyningsun.com/07-19-2021/go-higher-order-function.html
版权声明 :本博客所有文章除特别声明外,均采用 CC BY-NC-ND 3.0 CN 许可协议。转载请注明出处!