如何通过流式编程打造围绕数据而非API的架构革新?

摘要:背景 在服务刚刚搭建时,通常的思维就是根据API编写业务逻辑:SendStream ... func (d *Svc) SendStream(stream MyApi_data.ProxyDialOut_SendStreamServ
背景 在服务刚刚搭建时,通常的思维就是根据API编写业务逻辑: // SendStream ... func (d *Svc) SendStream(stream MyApi_data.ProxyDialOut_SendStreamServer) error { for { ... data, err := stream.Recv() if err != nil { logrus.Errorf("recv error:%v", err) return err } ... // 对data做相关的操作 } } 在服务暴露出越来越多的API后,相似的操作会越来越多。此时会进行抽象和封装,提取公共操作,例如提取函数、建立工厂等。 比如,在已有的API中添加监控统计。虽然对统计器做了抽象(对象或者函数),但可能仍然需要侵入到所有不同的API实现中。 // SendStream ... func (d *MyApiSvc) SendStream(stream MyApi_data.ProxyDialOut_SendStreamServer) error { for { ... data, err := stream.Recv() if err != nil { logrus.Errorf("recv error:%v", err) return err } ... // 对data做相关的操作 ... // 添加一个共享的监控统计器,调用上报业务,每个api都需要改动 counter.Add("MyApi", 1) } } 这在简单项目中无可厚非,但长此以往,随着各种功能的加入,API的业务代码会迅速臃肿起来。 后续,会发现每个API都各不相同,却又有公共部分。所以不得不写出大量形容相似的代码。这在部门大部分项目中都屡见不鲜。 究其原因,这是因为抽象层次不够造成的。 摒除以API为中心的编程模式 在网络编程中,一般会引入中间件(比如trpc的filter)来处理共有逻辑,比如鉴权,日志,panic处理等。 但中间件一般太过于抽象并不直观,使得编写调试不易。但它的思路值得借鉴。 在对业务进行思考后,突发奇想。虽然对客户端(用户)而言,每个API都是服务(消费者)。但对于具体处理而言,每个API同时也是生产者。 将每个API看成data source,生产数据(data),就是对api最底层的抽象。 在这里,引入一个简单的流式编程包go-streams(github.com/reugn/go-streams),方便快速建立流式编程的架构。
阅读全文