BT

如何利用碎片时间提升技术认知与能力? 点击获取答案

采用模式和泛型技术为应用增加策略控制(静态部分)

| 作者 王翔 关注 0 他的粉丝 发布于 2007年4月13日. 估计阅读时间: 18 分钟 | Google、Facebook、Pinterest、阿里、腾讯 等顶尖技术团队的上百个可供参考的架构实例!

前言

随着大量分布式计算和SOA类型应用的加入,企业内部具体应用的服务功能也趋于多元化,为了快速实现不断变化的业务需求、充分利用团队开发资源,很多架构师在高层技术设计阶段往往会在应用架构中集成一些公共库,完成诸如数据访问、日志记录、异常管理、授权控制等常规技术实现,从逻辑视图看他们是一组伴随业务逻辑部分的辅助技术内容:



图1:常用逻辑控制辅助机制布局

但是就一个具有规模化信息系统的企业环境而言,常常需要对应用实施很多全局性的技术处理,范围可能涉及整个应用层面或整个组织层面。例如:每个企业、每个行业内部总会有一些主要业务数据,这些数据又分散在企业不同的业务系统中——生产、财务、风险分析和决策支持等,但是出于安全的考虑可能要对这类数据在不同系统的处理过程进行使用安全审计,这时候处理上就有了两个方式:

  • 逐个修改现有功能公共库,把审计功能嵌入到每个功能库之中。
  • 纵向提供一个统一的组织级审计策略控制机制,每个公共库调用之。
  • 采用前者可以在短时间内快速实施改造,但如果这类策略变化相对频繁,而且又增加很多不同数据控制要求(例如:对于高价商品、低诚信度客户进行报警),那么反复改造带来的开发、测试、重新部署成本相对就比较可观,究其原因就是由于每个应用内部的个例处理对象与整体处置要求对象间耦合过于紧密。因此,本文试图通过增加一个策略控制框架来用尽可能小的代价集中解决这类问题,设计目标如下:

  • 自身结构要灵活,可以动态的适应多种策略要求。
  • 以配置为中心,便于测试和部署人员根据需要以Plug & Play的方式修改和维护策略。
  • 同时对业务调用的前期和后期提供可以回调的响应机制。
  • 定义多种策略匹配规则,确保规则可以按需被公共库的不同实现层次组成调用(程序集、类和接口、方法)。
  • 确保本地调用和跨进程调用的界面一致性。
  • 基于策略的控制主要包括两个方面:对操作实体执行逻辑部分的影响和对业务信息内容的影响。前者需要嵌入到其他操作实体的方法内部,因此为了尽量减少对它们的影响,要设计成一个单一的接口,而且接口所约定的控制方法也要尽量唯一;后者则要同时考虑在操作方法实施前后对业务信息内容增加不确定数量的预处理(Pre Process)和后续处理(Post Process)。另外,有时候策略控制的影响还会涉及到对象的构造过程(Constructor),为了屏蔽相关构造影响细节,可以采用一个工厂(Factory)割裂相关依赖。出于说明方便的考虑,下文笔者通过示例为一个简化的多通道日志系统穿插纵向的策略控制,务求从给读者展现一个明晰的梗概实现视图。

    注:说明中为了抽象和进一步扩展的考虑,所有类图的定义尽量采用抽象的接口和抽象类描述,如果说明上提及“某某抽象类实例”请理解为“该抽象类某个实体子类的实例”,提到“某某接口实例”时也请理解为“该接口实现类的实例”。)

    初始示例日志系统

    下面是示例系统的抽象初始静态结构:



    图2:示例日志系统静态布局

    说明:

  • 为了隔离客户程序与日志系统的耦合,增加了接口ISource负责定义客户程序记录日志的抽象操作。
  • 抽象类LogEntityBase描述了基本日志实体内容,考虑到分布式系统环境下日志集中收集的要求,相关的串行化工作、时间戳登记等操作也都在该抽象基类完成。
  • ISource经由LogEntityBase与抽象类FilterManagerBase关联,而FilterManagerBase主要管理一组IFilter对象,根据每个具体IFilter类型的命中规则对ISource提交的LogEntityBase进行过滤规则的匹配。
  • 接口IPublisher则是对筛选后的LogEntityBase通过IRender进行一些实际写入日志持久层前进行一些发布处理的,例如:按照表格方式展开包括Dictionary内容的数据、按照层次型的XML格式展开日志消息内容的描述部分、对某些复杂消息项目进行串行化。
  • 整理好的LogEntityBase最后提交给LogWriter实体类,它根据预先维护好的ITraceListener列表逐个发送日志消息,然后由每个ITraceListener完成具体写入持久层的操作。
  • 在设计整个策略控制之前,先设计好一个灵活且很易于扩展的Policy对象非常重要,毕竟“九层之台,起于垒土”,因此本文首先花大力气设计好一个具体的策略对象,然后为了适应企业应用环境的需要,在此基础上还会增加必要的辅助机制。整体系统的设计将沿着这样一个主线展开:



    图3:策略控制系统的展开步骤

    最终的策略系统也将由如下几个部分组成:



    图4:策略系统的整体逻辑结构

    第一个策略(Policy)对象

    Policy对象简单设计

    这其中主要涉及两方面问题,一个是策略的适用问题,由于策略一般影响的是目标实例的操作,因此“策略适用”主要解决的也是策略对象如何作用于目标实例的相关方法(Method、Property)的问题,一个是策略适用后的响应问题。前者需要增加一个MatchRuleCollection用于管理所有的适用规则,后者需要定义一组回调(Callback)句柄,目的主要是为了为了给策略控制框架提供必要的通知响应机制,下图就是一个抽象策略对象的静态结构。



    图5:抽象Policy类型定义

    说明:

  • PolicyBase被定义为抽象类,一方面说明它仅仅描述了一个策略对象所应具有的基本抽象特征,另一方面也是为每个具体策略对象提供扩展上的便利。主要属性包括一个名称(Name)、一组与之相关的匹配规则(MatchRuleSet)、一组指向对象实例的回调句柄(Callbase)和一个标示策略有效期的结构体属性(PeriodOfValidity)。同时将策略的执行过程定义为一个序方法(Virtual Method)以供策略实体类覆盖其执行过程。
  • 这里引入策略有效期主要为了便于给策略控制的实施过程提供预定式、预警式控制方式,毕竟很多策略的生效时间都在一些公休日期(元旦、五一、十一、某个月初等),生效时间也都是零点,因此考虑到运行维护人员工作时间的安排增加这些可预定内容比较必要。
  • Callback保存了一组委托(Delegate),通过这种对象化的函数指针定义PolicyBase各个响应操作的引用。
  • MatchRuleSet则是管理了一组IMatchRule的集合类型,负责遍历每一个IMatchRule以确定当前的业务操作方法或者业务数据是否满足命中条件。不过抽象考虑到每个MatchRule可能还会出现可能的依赖、嵌套关系,为了屏蔽每部组织结构,MatchRuleSet还需要借用Iterator模式隔离内部具体IMatchRule组织布局。考虑到效率并充分利用.Net语言特点的情况,实现一个Iterator模式相对很容易,可以采用IEnumerable< IMatchRule> + yield return IMatchRule的方式。
  • 图6:PolicyBase匹配规则的Iterator模式实现

    业务化语言的匹配规则设计

    从某个角度看,策略对象对于匹配规则的解析能力几乎决定了整个策略控制体系的智能性、决定了它与业务的吻合度,抽象来看匹配规则MatchRule的功能如下:

    图7:匹配规则MatchRule的功能

    从上面的说明可以看出匹配规则本身具有一定的组合特性,即Context、Data、Logic和Type本身可能通过组合形成一个统一的PolicyBase,同时为了隔离MatchRule管理能力与每个具体匹配规则分析能力,还需要引用一个额外的规则解析部分IRuleParser。



    图8:匹配规则解析部分的静态结构

    说明:

  • 每个抽象规则对象IMatchRule本身通过Composite模式组合在一起,使得其可以同时适应Context、Data、Logic、Type及其组合的规则定义情况。
  • 由于对于每个具体领域的规则都会有很多不同,因此为了隔离规则解析部分与实际领域规则领域信息的区别,这里增加了一个RuleParserProxy,也就是通过Proxy来间接获得各个领域规则分析。
  • 考虑到不同企业都有自身不同的规则语言、不同的计算要求,因此需要为IRuleParser增加一个扶助的具体规则函数解析对象IFunction,IFunction与IRuleParser最大的不同就是IFunction计算的结果是不定的,例如:数学计算结果可能是double、逻辑计算的结果是bool、字符串计算的结果为int或string,而IRuleParser的结果在本文的上下文中主要就是bool,也就是匹配规则判断后是否匹配这个结果。
  • 考虑到企业内部不同项目的建设次序不同,因此技术表示上对于同一个业务信息项目有所不同(例如:税号可能被称为 TaxID、Tax_ID、SH),所以为了避免上层策略逻辑部分对于规则解析的耦合性问题,增加了一个ISynonym用于协助解决有关同义词处理问题。
  • 规则匹配部分的性能考虑

    对一个执行过程阶段而言,策略与规则的匹配关系是相对固定的,同时每个匹配关系从其持久介质到内存实例化后本身也是相对固定的。如果每次执行过程反复进行解析会浪费过多的处理器计算和内存空间,因此这里会增加策略类型与匹配规则可用示例的匹配关系缓冲。如上文所说为了避免PolicyBase与具体IMatchRule的一一对应,因此还需要增加一个MatchRuleSet与IMatchRule对应关系的缓冲。



    图9:为策略对象匹配关系关联增加的缓冲机制

    说明:

  • 缓冲的加入一方面大大减少了每个PolicyBase相对固定部分的创建工作,同时通过这种Flyweight模式的应用,最大可能地减少了IMatchRuleSet(及其各个组成实体)对于内存的占用。

    图10:每个PolicyBase类型中相对固定的部分

  • PolicyBase与MatchRuleSet的对应关系由一个PolicyMathRuleSetCacheRecord对象保存,他们按照PolicyName和MatchRuleName两个联合关键字保存在PolicyMatchRuleSetCache中。后续PolicyBase创建前先从PolicyMatchRuleSetCache检索是否已有缓冲好的MatchRuleSetRecordset对象,并把相关的List解析结构直接引用,否则作为第一个创建者生成对应结构后现保存在Cache中,然后也适用Cache中既有的这个缓冲对象。这里PolicyMatchRuleSetCache被设计为一个静态类(static class),它本身以Flyweight的方式向所有PolicyBase实例提供既有缓冲记录的实例。
  • 需要说明的是这里PolicyBase + MatchRuleSet与PolicyMathRuleSetCacheRecord的对应关系是双Key的字典关系,所以这里增加了一个Helper类型——DualKeyDictionary,它本身继承自Dictionary,只不过内部增加了一个K1 + K2的合并然后映射到K的操作(例如:为了确保唯一性,可以采用K1 + K2字符串关联后散列处理的结果作为K)。
  • MatchRuleCache则是另一个静态缓冲类型,它主要保存的是每个具体解析后的IMatchRule,因为MatchRuleSet与IMatchRule本身是M :N的关系,也就是一个IMatchRule本身可以属于多个MatchRuleSet,同时它也可能和其他一些IMatchRule一起组成一个MatchRuleSet,因此除了让各个MatchRuleSet可以重用既有的IMatchRule增加了这个缓冲。
  • 响应机制的设计

    响应机制是PolicyBase的后续动作阶段,本身是一组委托(Delegate)的集合,而每个委托指派的既有可能是策略控制系统内部的方法也可能是外部控制逻辑的方法,加之每个方法的参数列表、返回值类型、甚至于每个参数的封送(Marshal)模式(按值封送Marshal by Value / 引用封送Marshal by Reference)也有很大差异,因此本身响应机制要设计为“响应模式层”和“响应交互层”两个层次,这两个层次的关系类似于TCP / IP协议栈中IP层与TCP层的关系:

  • “响应模式层”主要管理每个委托的执行调度过程;
  • 而“响应交互层”则负责完成与每个方法实际交互的过程。
  • 响应交互层

    交互层的对象描述要最终和可能的方法描述一致,因此设计上最终的类型系统要归到反射(System.Reflection)类型对象系统上,相关映射关系如下表:

    图11:响应类型与反射类型的最终映射关系

    同时抽象来看,一个方法的调用过程就是定位到一个MethodBase,然后向其传入需要的参数集合,最后获得返回结果的过程,因此响应交互层的静态结构如下:

    图12:响应交互层静态结构

    说明:

  • IParameterInfoCollection本身继承自接口IList ,为了使用方便,它本身通过索引器(Indexer)按照参数名称对外提供ParameterInfo类型的实例。
  • IMethodReturn接口用于描述一个调用的返回结构,包括三个部分,其中Return表示参数的返回值,ReturnContext包括了调用中生成的一些上下文信息,可以把异常也保存在这里,OutParameters则是考虑到.Net语言提供纯Output类型的参数,而这个参数本身又不包括在调用方法内部(与引用ref型不同),所以IMethodReturn部分增加了一个OutParameters结构。
  • 上层IMethodCaller接口负责按照“准备参数”、“准备上下文”、“调用方法”、“从IMethodReturn获取调用结果”的次序依据反射直接与一个实际的方法目标进行交互,交互过程被抽象为一个统一的Invoke方法。
  • 响应模式层

    在完成了响应机制底层结构的设计之后,就要从调用模式层面设计响应机制与策略类型的动态执行过程。为了避免PolicyBase具体管理每个响应机制的执行过程,这里采用链式方式让响应机制自动完成一系列联动的相应调用,也就是应用Chain of Responsibility(职责链)模式的过程,因此这里需要修改一下IMethodCaller的Invoke方法,增加一个指向下一个IMethodCaller实例的引用。



    图13:响应部分的链式调用机制(数据结构逻辑视图)

    图14:响应部分的链式调用机制(静态结构)

    策略对象说明

    至此,已经完成了整个策略系统的基石——PolicyBase的设计,通过上面的介绍相信各位读者已经对于最初的静态结构有了一个相对直观的认识。


    作者简介:王翔,全国海关信息中心高级架构师,从事海关主要广域分布式系统的设计和实施,多次参与各业务系统的优化。此外,作为信息安全工作组副组长,他还一直致力于应用密码技术和公钥基础设施保障海关业务的安全运行。此外,他还是《程序员》杂志的专栏作者。

    编辑注:感谢百合网技术总监刘如鸿对本文的技术审校。

    评价本文

    专业度
    风格

    您好,朋友!

    您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

    获得来自InfoQ的更多体验。

    告诉我们您的想法

    允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

    当有人回复此评论时请E-mail通知我
    社区评论

    允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

    当有人回复此评论时请E-mail通知我

    允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

    当有人回复此评论时请E-mail通知我

    讨论

    登陆InfoQ,与你最关心的话题互动。


    找回密码....

    Follow

    关注你最喜爱的话题和作者

    快速浏览网站内你所感兴趣话题的精选内容。

    Like

    内容自由定制

    选择想要阅读的主题和喜爱的作者定制自己的新闻源。

    Notifications

    获取更新

    设置通知机制以获取内容更新对您而言是否重要

    BT