头图

Go 函数式编程:Higher-order function

在请求处理过程中,应用程序会接受和处理请求,然后返回响应结果。在该过程中,还存在一些通用的功能,例如:鉴权、监控、链路追踪。众多 RPC 框架会提供称之为 Middleware 或者 Interceptor 等概念,以可插拔的方式来支持上述谈到的众多功能。以 gRPC 为例,工作原理如图:

grpc-interceptors.png

其服务端的接口如下所示:

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(高阶函数)

高阶函数是指至少支持以下特定之一的函数:

  1. 将一个或多个函数作为参数(即过程参数),
  2. 返回函数作为其结果

第二点,正是需要的特性。以限流的 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。

最后

做个总结,谈个观点:

  1. 高阶函数,并不是属于哪一个特定的编程语言。其他语言如C++,同样支持类似的特性。
  2. 作为架构师需要了解实现细节么,答案是需要。否则,在特定环境下,拿什么来支撑设计所谓的扩展性呢。

本文作者:cyningsun
本文地址https://www.cyningsun.com/07-...
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-ND 3.0 CN 许可协议。转载请注明出处!


12 声望
5 粉丝
0 条评论
推荐阅读
如何快速定位现网 BUG
“幸福的家庭都是相似的,不幸的家庭却各有各的不幸”,托尔斯泰的名言。在 BUG 定位这件事情上,其实也有类似的现象:”菜鸟们的紧张无措都是相似的,老鸟们的方法却各有各的不同”。

有疑说阅读 1k

封面图
「刷起来」Go必看的进阶面试题详解
逃逸分析是Go语言中的一项重要优化技术,可以帮助程序减少内存分配和垃圾回收的开销,从而提高程序的性能。下面是一道涉及逃逸分析的面试题及其详解。

王中阳Go4阅读 1.9k评论 1

封面图
初学后端,如何做好表结构设计?
这篇文章介绍了设计数据库表结构应该考虑的4个方面,还有优雅设计的6个原则,举了一个例子分享了我的设计思路,为了提高性能我们也要从多方面考虑缓存问题。

王中阳Go4阅读 1.7k评论 2

封面图
滚蛋吧,正则表达式!
你是不是也有这样的操作,比如你需要使用「电子邮箱正则表达式」,首先想到的就是直接百度上搜索一个,然后采用 CV 大法神奇地接入到你的代码中?

良许4阅读 2.3k

又一款眼前一亮的Linux终端工具!
今天给大家介绍一款最近发现的功能十分强大,颜值非常高的一款终端工具。这个神器我是在其他公众号文章上看到的,但他们都没把它的强大之处介绍明白,所以我自己体验一波后,再向大家分享自己的体验。

良许5阅读 1.8k

一分钟搞明白!快速掌握 Go WebAssembly
最近因为各种奇怪的原因,更多的接触到了 WebAssembly。虽然之前很多博客也翻过写过各种文章,但总感觉欠些味道。于是今天梳理了一版,和大家一起展开学习。

煎鱼4阅读 2.2k

面试官:请说一下如何优化结构体的性能?
使用内存对齐机制优化结构体性能,妙啊!前言之前分享过2篇结构体文章:10秒改struct性能直接提升15%,产品姐姐都夸我好棒 和 Go语言空结构体这3种妙用,你知道吗? 得到了大家的好评。这篇继续分享进阶内容:结...

王中阳Go4阅读 3.8k评论 2

封面图
12 声望
5 粉丝
宣传栏