大师级人物Martin Fowler的书,04年出版,现在看来有点老,在当时很是很溜的,尤其是其中的对象-关系部分,在后来的ormapping框架中,都能看到其中的影子。带着增长见识,扩展视野的目的阅读下。
分层
分层的理念提的非常好,即使放到现在,依然值得关注和思考,我们的开发,从最小的java的分package到分模块、分层、maven的不同模块的组织依赖,然后再划分业务、切分系统等等,都应该属于分层吧。
Martin 将应用主要分为3层:
1. 表现层;
2. 领域层;
3. 数据源层。
提出一条关于依赖性的普遍原则:领域层和数据源层不要依赖表现层。现在大部分系统都可以按照这种分层,也不会违背这条原则。
组织领域逻辑
领域逻辑组织的3种模式:
1. 事务脚本:从表现层获得输入到最后将数据存储到数据库的一条龙操作,一个过程控制一系列逻辑的执行;
2. 领域模型:不再是一个动作控制所有,按照面向对象的思维,由各个对象分摊各个逻辑和操作。领域逻辑做的越好,数据库映射就会越复杂;
3. 表模块:事务脚本和领域模型的折中方案,按照领域模型来说,所有表现层的输入到领域层,其基本逻辑都是不同对象之前的交互,而表模块则更多关注的表。对应数据中一个合同表,领域逻辑可能一个合同对应一个实例,而表模块合同表示成一个记录集,先查询出一个合集构成一个对象。
领域逻辑复杂度,作者给出了图示:
按照现在的调性,基本也都是领域模型了,其他2中大部分情况是不会再使用了。
Martin提出有时在领域层上再提出一层服务层,其目的很多是为了传递上层调用到底层,承上启下的作用,可以使用facede用来简化领域层逻辑。
我们项目中好像都是直接表现层-服务层-数据源层,服务层合并了领域层。
映射到关系数据库
- 将sql从领域逻辑中分离从来,每张库表对应一个类;
- 使用数据映射器让领域对象和数据库表完全独立,各自演进;
- 数据库读取加载关联对象,可以使用延迟加载;
- 读取数据时,性能问题会变得突出,几条经验教训:
- 一次读取多行,避免重复查询;
- 使用join,一次查询多表。
- 表结构关系的映射,持有标识,延迟加载;
- 通过继承关系相互关联的类层次:单表继承、具体表继承、类表继承:
- 单表继承:为一个层次中所有类建立一个表,所有超类和接口建立一个大表;
- 具体表继承:为一个层次中每个具体类建立一个表,每一个具体表包含所有接口和超类;
- 类表继承:为一个层次中每个类建立一个表,为每一个超类和接口建立一个表。
- 利用元数据,使用代码生成;
- 数据库连接,使用连接池。
这本书写的最好的就是ormapping部分。
表现层
- MVC模式;
- 视图模式:
- 转换视图;
- 模板视图;
- 两步视图。
- 输入控制器模式:
- 每个页面对应一个控制器:最常见,我们一般都会用的,处理输入和页面转换;
- 每个动作对应一个控制器:这个牛,我们有个系统的单页面设计就用这个,之前都没想到可以这样做,同一个动作到控制器,然后转到不同的处理逻辑。
并发、会话状态、分布式
并发
并发这个话题,写本书都不为过,能讲的东西太多。这里Martin主要提的数据库事务并发。并发问题关注的焦点:
1. 更新丢失;
2. 不一致读。
共享资源的访问导致了并发的产生,解决方案:
1. 隔离;
2. 不变性。
2种并发控制策略:
1. 乐观锁:版本号或时间戳方式;
2. 悲观锁。
2者选择的标准是:冲突的频率与严重性。如果冲突冲突较少或者后果不会太严重,通常选择乐观锁,可以得到更好的并发性。
悲观锁容易引起死锁,可以通过超时控制和double-check检测防止死锁。
事务ACID。
事务的隔离级别:
对于并发这块,Martin大神提的不多,主要还是提了下数据库事务、乐观悲观锁这些,对于分布式事务和跨系统系统基本没说,而这2个估计是现在分布式要处理的最重要的东西了。
会话状态
有状态还是无状态会话。为了提高并发,现在流行的应该是无状态会话吧。
会话的存储:
1. 客户端:使用cookie,大小有限制,会丢失,不安全;
2. 服务端:session;
3. 数据库会话:将会话存储到数据库,这个应该很少这么做吧。
现在通常采用服务器端会话,session集群统一存储缓存,带超时控制。
分布策略
这里我觉得应该讲的是rpc远程调用吧,但是翻译看起来有点那个。这里Martin不建议使用远程调用,能不用就不用,跟现在主流不一样啊,必须用的时候,大神也建议使用粗粒度的外观。
为细粒度对象提供粗粒度的外观来改进网络上的效率。
还比较了其他分层架构的模式,大多是在Martin3层架构的基础上根据实际情况增加层次,如在表现层和领域层间增加一层控制层、在领域层和数据源层增加数据映射层这些,大多类同。
领域逻辑模式
事务脚本
使用过程来组织义务逻辑,每个过程对应表现层的单个请求。这种现在基本不会用。
领域模型
合并了行为和数据的领域的对象模型。
简单领域模型看起来和数据库设计很类似,每一张数据库表对应一个领域对象,而复杂领域模型则是使用各种设计模式,是一张有互联的细粒度对象组成的复杂网络。这里的复杂领域模型我的理解是不考虑数据库设计,而是完全的面向对象设计,这样会导致下层数据映射会很复杂。
表模块模型
处理某一数据库表或视图所有行的业务逻辑的一个实例。
我感觉这里讲的又有点像简单领域模型了,有点混淆,最后还是没纠结这个细节了。
服务层
通过服务层定义系统的边界,在服务层中建立一组可用的操作集合,并在操作内部调用系统的响应。
不知道该说翻译不好还是什么的,理解起来费劲。明显是想在领域层之上定义一层面向表现层具体应用,具体场景的开放API接口,说的那么纠结,真tm难过。
我们现在的处理方式就是直接合并服务层跟领域层,如果需要开放外部接口,再定义一层接口服务层。
数据源架构模式
表数据入口
充当数据库表访问入口的对象,一个实例处理表中所有的行。大多数使用表数据入口的时候,对数据库的每一张表使用一个入口。
行数据入口
充当数据源单条记录入口的对象。每行一个实例。
活动记录
一个对象,包装数据库表或试图中某一行,封装数据库访问,并在这些数据上增加了领域逻辑。这样就会类似领域逻辑了。
虽然这种很面向对象,但是我们系统基本不用。
数据映射器
在保持对象和数据库彼此独立的情况下,在二者之间移动数据的一个映射器层。这种就是数据库表对应的对象和领域逻辑中领域对象之间存在一层映射关系。
我们现在用的就是这种,有DMO(Data Mapping Object)或DPO(Data Persitence Object)类对应数据库表,层间使用DTO(Data Transfer Object)来传输业务对象,DTO和DMO间存在映射关系。
ormapping
Martin在书的11、12、13章花了大量篇幅讲解了ormapping,这些东西在现在ormapping框架都有体现。不太感兴趣,只是大概翻了下,悲剧。
基本模式
入口
入口是一个封装外部系统或资源访问的对象。我觉得这样做的好处就是便于测试。
映射器
两个独立的对象之间建立通信的对象。其实就是2个独立系统不愿意相互依赖,这样就可以在一个第三方的系统来维持依赖,或者让一个系统成为另一个系统的观察者,使用观察者模式。常见的是数据映射器。
层超类型
不知道为什么提出这个模式,这个只是把一些公用方法提出到父类中。
分离接口
在一个包中定义接口,在另一个包中实现这个接口。这个有时候系统复杂时,需要采用这种方式,更好的分离接口和实现。
注册表
一个众所周知的对象,其他对象可以通过该对象找到公共的对象和服务。感觉就像常量类,定义所有通用变量和配置。
值对象、货币
一个如货币或日期范围这样的小而简单的对象,判等时并不根据标识ID。没感觉这2个模式什么意思,如果只是判等,大不了重写判等方法呗,而对于货币,java有BigDecimal处理,估计其他语言也应该有相应的处理类。
特殊情况
针对特殊情况提供特殊行为的子类。我们在系统中有很多地方都使用null判断返回,有时候你都不知道返回null究竟是空值还是系统处理异常导致null。使用有意义的特殊类,比使用空值更好。
插件
在配置时而非编译时连接类。这个应该是想让系统支持热部署吧。有时候需要根据环境或配置使用不同的实现,当然你可以使用工厂,但是工厂一多就会感觉很乱。推荐SPI,dubbo中有这个,的确不错。
服务桩
在测试时移除对有问题服务的依赖。这个应该配合入口模式使用,主要是调用第三方服务的时候,使用服务桩,便于测试。
记录集
表格数据在内存中的表现方式。这个应该不会使用了,现在应该是使用数据映射和代码生成了。
现在
有些漏掉或很简单,但是现在很重要的东西:
1. 分布式事务;
2. rpc;
3. session统一管理;
4. 并发讲的太少,但是对于现在的应用很重要;
5. 监控,尤其是全链路监控,即使我们公司是很大的公司,都还没做好,但是个人感觉对于现在的互联网应用太重要了;
6. 缓存系统,估计出书的时候,还不太流行;
7. 3H(可用、可靠、可扩展)。
总体来说,这本书虽老,可看,不过不用深究,写的最好的地方是ormapping部分,感兴趣可深究。