返回目录列表
重构设计
不能自动更新的问题
刚才发的那个帖子居然没有修改这个权限,好像目前还没人回复那个帖子吧,怎么自己的发的帖子都不能修改了呢
不但不能修改,在主题列表中的更新时间中,也无法发现最新回贴的信息,以及最新回帖的作者。
这个BUG是偶尔出现,只有上线在jdon.com运行时才会出现,正常功能测试和并发测试无法发现。
这种不可捉摸的问题一般都是设计思路引起的,也就是你的设计没有与领域中那个规律共振,所以,难免有不和谐的问题。
我也从并发线程锁等方面考虑,但是发现,其实最好最严格的对象封装设计才是线程最安全的,这个可以从下面理解:
这个顽固问题是每次回帖后,再主题列表中,看最新回复,没有得到更新反应,在JJ中,我设计师用ForumThreadState来封装更新信息的,现在问题就是,也许ForumThreadState没有更新,或者更新的ForumThreadState是另外一个对象。不过,对象不一致性已经通过缓存解决了,因为发表回帖时,首先要缓存中获得父贴,而父贴因为之前用户阅读了,肯定在缓存中,不会立即被更新,所以,新的回帖和父贴就公用一个ForumThread,所以,不存在不一致性,可能很小。
那么为什么ForumThread的ForumThreadState有时没有执行呢?代码中ForumThreadState有几个动作:读取TreeModel(用来维持内存中帖子的树形结构);从数据库SQL查询获得最新帖子然后将这个帖子赋予ForumThreadState的lastPost,问题可能就出在这两处:
首先,通过JProfiler发现“读取TreeModel”很费CPU,每次帖子更新,需要从数据库中重新收集树形结构信息,树形结构反复访问很耗数据库性能,为什么要重新刷新呢,难道不能就只是将最新帖子加入已经存在内存树形结构中,不就更好吗?不必要读取数据库了,内存中ForumThread其实有一份,思考到这里,我也很惭愧我潜意识还是数据库编程,完全忽视内存中ForumThread,总是将缓存或内存中对象看成是数据库的备份,而不是将数据库看成是内存中对象的备份,当我的思路转到后面上来时,就豁然开朗了。
这样,我在ForumThread增加AddNewMessage这样的方法,将当前回帖加入内存中ForumThread的TreeModel中,而不是每次回帖后,从数据库中查询后,再创建新的treeModel,这样,性能快多,也避免因为回帖动作刚刚写完数据库,事务过程有可能没有结束(事务通常较慢),数据库隔离级别是read_commit,再读取可能因为没有commit,不能从数据库读取到最新回帖,新的思路也避免了这种情况的存在。
当我思路回到围绕内存中 ForumThread 对象时,该主题中最新一个帖子也是直接在addNewMessage中通过setLastPost迅速直接完成,不必再到数据库按时间select最新帖子了,这样,ForumThread 中state对象都可以通过 ForumThread 的addNewMessage update 等方法完成,ForumThread 也成为一个标准的胖模型,丰富模型。
看到有人将saveToDB放入领域模型中,这是一种生搬硬套,因为saveToDB涉及服务和SQL,这些很重的单例资源是不能绑在领域模型中的,只有该领域模型做聚合根,对其内部各个子对象的封闭性操作方法才是最好的胖模型行为。
而且,这样的领域模型因为保存在缓存中,每个实例只有一个,在多用户访问同一个实例领域模型情况下,也符合并发计算中所谓安全发布,在多线程下,只有将自己的行为封装在自己对象内部,再通过synchronized等内部锁机制保证这个模型对象内部操作一致性和原子性,否则,你将这些本来属于内部操作的,如forumThread的addNewMessag搬迁到service或外部实现,如果上锁,保证写读的封闭性都是很难做到的。
以上,从一个顽固BUG修正,谈到围绕对象编程和围绕数据库编程两个思路导致的问题,以及领域胖模型 和并发线程安全,这些都是统一的,当你思路重点在对象上,你才会关注领域模型的质量,才会关注并发线程安全。
问题依然存在
经过上述设计重构,发现问题依然。
前面认为出现这个BUG有两个原因:
1.
回帖更新的ForumThread可能不是浏览时的ForumThread,前面排除这个原因,焦点还是转移到这里。
2.
回帖更新动作很费CPU,或者从数据库中读取最新贴这一过程过于重量,经过前面重构,通过丰富ForumThread行为,增加addNewMessage等方法来直接在内存中更新。
既然第2点已经做得很好,那么就是第一点还是有问题,也就是说回帖更新的ForumThread就不是浏览时的ForumThread。这个情况出现是因为Thread的生命周期掌握不对。
至于在哪里不对,也无法从代码中一一定位,因为生命周期问题不会出现在某个具体代码处,它是整个代码在运行时刻一个总体表现,只有从两个方面来解决这个问题:
1.
断点跟踪回帖更新流程,仔细核对流程中thread的生命周期状态。
2.
重新考虑Thread模型性质:是实体 还是值对象?
这两个方面考虑都得到进展:
在核对回帖流程中,会想到一种可能:thread平时都保存在cache中,但是cache会定时清除,这样thread被cache清除后,下次再访问thread时,必须重新创建一个新的thread对象;而thread又可能被Message引用,这些Message还在缓存中,没有清除,这样这些message中的thread就一直存在,这些thread就和重新创建的thread不是同一个对象,thread不是共享了,变成复制有多份,回帖更新只是其中一个thread。
重新考虑thread的模型性质,它实质是根message,根贴这样实体的代表,thread应该和RootMessage根贴生命周期是一致的。生命周期控制是在Factory中实现的,因此Factory代码中应该加入这两者一致的约束性,之前没有做到,这部分代码要重构。
Thread虽然是实体RootMessage代表,但是它又有值对象Value Object影子,thread一般不变的,只有更新时才会变化,这个变化并不违反DDD中关于值对象不变的约束。
值对象有两种被其他对象使用方式:复制和共享,复制是被推荐的方式,因为值对象是不变的,没有标识,当然可以使用FlyWeight大量复制,每个引用对象人手一份,虽然每个引用对象引用的值对象不是同一个实例,但是他们内容相同,而且在传统的分布式环境中复制很容易传播。
thread因为有实体影子,用户浏览主题列表时,需要查询thread集合,这些使用复制是都可以实现,关键是:用户浏览主题时,他想知道这个主题更新信息,最后回帖是谁等等,如果使用thread的复制,那么所有thread都要通知到,显然不切实际。所以,thread只能特殊使用共享。
一个thread实例被缓存后,可以被这个主题下所有帖子Message共享,当有新帖更新时,更新thread状态,所有帖子对象引用的是同一个thread,他们就都知道,当前这个主题中有更新,如果更新的根贴RootMessage,就用新的根贴替代thread中原来的。
所有重构都集中到thread的工厂中,工厂不但要保证thread和RootMessage是一致的;而且必须保证thread是单例被共享的。
更多Jdon框架专题讨论
JdonFramework作为一个免费开源软件开发平台,可以商用开发大多数数据库应用软件和管理软件: 电子商务软件 在线教育软件 税务软件 Web快速开发软件 财务软件 购物车软件 医院帐务软件 crm software medical software 人事薪资软件payroll software 在线购物软件 销售软件 项目管理软件 房产不动产管理软件 生产软件 PDM软件 制造业软件 仓库软件 采购软件 进销存软件 危险源监控软件 物流软件 超市软件 银行软件 保险软件 汽车软件 医疗软件 电子软件 自动化软件 服装软件 烟草软件 分销管理软件 供应商管理软件
|