头图

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 定位这件事情上,其实也有类似的现象:”菜鸟们的紧张无措都是相似的,老鸟们的方法却各有各的不同”。

有疑说阅读 964

封面图
Golang 中 []byte 与 string 转换
string 类型和 []byte 类型是我们编程时最常使用到的数据结构。本文将探讨两者之间的转换方式,通过分析它们之间的内在联系来拨开迷雾。

机器铃砍菜刀24阅读 58.4k评论 2

万字详解,吃透 MongoDB!
MongoDB 是一个基于 分布式文件存储 的开源 NoSQL 数据库系统,由 C++ 编写的。MongoDB 提供了 面向文档 的存储方式,操作起来比较简单和容易,支持“无模式”的数据建模,可以存储比较复杂的数据类型,是一款非常...

JavaGuide8阅读 1.7k

封面图
数据结构与算法:二分查找
一、常见数据结构简单数据结构(必须理解和掌握)有序数据结构:栈、队列、链表。有序数据结构省空间(储存空间小)无序数据结构:集合、字典、散列表,无序数据结构省时间(读取时间快)复杂数据结构树、 堆图二...

白鲸鱼9阅读 5.4k

PHP转Go实践:xjson解析神器「开源工具集」
我和劲仔都是PHP转Go,身边越来越多做PHP的朋友也逐渐在用Go进行重构,重构过程中,会发现php的json解析操作(系列化与反序列化)是真的香,弱类型语言的各种隐式类型转换,很大程度的减低了程序的复杂度。

王中阳Go11阅读 2.7k评论 4

封面图
Git操作不规范,战友提刀来相见!
年终奖都没了,还要扣我绩效,门都没有,哈哈。这波骚Git操作我也是第一次用,担心闪了腰,所以不仅做了备份,也做了笔记,分享给大家。问题描述小A和我在同时开发一个功能模块,他在优化之前的代码逻辑,我在开...

王中阳Go6阅读 2.9k评论 4

封面图
妙啊,空结构体还能这么用?Go语言的结构体看这篇就够了
本文详解了Go语言结构体的各个知识点,最后介绍了空结构体的3种妙用。希望对你有帮助。定义结构体,是一种自定义的数据类型,由多个数据类型组合而成。用于描述一类事物相关属性。定义方式: {代码...} 实例化结...

王中阳Go6阅读 1.2k

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