谈谈应用层切面设计

jopen 8年前

AOP概要理解纠偏

说到AOP,这个东东现在已经不是一个新词汇了,我们拿一下百度词条来看看是下面的样子的:

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

但是今天我要说,这个定义太过狭义,实际上AOP的概念要比上面的定义要宽泛得多,今天我就给大家来扯扯AOP的事儿。

从AOP这三个单词来看,它的定义还是非常准确的,就是面向切面编程。但是后面的这一堆解释:

通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

这明显是鬼扯了,明显是把一种具体的实现作为它定义,这就有点“白马即马”的意思了。

主要的变化就是把中间一段与具体的实现有关系的部分去掉。

通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。

这一段太过狭隘,已经远远不能满足软件技术发展的需要了,面向切面就面向切面,与什么预编译的方式有毛关系?与运行期代理有毛关系?与Spring、函数式编程有毛关系?

因此,今天悠然就把AOP的概念重新缕缕:

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过对某一领域进行高内聚低偶合方式进行实现,并通过非偶合的方式与其它业务逻辑进行整合,以对不同切面的实现逻辑进行隔离,从而使得不同领域的实现逻辑之间的耦合度降低,提高程序的可重用性、提高开发效率,同时也大大降低集成与整合难度的一种编程方法。

嗯嗯,虽然不一定严谨,但是比原来的广泛多了,也更能体现AOP的真正意涵。它不仅包含原有定义里的模式,还包含各种各样其它AOP模式,今天悠然就来给大家试着阐述一下。

AOP在软件编程中的应用场景

传统意义上的AOP

传统意义上的AOP,是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,来获得逻辑过程中各部分之间低耦合性的一些隔离效率,比如:常见的应用场景有:

  1. Authentication 权限
  2. Caching缓存
  3. Context passing内容传递
  4. Error handling 错误处理
  5. Lazy loading 延时加载
  6. Debugging 调试
  7. logging, tracing, profiling and monitoring 记录跟踪 优化 校准
  8. Performance optimization性能优化
  9. Persistence 持久化
  10. Resource pooling资源池
  11. Synchronization 同步
  12. Transactions事务
  13. 全文索引
  14. 等等

确实,AOP的应用场景有许多许多,也取得了良好的应用效果,这个已经经过了充分的实践,许多书里也都进行了充分的阐述,这里不再赘述。

非传统意义上的AOP

嗯嗯,今天的重点是这里,别人说得好的地方悠然说不过,因此最好的办法就是不说,悠然说别人不说的地方,这样就压力小多了:)

以下针对@红薯 的猜测纯属臆断,如有雷同纯属巧合:

在正式开始之前,我们来举个实践场景:

<h2>@红薯 的开源中国博客1.0 </h2>

话说,在开源中国的博客列表中,每一篇博客是像下面一样展示的:

从这一块内容中,我们可以看到如下内容:

  1. 原创的图标
  2. 置顶的图标
  3. 博客的标题
  4. 博客的分类
  5. 博客的统计信息30评、2594阅、7赞
  6. 博客的编辑、删除

我们从看到的这些信息里面,可以猜一下@红薯 的表结果是怎么设计的:

表BLOG结构:

  • 博客标识
  • 博客标题
  • 博客标题内容
  • 是否原创
  • 是否置顶
  • 评论次数
  • 阅读次数
  • 获赞次数
  • ....

当然这里应该也有一些其他的字段,我们上面的图片里看不到,我们就当它们不存在。

我们再想想,估计@红薯 还有详细的记录,比如:谁评论的,评论的内容是什么?谁阅读的,是什么时候阅读的?谁点赞的,是什么时候点赞的

于是就有了下面的表:

    评论详情:

  • 博客标识
  • 评论者
  • 评论时间
  • 评论内容
  • ...

    阅读详情:

  • 博客标识
  • 阅读者
  • 阅读时间

    点赞详情:

  • 博客标识
  • 点赞者
  • 点赞时间

嗯嗯,@红薯 仔细想了一想感觉差不多了的样子,这个时候@红薯 缕着他的头发,点上一支烟进入了写代码的酸爽状态。

比如,功能是这样写的:

//博客点赞    保存点赞(博客标识,点赞者标识){      博客biz.添加点赞(博客标识,点赞者标识);  }    class 博客biz{       添加点赞(博客标识,点赞者标识){            博客dao.增加点赞数(博客标识);            点赞详情dao.增加点赞详情(博客标识,点赞者标识);       }  }

伴随着一阵阵欢快的噼里啪啦键盘声,开源中国博客1.0 OK了。

<h2>@红薯 的开源中国新闻1.0 </h2> 看着开源中国博客1.0的稳步运行,PV+UV双丰收,@红薯 缕着头发咧着大板牙笑开了花,那就再整个开源中国新闻1.0呗!

于是红薯开始设计表结构:

