众多的设计书籍都推荐用分层结构,这也几乎是框架设计者的共识了。然而层是分
了,具体分几层,还是一个值得决策的问题。我最熟悉的《责任驱动设计》和《领域驱
动设计》都推荐用四层,而且两者的吻合度相当高,我受这两本书的影响也最大,所以
很自然我的框架也分四层。
哪四层?表现层、应用层、领域层和技术层。
浏览器、PHP和数据库是比较容易区分出来的物理上的三个层次。然而这只是物理
分层,跟逻辑分层还是有区别的。目前Ajax非常流行,而且很多应用也确实需要Ajax来
提供富交互功能,所以支持Ajax是相当自然的一件事。在物理层面上,Ajax属于浏览器
层。实际上我对在浏览器端维护一个领域模型并不感兴趣,原因是难以在两者间保持同
步,也无谓地增加了复杂性。我完全可以直接在Action里处理,然后直接用PHP内置的
JSON系列函数直接输出数据供客户端JavaScript进行eval调用。用JavaScript Helper
来生成Ajax,目前我还没试过,具体是否支持,还要到时看情况而定。另外在资源定位
方面,我觉得REST的思想不错,不过目前还不是很熟悉REST,所以这一块打算暂时先放
一放,到时再重构以适应REST。
回到PHP这一物理层面上来。上面所说的四层架构,就是在PHP这一物理层次。
先说表现层。PHP对表现层的支持非常灵活非常方便,因为PHP最初就是用来做表现
层的。关于页面的组织,我的方法是把一个HTML页面分成两类元素,一类是布局,另一
类是模板。跟现有的一些框架不一样的是,我的模板没有再细分为其它元素,如slot之
类。因为没必要。一个布局对应一个有HTML输出的Action,同时一个布局还对应多个模
板。注意,布局跟Action是一一对应的,但模板不同,模板是完全灵活的,在某个时候
它可以嵌入某个布局,在另一个时候可以嵌入另一个布局。这样一来,模板就成了完全
可以复用的单元,而且经过我的处理,每个模板可以在树结构上从树叶一直往上移到树
根,以在多个Action、多个Module乃至多个App间的布局里共享。如果你熟悉JavaScript
的对象绑定,你就很容易理解一个对象做为另一个对象的某个属性这一过程跟我的模板
对应布局这一过程非常相似,都是动态绑定的。
表现层由Action调用,而Action则处于应用层,多个Action组成一个Module,多个
Module组成一个Application(这一点参考自symfony,实际上在没有接触过symfony前
我就想到过这种组织方式)。这里有一个取舍问题,就是是把一个Action当成一个文件
处理,还是多个Action集中到一个文件来处理。symfony,Zend Framework采取的是一个
文件多个Action的方式。我之前采用的是一个文件一个Action的方式,后来想想这种方
式有一个缺点:难以在多个Action间共享某些计算,如Admin Module下的多个Action,
它们都需要检查客户端是否有有效的会话信息以确保用户已经登录。在一个文件一个
Action的情况下,这些重复代码必然会在多个Action间复制,除非你把这部分代码抽取
出来放到某个地方,但问题是应该放在哪个地方?我想不到好的地方(你不可能把它们
放到抽象的Action父类,因为这些逻辑只是属于某个模块的,不是所有的Action子类都
有的),所以我打算采用一个文件多个Action的方法,并把这些重复代码抽象成一个预
处理方法和后处理方法,当调用某个模块的Action时,预先执行这些代码。在这些代码
里,自然是校验和计算,如果通不过,直接给Response对象发redirect消息,让客户端
重定向到某个地方。
除了调用表现层,Action还有哪些职责?实际上,Action的职责并不多,但它仍是
系统的核心所在,因为你的Action必须捕获所有的用例。可能一个用例有多个Action参
与,也可能一个用例只有一个Action,这取决于你的用例分析。这是OOA阶段的任务,
这里不多讲,一个优秀的分析人员必须能编写高质量的用例(可以参考W.C的《编写有
效用例》一书)。捕获了用例后,Action有两个任务:一,选取某个领域服务,让该
领域服务处理某个逻辑事务;二,选取某个应用服务,让该服务完成应用相关的任务,
如发送Email,把领域对象缓存进Cache源,把领域对象保存到Session数据源,等等。
这里的关键,是区分领域服务和应用服务,否则很难做出正确的设计。关于服务层,我
推荐阅读三本书籍:一是《领域驱动设计》,这本书有一小节详细地论述了服务层的设
计以及领域服务和应用服务的区分;二是《企业应用架构模式》,这本书有两个地方讲
述了服务层,讲得比较细致;三是《J2EE核心模式》,里头的“业务服务”模式就是领
域服务的精确描述,简洁易懂。
应用服务操作技术层对象,那么领域服务操作什么对象?领域服务操作领域模型。
关于领域模型,有非常多的书有详细的讲述,前面提到的三本书都有详细的讲述,除此
外还有《UML与模式应用》,《UML面向对象建模与设计》等书,都有对领域模型的细致
讲述。理解领域模型并运用领域模型是一种非常重要的技能,因为只有分析并提炼出合
适的领域模型,设计才不会偏离需求。需要注意的是,我个人对领域模型复杂化并没有
好感,因为这样一来映射到数据源将非常麻烦。我崇尚让领域模型跟数据库的E-R模型
一一对应,这样一来无论理解还是修改,都非常简单。另外一个可能影响设计的东西是
值对象。目前对这些比较细节方面的东西的处理,我还暂时没有深入考虑,等设计到那
时再说。
需要注意的是,领域服务不能剥夺本应属于领域模型的一部分业务逻辑,否则就混
淆了领域服务与领域模型的区别。比如一个领域模型:用户,它有一个规格检查:年龄
是否达到要求,这个规格检查就不应该放到领域服务里,而应该直接加在领域模型上,
这也是非常自然的处理方法。此外,类似查询用户是否存在这样的任务,就应该组织到
一个领域服务里,由Action调用该服务。领域服务并不知道是谁调用它(这符合分层的
基本原则,即上层对象可以调用它下层的对象,以及更下层的对象,但下层对象不能调
用上层对象,如果确实需要,至少也要用观察者模式来解开耦合,以让领域服务能更好
地在多个Action间重用。
领域层是由开发人员组织的,框架对领域层基本没有做限制,开发者可以进行任意
的发挥,前面我只是提出一种我认为比较合适的组织方法。下一篇将讲述框架内最重要
的组成部分——技术层,以及技术层里的重中之重——持久层。
了,具体分几层,还是一个值得决策的问题。我最熟悉的《责任驱动设计》和《领域驱
动设计》都推荐用四层,而且两者的吻合度相当高,我受这两本书的影响也最大,所以
很自然我的框架也分四层。
哪四层?表现层、应用层、领域层和技术层。
浏览器、PHP和数据库是比较容易区分出来的物理上的三个层次。然而这只是物理
分层,跟逻辑分层还是有区别的。目前Ajax非常流行,而且很多应用也确实需要Ajax来
提供富交互功能,所以支持Ajax是相当自然的一件事。在物理层面上,Ajax属于浏览器
层。实际上我对在浏览器端维护一个领域模型并不感兴趣,原因是难以在两者间保持同
步,也无谓地增加了复杂性。我完全可以直接在Action里处理,然后直接用PHP内置的
JSON系列函数直接输出数据供客户端JavaScript进行eval调用。用JavaScript Helper
来生成Ajax,目前我还没试过,具体是否支持,还要到时看情况而定。另外在资源定位
方面,我觉得REST的思想不错,不过目前还不是很熟悉REST,所以这一块打算暂时先放
一放,到时再重构以适应REST。
回到PHP这一物理层面上来。上面所说的四层架构,就是在PHP这一物理层次。
先说表现层。PHP对表现层的支持非常灵活非常方便,因为PHP最初就是用来做表现
层的。关于页面的组织,我的方法是把一个HTML页面分成两类元素,一类是布局,另一
类是模板。跟现有的一些框架不一样的是,我的模板没有再细分为其它元素,如slot之
类。因为没必要。一个布局对应一个有HTML输出的Action,同时一个布局还对应多个模
板。注意,布局跟Action是一一对应的,但模板不同,模板是完全灵活的,在某个时候
它可以嵌入某个布局,在另一个时候可以嵌入另一个布局。这样一来,模板就成了完全
可以复用的单元,而且经过我的处理,每个模板可以在树结构上从树叶一直往上移到树
根,以在多个Action、多个Module乃至多个App间的布局里共享。如果你熟悉JavaScript
的对象绑定,你就很容易理解一个对象做为另一个对象的某个属性这一过程跟我的模板
对应布局这一过程非常相似,都是动态绑定的。
表现层由Action调用,而Action则处于应用层,多个Action组成一个Module,多个
Module组成一个Application(这一点参考自symfony,实际上在没有接触过symfony前
我就想到过这种组织方式)。这里有一个取舍问题,就是是把一个Action当成一个文件
处理,还是多个Action集中到一个文件来处理。symfony,Zend Framework采取的是一个
文件多个Action的方式。我之前采用的是一个文件一个Action的方式,后来想想这种方
式有一个缺点:难以在多个Action间共享某些计算,如Admin Module下的多个Action,
它们都需要检查客户端是否有有效的会话信息以确保用户已经登录。在一个文件一个
Action的情况下,这些重复代码必然会在多个Action间复制,除非你把这部分代码抽取
出来放到某个地方,但问题是应该放在哪个地方?我想不到好的地方(你不可能把它们
放到抽象的Action父类,因为这些逻辑只是属于某个模块的,不是所有的Action子类都
有的),所以我打算采用一个文件多个Action的方法,并把这些重复代码抽象成一个预
处理方法和后处理方法,当调用某个模块的Action时,预先执行这些代码。在这些代码
里,自然是校验和计算,如果通不过,直接给Response对象发redirect消息,让客户端
重定向到某个地方。
除了调用表现层,Action还有哪些职责?实际上,Action的职责并不多,但它仍是
系统的核心所在,因为你的Action必须捕获所有的用例。可能一个用例有多个Action参
与,也可能一个用例只有一个Action,这取决于你的用例分析。这是OOA阶段的任务,
这里不多讲,一个优秀的分析人员必须能编写高质量的用例(可以参考W.C的《编写有
效用例》一书)。捕获了用例后,Action有两个任务:一,选取某个领域服务,让该
领域服务处理某个逻辑事务;二,选取某个应用服务,让该服务完成应用相关的任务,
如发送Email,把领域对象缓存进Cache源,把领域对象保存到Session数据源,等等。
这里的关键,是区分领域服务和应用服务,否则很难做出正确的设计。关于服务层,我
推荐阅读三本书籍:一是《领域驱动设计》,这本书有一小节详细地论述了服务层的设
计以及领域服务和应用服务的区分;二是《企业应用架构模式》,这本书有两个地方讲
述了服务层,讲得比较细致;三是《J2EE核心模式》,里头的“业务服务”模式就是领
域服务的精确描述,简洁易懂。
应用服务操作技术层对象,那么领域服务操作什么对象?领域服务操作领域模型。
关于领域模型,有非常多的书有详细的讲述,前面提到的三本书都有详细的讲述,除此
外还有《UML与模式应用》,《UML面向对象建模与设计》等书,都有对领域模型的细致
讲述。理解领域模型并运用领域模型是一种非常重要的技能,因为只有分析并提炼出合
适的领域模型,设计才不会偏离需求。需要注意的是,我个人对领域模型复杂化并没有
好感,因为这样一来映射到数据源将非常麻烦。我崇尚让领域模型跟数据库的E-R模型
一一对应,这样一来无论理解还是修改,都非常简单。另外一个可能影响设计的东西是
值对象。目前对这些比较细节方面的东西的处理,我还暂时没有深入考虑,等设计到那
时再说。
需要注意的是,领域服务不能剥夺本应属于领域模型的一部分业务逻辑,否则就混
淆了领域服务与领域模型的区别。比如一个领域模型:用户,它有一个规格检查:年龄
是否达到要求,这个规格检查就不应该放到领域服务里,而应该直接加在领域模型上,
这也是非常自然的处理方法。此外,类似查询用户是否存在这样的任务,就应该组织到
一个领域服务里,由Action调用该服务。领域服务并不知道是谁调用它(这符合分层的
基本原则,即上层对象可以调用它下层的对象,以及更下层的对象,但下层对象不能调
用上层对象,如果确实需要,至少也要用观察者模式来解开耦合,以让领域服务能更好
地在多个Action间重用。
领域层是由开发人员组织的,框架对领域层基本没有做限制,开发者可以进行任意
的发挥,前面我只是提出一种我认为比较合适的组织方法。下一篇将讲述框架内最重要
的组成部分——技术层,以及技术层里的重中之重——持久层。