[Golang]如何优雅的管理几十个UDF(API)

一根薯条 · · 787 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

组内一个服务中有个叫算子的模块,所谓算子可以理解为UDF(User Defined Function),这个模块的核心思想是:在做业务需求时,把业务拆解为几块通用的业务代码(UDF),不同的代码块承担不同的业务功能。这些代码块提供出不同的配置项(或者叫“函数签名”),用户传入对应的参数调用这块代码。

这样做的好处是:后续接业务需求时只需要通过编排算子配置就可以复用通用算子。业务不复杂时,单个算子即可支持业务;业务复杂时,通过多个算子组合为pipeline支持业务。

编个🌰,现在有个业务需求:用户下单后需要统计用户当天完单量,并给下游发送用户的单量消息,下游营销系统根据用户的完单量给用户推送不同的优惠策略。

对整个业务流程做一下抽象:

image.png

这里编排schema和转发MQ都是不涉及存储的纯计算型业务需求。现在把这两个功能抽象成算子:

第一个算子为"编排schema",功能是把各种参数编排成一个string格式的字符串并返回。其配置schema可能长这样:

{
    "calc_param": ["user_id", "finish_cnt", "time_stamp"],
    "calc_options": {
        "template": "{\"user_id\":\"{{user_id}}\",\"finish_cnt\":\"{{finish_cnt}}\",\"time_stamp\":\"{{time_stamp}}\"}"
    }
}

第二个算子为"发送MQ",功能是把输入的string格式的字符串作为生产者发送到一个MQ中。其配置schema可能长这样:

{
    "calc_param": ["last_calc_string_res"],
    "calc_options": {
        "topic_name": "an_awesome_topic"
    }
}

把这两个算子组成一个json list,作为一个业务配置,服务读取配置就实现业务需求了,整个过程中不用写一行代码。

理想很丰满,实现很骨感。在实际开发迭代中,由于团队扩张、人员流动、代码注释缺失的问题,算子模块出现了三个问题:

  1. 有人不知道系统中有哪些通用算子
  2. 通用算子使用成本较高(测试文件(如果有) or 看算子源码)
  3. 通用算子维护成本较高(写完代码之后需要写wiki,更新完代码之后需要改wiki)

这两个问题导致的结果是:有的需求可以通过一些通用算子的配置组合支持业务,但是在不熟悉的情况下有的人会选择写一坨"定制算子(全是具体的业务逻辑)"去支持需求。

这么做倒是无可厚非,相比于纯写配置一行代码都不开发而言,一天写300行业务代码支持一个业务可能更会带来成就感,且上线之后也不出Bug就完事了呗,条条大路通罗马嘛;)

但这样做有点不符合组内系统的核心思想,还是应该去建设更加通用的算子,当通用算子积累到一定程度,并以一种合理的方式被管理起来,那最终的业务收益就可能变成这样:RD在平台上通过拖拉拽组合通用算子就可以支持业务,在系统建设足够完善的情况下,一人一天支持100个业务需求都不在话下;)

所以现在就有一个不太痛的痛点:需要把算子模块管理起来。如果在基于一开始的设想:

算子 == UDF

那完全可以学习编程语言管理UDF的方式对算子模块进行管理,具体的解决方案为:

痛点 方案
新同事不知道系统中有哪些通用算子 对通用算子打上不同的分类标签:比如上面的 编排schema 算子可以打上"字符串操作" tag;发送MQ 算子 可以打上 "MQ操作"、"外部rpc" tag
通用算子使用成本过高 搞一个算子平台,提供REPL的能力供用户使用算子
通用算子维护成本过高 问题发生的本质原因是:代码与wiki编写过程是剥离的,那就把这两个过程放在一起,把wiki放在代码里,并写一些元编程的代码生成这份wiki。

实际上这个算子模块管理系统是必须的:在系统刚开始迭代时,系统里可能只有几个通用算子,这时你的使用成本很低,这就好比你随时记得你喜欢的Go语言里有json.Unmarshaljson.Marshalhttp.ListenAndServestrings.Split这几个系统函数。

但是随着业务的发展,你的系统也会迭代,最终你的代码里可能有几十个、上百个UDF,如果不把算子按照类型管理起来,你就需要随时记忆这么多的UDF,这就好比 你可能并不记得你喜欢的Go语言的strings包里还有一个叫做EqualFold的系统函数,即使你有一个模糊的印象,那你使用时候也得去看看wiki里是怎么写的。

对于如何把代码编写wiki维护这两个割裂的步骤放在一起,在下有一些不成熟的想法。在这里写一种思路:

对于每一种段子,都抽象出其配置schema和参数schema,所谓的schema在Golang中即结构体,我们在结构体中写多种tag记录各个属性的元信息,并通过反射把这些元信息同步到DB中,元信息落库后,就可以和前端同学合作建立酷炫的管理平台管理算子了。

Go的反射及其不好用,这里简单写一些反射代码,把上文那两个算子配置读取结构体元信息这块表示一下:

package main

import (
    "fmt"
    "reflect"
)

type MQOption struct {
    TopicName string `json:"topic_name" comment:"消息队列名称"`
}

func getStructTag(v interface{}, tag string) {
    structVal := reflect.ValueOf(v).Elem()
    structType := structVal.Type()
    fmt.Println(structType, structVal)

    for i := 0; i < structVal.NumField(); i++ {
        fieldType := structType.Field(i)
        fmt.Println("fieldType", fieldType)
        field := structVal.FieldByName(fieldType.Name)
        fmt.Println("field", field)

        comment := fieldType.Tag.Get(tag)
        fmt.Println("tag", comment)
    }
}

func main() {
    mo := new(MQOption)
    getStructTag(mo, "comment")
}

// ------------ output ------------
//      main.MQOption {}
//      fieldType {TopicName  string json:"topic_name" comment:"消息队列名称" 0 [0] false}
//      field
//      tag 消息队列名称
// ------------ output ------------

上面这块代码只是玩具,不建议用于生产环境,如果对这块有兴趣,可以去github上找开源项目。

欢迎关注我的公众号:薯条的自我修养


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:一根薯条

查看原文:[Golang]如何优雅的管理几十个UDF(API)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

787 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传