这是从一个小服务器里碰到的小问题提取出来的小模式。
要解决的问题
在上一个项目里,我们发现系统里的数据流动有这两种模式:
- 业务逻辑 -> 客户端
- 业务逻辑 -> 其他业务逻辑 -> ...
说白了就是,一个业务逻辑需要有面向客户端和面向其他业务 逻辑的两套接口。在数据入口处,我们已经做了处理,把客户端过 来的数据和其他业务逻辑过来的数据统一起来了:
1 2 3 4 5 |
|
这是因为所有的协议数据都是同构的,而且来源相同(都来自网络层), 要实现“Protocol Decoder”很简单;但是在数据出口处,有下面 两个原因导致我们没办法也对出去的数据做统一处理:
- 我们用
gen_server
处理请求,直接向客户端回复的数据没必要用 同步call,让业务逻辑所在的进程直接发送有助于提高系统的并发 性能,所以在具体模块的实现上就必须区分是向客户端回复还是向 其他业务逻辑回复:客户端的数据用cast,其他业务逻辑的数据用 call; - 业务逻辑之间的数据,来源和结构都不一致,在用
gen_server
的 情况下比较难处理得好。
所以我们所有的旧代码都是在业务逻辑中写死了数据流向,而没有对出 去的数据做routing,即便是相同的逻辑,也要提供两个接口,一个面 向客户端,一个面向其他业务逻辑。
解决思路
分析一下不难看出,现在是业务逻辑对数据流向有很强的依赖,那应该 可以把业务逻辑和数据流向分开来,业务逻辑只提供数据,至于这些数 据应该去哪,由别的规则确定。这个方案最简单的实现就是,由调用者 (也就是数据入口处)决定数据出去时的流向,因为数据入口处实际上 只有两种情况:从网络来的请求和从其他业务逻辑来的请求:
1 2 3 4 5 6 7 8 9 |
|
gen_cb
gen_cb
就是把数据流向交给调用者决定的实现。原理很简单,调用
者的每个请求都要提供两个回调函数,一个由本地代码执行,一个由
处理请求的进程执行,通过两个回调的配合,可以把具体请求调整为
异步或者同步请求,所以实现gen_cb
的模块不需要考虑同步异步之
类的事情。
同步调用:
1 |
|
异步调用:
1 |
|
当然,由于实现方式很灵活,gen_cb
能干的不止是同步/异步的调
整,你还可以把各种回调串起来,为某个模块添加各种filter和hook……
限制
在Erlang节点之间传递函数变量是有点危险的事情,因为需要保证节
点之间的代码是一致的,所以不鼓励在分布式环境里用gen_cb
,除
非你真的知道自己在干什么。
另外在实现细节里,ge_cb
传递的Replier回调(见代码)会
有一块闭包,所以gen_cb
的消息要比gen_server
大上不少。
代码
托管在Github上,可以当作rebar
依赖直接用。
TODO
添加测试;添加休眠(hibernation)代码切换(code change)支持;添加license.