表NEWS结构:

  • 新闻标识
  • 新闻标题
  • 新闻标题内容
  • 是否置顶
  • 评论次数
  • 阅读次数
  • 获赞次数
  • ....

当然,考虑到新闻也有点赞,也有评语,也有阅读,于是下面的表结构也是需要的:

    评论详情:

  • 新闻标识
  • 评论者
  • 评论时间
  • 评论内容
  • ...

    阅读详情:

  • 新闻标识
  • 阅读者
  • 阅读时间

    点赞详情:

  • 新闻标识
  • 点赞者
  • 点赞时间

嗯嗯,红薯看了下,尼玛除了少个原创,别的都差不多的样子么,但是不一样还是不一样的,那就再做一遍吧,于是一阵阵噼里啪啦之后又搞定了,红薯抹着汗水,再仔细看一下代码,尼玛,怎么这两边的代码几乎一样?感觉哪里有些不对的样子。

管呢,先运行起来再说,哥去泡个妹子放松一下,于是红薯打开了MAC,IPAD,IPHONE7S....

初级AOP方式

运行了一段时间,红薯觉得要加个动态功能,发现要在两边都增加代码,越来越觉得不对劲,于是拿起新买的《企业级JavaEE架构实践》翻看起来,唉当时看书的时候不仔细,这个用切面来解决不是挺好的?

于是红薯把表结构重构了一下:

    评论详情:

  • 评论类型
  • 评论对象标识
  • 评论者
  • 评论时间
  • 评论内容
  • ...

    阅读详情:

  • 阅读类型
  • 阅读对象标识
  • 阅读者
  • 阅读时间

    点赞详情:

  • 点赞类型
  • 点赞对象标识
  • 点赞者
  • 点赞时间

另外增加一个动态表

  • 动态类型
  • 动态对象标识
  • 动态者
  • 动态时间
于是红薯把原来的代码重构了一下,把原来重复做了两遍的的评论、阅读、点赞都抽到了一个里面,然后把原来在业务代码里调用相关评论、阅读、点赞的业务逻辑都抽到了切面当中。嗯嗯,红薯感觉棒极了,脑海中又出现了妹子的身影,对了,IPHONE8P哪里去了?

高级AOP方式

自从引入了切面功能,红薯的工作明显轻松多了,但是唯一不爽的是 随着开源中国的用户越来越多,投资人要求功能方面也要快速推进,现在的红薯几乎已经没有可以缕的头发了,投资人要增加日历、要增加勋章、要增加TAG、要增加提醒,要增加的东西越来越多,同时开源中国的访问者们的眼界又越来越宽了,要求越来越高了,今天这个做的丑了,明天那个做得不人性化了, 红薯开发、测试、发布、切面配置、界面调整、控制层啥都要做,这不又出问题了:


红薯的手习惯的抬起来缕头发了,但是随着OSC功能的增加,头发已经几乎找不到了,红薯狠的把烟屁股拧进烟灰缸,是要彻底解决这个问题了,看起来还得读书啊,再翻企业级JavaEE架构实践,看看有没有啥思路。

Think Big, Start small , Scale Fast.

这是什么鬼?
好的软件是品出来的。
品个屁,现在解决问题才是正点,能不能让我不要每次都改这么多东西?再翻翻目录,看看有啥值得研究的?

仔细看了几遍,红薯的眼睛忽然亮了起来,只要把所有的评论、点赞、评论、动态、以及其它杂7杂8的东西都让它高内聚,低耦合,我做博客只管做博客、做新闻只管做新闻,哪怕再做他多少个,也是仅仅关注我要做的东西,而把这些东西都做成一个个的切面让这些切面去完整的解决这些问题,这样我不就轻松了?原来虽然也引入了AOP技术,但是由于只是初级切面,所以只解决了部分问题,并没有彻底的解决问题。如果我能把切面也切到控制层、界面层,这样就可以真正的做到高内聚、松耦合,而且是真正的业务切面。

技术的切面还是初级的,业务层级的切面才是终级的。

具体实现起来,还是没有什么思路,听听悠然有没有什么思路?

终级解决方案

通过上面对开源中国两个功能模块的分析,我们大致清楚了问题的症结所在,并通过对红薯的心路历程进行分析,大致引入了问题的解决思路,但是真正的落地确不是那么简单的。一个问题的抽象的程度越高,其通用性越好,但是它的易理解程度和易实现程度也就越难。我对实现者面对面多次沟通这个思路时,更多的时候看到的是他们茫然的眼神,以及多次感觉理解了,但是真正实现的时候却又达不到要求,直到许多次反复之后,才慢慢进入状态。

所以我会尽力讲清楚,但是不能保证你一定能理解到我内心所想。

要解决的问题

