网易考拉规则引擎平台架构设计与实践

背景
考拉安全部技术这块目前主要负责两块业务:一个是内审 , 主要是通过敏感日志管理平台搜集考拉所有后台系统的操作日志 , 数据导入到es后 , 结合storm进行实时计算 , 主要有行为查询、数据监控、事件追溯、风险大盘等功能;一个是业务风控 , 主要是下单、支付、优惠券、红包、签到等行为的风险控制 , 对抗的风险行为包括黄牛刷单、恶意占用库存、机器领券、撸羊毛等 。这两块业务其实有一个共通点 , 就是有大量需要进行规则决策的场景 , 比如内审中需要进行实时监控 , 当同一个人在一天时间内的导出操作超过多少次后进行告警 , 当登录时不是常用地登录并且设备指纹不是该账号使用过的设备指纹时告警 。而在业务风控中需要使用到规则决策的场景更多 , 由于涉及规则的保密性 , 这里就不展开了 。总之 , 基于这个出发点 , 安全部决定开发出一个通用的规则引擎平台 , 来满足以上场景 。
写在前面
在给出整体架构前 , 想跟大家聊聊关于架构的一些想法 。目前架构上的分层设计思想已经深入人心 , 大家都知道要分成controller,server,dao等 , 是因为我们刚接触到编码的时候 , mvc的模型已经大行其道 , 早期的jsp里面包含大量业务代码逻辑的方式已经基本绝迹 。但是这并不是一种面向对象的思考方式 , 而往往我们是以一种面向过程的思维去编程 。举个简单例子 , 我们要实现一个网银账户之间转账的需求 , 往往会是下面这种实现方式:

  1. 设计一个账户交易服务接口AccountingService , 设计一个服务方法transfer() , 并提供一个具体实现类AccountingServiceImpl , 所有账户交易业务的业务逻辑都置于该服务类中 。
  2. 提供一个AccountInfo和一个Account , 前者是一个用于与展示层交换账户数据的账户数据传输对象 , 后者是一个账户实体(相当于一个EntityBean) , 这两个对象都是普通的JAVABean , 具有相关属性和简单的get/set方法 。
  3. 然后在transfer方法中 , 首先获取A账户的余额 , 判断是否大于转账的金额 , 如果大于则扣减A账户的余额 , 并增加对应的金额到B账户 。
这种设计在需求简单的情况下看上去没啥问题 , 但是当需求变得复杂后 , 会导致代码变得越来越难以维护 , 整个架构也会变的腐烂 。比如现在需要增加账户的信用等级 , 不同等级的账户每笔转账的最大金额不同 , 那么我们就需要在service里面加上这个逻辑 。后来又需要记录转账明细 , 我们又需要在service里面增加相应的代码逻辑 。最后service代码会由于需求的不断变化变得越来越长 , 最终变成别人眼中的“祖传代码” 。导致这个问题的根源 , 我认为就是我们使用的是一种面向过程的编程思想 。那么如何去解决这种问题呢?主要还是思维方式上需要改变 , 我们需要一种真正的面向对象的思维方式 。比如一个“人” , 除了有id、姓名、性别这些属性外 , 还应该有“走路”、“吃饭”等这些行为 , 这些行为是天然属于“人”这个实体的 , 而我们定义的bean都是一种“失血模型” , 只有get/set等简单方法 , 所有的行为逻辑全部上升到了service层 , 这就导致了service层过于臃肿 , 并且很难复用已有的逻辑 , 最后形成了各个service之间错综复杂的关联关系 , 在做服务拆分的时候 , 很难划清业务边界 , 导致服务化进程陷入泥潭 。
对应上面的问题 , 我们可以在Account这个实体中加入本应该就属于这个实体的行为 , 比如借记、贷记、转账等 。每一笔转账都对应着一笔交易明细 , 我们根据交易明细可以计算出账户的余额 , 这个是一个潜在的业务规则 , 这种业务规则都需要交由实体本身来维护 。另外新增账户信用实体 , 提供账户单笔转账的最大金额计算逻辑 。这样我们就把原本全部在service里面的逻辑划入到不同的负责相关职责的“领域对象”当中了 , service的逻辑变得非常清楚明了 , 想实现A给B转账 , 直接获取A实体 , 然后调用A实体中的转账方法即可 。service将不再关注转账的细节 , 只负责将相关的实体组织起来 , 完成复杂的业务逻辑处理 。


推荐阅读