我们在应用开发过程中,一部分是与具体的业务相关的,另外一部分它与具体的业务并不相关,但是在业务过程中又是有这样那样关系的。如果我们能把这些与具体业务没有直接关系,但又是对业务数据的一种维度的补充或描述能独立出来,想用或不想用仅在一念间,那么这个时候做软件就是一种享受了,而且业务和切面都可以独立变化,而不必考虑彼此的影响。

这个时候从开发效率来说,由于大家各自高内聚低耦合,所以开发、测试、验证、发布都会方便快速许多,集成的时候由于也不必有配置和代码及界面上的协作和引用,这样就可以大大的降低系统集成过程中的开发、测试工作,也避免了由此导致的一系列的可能出现的问题。

那么接下来,我们就用上面分析问题时的例子来阐述,如何解决这个问题,整个实现思路采用Tiny框架相关技术进行解决,非TINY技术平台或框架也是可以实现的,当然实现细节可能会有不同,需要相关的架构师进行相应的调整。

解决问题的思路

每个切面的内容都在自己的业务单元中实现,同时要能在能出来的时候它就能出来。

这里我们就拿上面说的一个场景"评论"来示例

   评论详情:

  • 评论类型
  • 评论对象标识
  • 评论者
  • 评论时间
  • 评论内容
围绕着这个,当可以可以知道它的DAO层代码怎么写,逻辑层代码怎么写(如果是SOA体系还有服务层代码怎么写),最后还有就是界面层的部分。

既然是高内聚低耦合,那当然主是要统一界面的样式。

评价的使用场景一般来说是在显示完主要业务内容之后(如:博客、新闻、文档、软件、etc),可以进行评论,可以进行回复,可以进行引入,当然也要能把已经评论的内容显示出来。

但是需要搞清楚的是,这些内容的展现与人机交互过程明显与不同的业务内容没有一点关系,这也就为进行切面提供了基础。为了便于进行说明问题,这里采用AJAX模式进行解释,虽然采用非AJAX方式也是可以解释得通的,但是应用到的技术的复杂度与解释起来的困难都非常更加复杂。

举个例子,做出来的效果是类似下面的样子:

这个时候,问题就转换为,如何把当前要处理的业务类型和业务数据的标识让评论部分知晓,这个也非常简单,只要用下面的样子写一段模板即可:

#comment("BLOG",blog.blogId)
上面的模板语言是指:现在我是在给一个BLOG类型的业务对象做评论,业务对象的标识是blog对象的blogId属性。

嗯嗯,这个时候较以前的开发,已经省了非常大的工作量了,只是简单的声明一下就可以了。

更进一步的思考

虽然上面的思路已经比较好的解决了问题,但是这里有一个问题:那就是实现时序问题。

也就是说,博客功能是红薯先加的,而评论功能是红薯后加的。这里就出现一个问题:为什么后增加的功能,需要我在已经实现的妥妥的功能要进行调整??这岂不是说,你后面不断的增加功能,我这里都要不断的进行声明?虽然也有一定的道理,但是是不符合好莱坞原则的。实际上也是违反常理的===我是先来的,为什么要为你一个后到的做调整?我根本就不知道你的存在好么?!

出于我们对完美架构的追求以前我们不将就的态度,我们当然要更好的解决此问题。

我们可以在应用当中预设一些扩展点,但是具体扩展些什么东西我是不管的。

这个时候,我们就可以只在界面中增加如下的一段模板语言:

#extendPoint("VIEW","BLOG",blog.blogId)

上面的脚本表示,我这里是一个VIEW类型的扩展点,业务类型是BLOG,业务对象标识是blog对象的blogId属性。

然后由extendPoint决定往里面塞哪些东西。

这个时候,我只要在评论工程里增加如下的配置:

<extend-points>      <extend-point type="page" order="10">          #comment(bizType,bizId)      </extend-point>  </extend-points>
然后如果这个时候,我们又要增加一个留脚印的功能

我们只要增加一个留脚本的功能模块,在这个模块中增加下面的配置:

<extend-points>      <extend-point type="page" order="9">          #footprint(bizType,bizId)      </extend-point>  </extend-points>

总结

通过上面的改进,红薯同学再也不用薅头发了,原有的业务代码再也不用动,这功能居然自动在所有功能中自动出现了。

想到这里,悠然就深深的自责,早点给红薯出主意,红薯同学的一头黑发就留下来了。

当然,上面只是说了一个思想,真正的实现的时候用到的技术还是非常多的:
  • 模块化
  • AOP
  • SOA(如果是互联网应用是需要的,普通的小工程可以不用)
  • 模板语言或类似技术

如果本人没有说清楚,或者读者朋友们没有理解,本人深感自责,也希望同学们能够理解,并在下面的回复博客提问,本人定尽力给出解释。

有喜欢本人博客内容的同学请加关注,以便及时获知本人精品内容!

来自: http://my.oschina.net/tinyframework/blog/604644