Domain Pollution Resolution
域污染解除
0. Domain 名词解释
首先说明一下 Domain 在本文中的意思。
<<Domain Driven Design>> 一书,令 Domain 这个词很火。引起了广泛争论:哪些Logic 应该放在 Business Service Layer, 哪些应该放在 Domain Object里面。这类争论纷纷扬扬,最后通常都上升到哲学高度,世界观高度,认知心理学高度。如果不幸发展到极端情况,双方很可能开始相互质疑对方的智商和对世界的基本理解能力。
这个层次的Domain 纷争,不是本文所关心的话题。
我一向认为,设计能够满足如下的条件,就可以称为是一个好的设计:能够使用多态性,代替Hard-Coded if else switch 等逻辑分支;新需求来的时候,不需要在旧代码里面添加if else switch等逻辑分支,而只需要加入一个新的Class;尽量避免 Package, Class 级别的交叉引用。
本文所关心的Domain 是一个更高的层次,类似于DSL, Domain Specific Language 里面的Domain的意思。
本文主要讨论如下议题:
HTML是否只应该用来表示结构?
HTML是否应该包含逻辑?
HTML是否应该包含服务器端脚本逻辑?比如,JSP, Velocity, Freemarker.
HTML是否应该包含浏览器端脚本逻辑?比如,Java Script.
Java 代码中是否应该输出HTML标签?比如,Taglib, Tapestry Page Component, etc?
Java 代码中是否应该使用框架特殊的HTML View Model? 比如,XMLC, Wicket, Echo, etc.
HTTP Web Server本来的设计宗旨是无状态,支持大用户量,多连接。目前的server side的page flow, web flow, continuation, 力图使得HTTP Web Server保持用户的流程状态。这种做法是否应该推荐?
SQL是一种可读性很好,应用广泛的一种DSL。如何动态拼装SQL,一直是一个难以解决的问题。
是否应该在Java 代码中嵌入SQL?
是否应该在Java 代码中使用 Criteria API?
是否应该在SQL Template中加入动态语言脚本逻辑?比如 iBatis 使用XML if otherwise, OR Bridge中使用velocity?
HQL, OQL在SQL中引入了OO语言的特性,似乎操作的是对象数据库,而不是关系数据库。这种做法是否应该推荐?
Hibernate 动态期间篡改用户代码的做法,是否应该推荐?
JDO静态期间篡改拥护代码的做法,是否应该推荐?
Dynamic Proxy, CGLib等动态篡改用户代码的做法,是否应该推荐?
Reflection绕开了 Java 的类型检查机制,是否应该推荐?
Thread Local是一种隐式契约,相当于绕开了显式契约(方法签名method signature),这种做法是否应该推荐?
这些属于开发模式、代码风格方面的问题,正如所谓Code Smell的感觉一样,也避免不了主观的成分。所以,下面的陈述并非论断,而只是“在我看来”。
1. HTML Template Layer
HTML应该尽量只用来表示结构,排除所有逻辑。
JSP, Velocity, Freemarker等服务器端脚本逻辑,属于Java Code或者Script Code对HTML的污染。
不仅应该排除JSP, Velocity, Freemarker等服务器端脚本逻辑,而且应该排除浏览器端脚本逻辑,比如Java Script.
当然,这里不是说,不用Java Script,而是说,不要把Java Script放在HTML里面。把Java Script放在一个单独的.js文件里面,在HTML中引入,并使用CSS把HTML Element 和 Java Script Event Handler 联系起来。
这方面的资料有:
Unobtrusive Javascript
http://www.onlinetools.org/articles/unobtrusivejavascript/
http://www.kryogenix.org/code/browser/aqlists/
http://www.bobbyvandersluis.com/articles/goodpractices.php
如何排除HTML中的Server Side Logic呢?
Ajax!
Ajax fans一定反应迅速,给出答案。
没错。Ajax可以做到。Ajax的主要问题在于使用和掌握难度。
除了Ajax呢?
有几个选择,XMLC, Jivan, fastm, Wicket, Echo等。(and Tapestry?)
fastm是我做的一个模板层,具体内容可以在此下载。
https://fastm.dev.java.net/files/documents/1911/25042/fastm1.0c.zip
fastm采用 XML Comment 作为文档结构标记。注意,是结构标记,而不是逻辑标记。不含有for if else。
可见,fastm并不是毫无污染,只是没有逻辑污染。fastm只在HTML中添加了自定义的结构标签,也勉强算没有违背HTML只用来表示结构的原意。
Ajax, XMLC, Jivan 的HTML很干净,而且对HTML 结构的控制能力很强,能够用代码动态控制布局。fastm也能够动态控制布局。Site mesh, tiles taglib, jsp, velocity, freemarker等做不到。
fastm 里面的例子中,包括一个分页的例子,和一个动态组装SQL的例子
2. Java Web Layer
Taglib, Tapestry Page Component, Echo, Wicket等在Java 代码中输出HTML标签,这也是一种,而且要求在Java 代码中使用框架特殊的HTML View Model,比如Table, List, Label, Formbean 等View Object。使用了这些框架,Java 代码的编译就需要依赖于这些框架了。
这相当于HTML标签对Java 代码的污染。
JSP, Velocity, Freemarker都不存在这种情况。Java 代码只是提供POJO,然后 HTML里面的Server Side Script使用这些POJO.
fastm不支持逻辑,所以这个方面弱一些。fastm要求Java Code把if, else 等逻辑分支变成一个Map 结构。由于Map属于java的基本类,所以,fastm也不需要在Java code中使用任何特殊的框架相关的view model。
3. Web Layer - Stateless or Stateful
与其在服务器端支持 page flow, web flow, continuation,那不如在浏览器端支持状态。这种场合下,我觉得,正是使用Ajax的最佳场所。
我倾向于这样的设计,服务器端尽量无状态,如果确实需要状态,那么尽量在浏览器段保持状态。
lightweb是我做的一个Web框架。
https://lightweb.dev.java.net/files/documents/4371/25044/lightweb0.9b.zip
主要的特点是,URL-Centric, 鼓励无状态的Service Oriented设计。支持三种Service模型,
Action (like WebWork Action),
Controller (like Spring MVC Controller),
Channel (like Struts Action Dispatcher),
lightweb支持多级模块寻找,使用几条匹配规则,代替庞大的Site Map 配置文件。
lightweb查找View的时候,不是根据Site Map 文件里面的那种小型状态机的配置(success -> showIt.jsp; fail-> this page), 而是直接资源定位。lightweb继承了fastm的观点,把template看作资源,而不是一段运行脚本。
Lightweb设计为IoC友好。在各个环节都暴露出插件接口。程序员可以自己选择IoC策略。可以采用一般的做法,用一个IoC container集中管理,比如,Spring IoC, Pico, Nano等;也可以分散到各子模块,进行管理;也可以分散到每个Action Validator里面管理。
4. ORM Layer
Hibernate 动态期间篡改用户代码,JDO静态期间篡改用户代码。
令我想起黑客,木马,夹带,偷梁换柱等。这属于语义上的Pollution。就是说,从原有的Source看起来,Domain Object并没有那样的行为,运行起来却有那样的行为。
iBatis采用reflection,确实干净。但是,性能、功能上又不够强大。某些功能的实现,确实需要代码生成。
lightor是我做的一个ORM框架。
https://lightor.dev.java.net/files/documents/4370/25043/lightor0.5a.zip
lightor也需要代码生成。区别在于,lightor不修改任何代码,只是产生新的Mapper代码。Domain Object运行的时候,还是你原来的Domain Object。这就避免了语义的污染。
lightor生成的Mapper代码,可以编译期类型检查,可以阅读,跟踪,调试。这些生成的代码,也都遵循本文的原则。Java 代码里面不含有SQL片断,SQL都存在于单独的资源文件中。
(另外,不采用CGLib,也是为了实现的简单)
lightor直接使用Native SQL。Lightor的目的不是屏蔽关系数据库,而是恰好相反。lightor的目的是,帮助程序员更清楚地认识关系数据库,SQL,JDBC。lightor和JDBC不冲突,可以一起使用。
lightor努力的第一个主要方向就是效率。大数据量查询和处理的效率。
据我所知,目前只有lightor才支持大数据量批量处理,因为只有lightor不惜降低ORM的身份和封装层次,能够接受ResultSet作为参数。
lightor的缓存策略比Hibernate更前进了一步,把query cache 和 ID Cache集成在一起。以便得到更好的控制。并且,把缓存API暴露给程序员,以便智能的控制某个特定的缓存。
5. SQL
HQL, OQL在SQL中引入了OO语言的特性,似乎操作的是对象数据库,而不是关系数据库。
我感觉,这是一种语法层次上的Domain Pollution。OO语法对SQL语法的污染。
正如感觉LinkQ是数据库查询语法对OO语法的污染。
Java 代码中使用 Criteria API,也属于数据库查询领域对OO领域的污染。
SQL是一种可读性很好,应用广泛的一种DSL。
我的看法是,尽量把完整的SQL放到单独的资源文件中,可以直接Copy到SQL Client就可以运行。特殊优化过的Native SQL,也是同样的地位。
HQL, OQL阻碍了用户对SQL进行特殊优化。
如何动态拼装SQL,一直是一个难以解决的问题。
直接在Java 代码中嵌入SQL,肯定不行。前面说了,Java里面最好不要有SQL片断,最好放到另外的资源文件中。
在Java 代码中使用 Criteria API? 前面也否定了。
在SQL Template中加入动态语言脚本逻辑?比如 iBatis 使用XML if otherwise, OR Bridge中使用velocity?
这种方法还是不错。不过仍然在SQL里面引入了脚本逻辑。
我也没有好的做法。我采用fastm来处理。在SQL里面引入结构标记。这也相当于污染。因为SQL和HTML不同。SQL不是描述结构的,而是一门DSL。
这种做法的一个好处是,整段SQL还是可以Copy到SQL Client里面,稍微修改一番,就可以直接运行。
6. A Demo Forum
farum是我做的一个简单的forum demo. 使用了fastm, lightor, lightweb, 也尽量体现我前面提出的原则。
https://farum.dev.java.net/files/documents/4372/25045/farum0.5a.zip
我觉得,使用一门语言,应该尽量发挥它的特长,而不是针对它的短处,修修补补。
Java作为静态类型编译语言的好处,就是编译期类型检查。那么如何发挥这个优势?前面说的Mapper源代码生成,就是一种尝试。
另外,其它的类似的胶水粘合部分,比如,IoC, Validation等,都可以进行这方面的尝试。目前,farum就是采用这样的做法,自己写的Validator Source进行validation, value setting, service implementation injection等工作。
farum没有使用Dynamic Proxy, CGLib等动态篡改用户代码的做法。
尽量不使用Reflection,因为Reflection绕开了 Java 的类型检查机制。与其使用reflection,真不如使用动态脚本语言。
当然,reflection是非常难以避免的。farum也少许使用了reflection。
Thread Local是一种隐式契约,相当于绕开了显式契约(方法签名method signature)。WebWork的一些用法,Spring Framework的Open Session In View,等都是采用了Thread Local。
farum没有采用Thread Local,而是采用了另一种方法实现了Open Session In view。而且如果缓存命中,甚至不需要从connection pool中获取Connection。
总结
<<Web开发构想>>一文中,我提出了,理想中的Web开发架构是这样的:
开发速度快,运行速度快,结构清晰优雅。
具体到每一层。
Web框架层主要追求 开发速度快。
O/R层主要追求 运行速度快。
页面资源层和页面模板层主要追求 结构清晰优雅。
下面就是我对这些理想的初步实现。
还远远达不到完美的程度,而只是朝这个方向努力。
Template Layer
fastm
https://fastm.dev.java.net/files/documents/1911/25042/fastm1.0c.zip
Web Layer
lightweb
https://lightweb.dev.java.net/files/documents/4371/25044/lightweb0.9b.zip
ORM Layer
lightor
https://lightor.dev.java.net/files/documents/4370/25043/lightor0.5a.zip
A forum demo using fastm + lightor + lightweb
farum
https://farum.dev.java.net/files/documents/4372/25045/farum0.5a.zip
域污染解除
0. Domain 名词解释
首先说明一下 Domain 在本文中的意思。
<<Domain Driven Design>> 一书,令 Domain 这个词很火。引起了广泛争论:哪些Logic 应该放在 Business Service Layer, 哪些应该放在 Domain Object里面。这类争论纷纷扬扬,最后通常都上升到哲学高度,世界观高度,认知心理学高度。如果不幸发展到极端情况,双方很可能开始相互质疑对方的智商和对世界的基本理解能力。
这个层次的Domain 纷争,不是本文所关心的话题。
我一向认为,设计能够满足如下的条件,就可以称为是一个好的设计:能够使用多态性,代替Hard-Coded if else switch 等逻辑分支;新需求来的时候,不需要在旧代码里面添加if else switch等逻辑分支,而只需要加入一个新的Class;尽量避免 Package, Class 级别的交叉引用。
本文所关心的Domain 是一个更高的层次,类似于DSL, Domain Specific Language 里面的Domain的意思。
本文主要讨论如下议题:
HTML是否只应该用来表示结构?
HTML是否应该包含逻辑?
HTML是否应该包含服务器端脚本逻辑?比如,JSP, Velocity, Freemarker.
HTML是否应该包含浏览器端脚本逻辑?比如,Java Script.
Java 代码中是否应该输出HTML标签?比如,Taglib, Tapestry Page Component, etc?
Java 代码中是否应该使用框架特殊的HTML View Model? 比如,XMLC, Wicket, Echo, etc.
HTTP Web Server本来的设计宗旨是无状态,支持大用户量,多连接。目前的server side的page flow, web flow, continuation, 力图使得HTTP Web Server保持用户的流程状态。这种做法是否应该推荐?
SQL是一种可读性很好,应用广泛的一种DSL。如何动态拼装SQL,一直是一个难以解决的问题。
是否应该在Java 代码中嵌入SQL?
是否应该在Java 代码中使用 Criteria API?
是否应该在SQL Template中加入动态语言脚本逻辑?比如 iBatis 使用XML if otherwise, OR Bridge中使用velocity?
HQL, OQL在SQL中引入了OO语言的特性,似乎操作的是对象数据库,而不是关系数据库。这种做法是否应该推荐?
Hibernate 动态期间篡改用户代码的做法,是否应该推荐?
JDO静态期间篡改拥护代码的做法,是否应该推荐?
Dynamic Proxy, CGLib等动态篡改用户代码的做法,是否应该推荐?
Reflection绕开了 Java 的类型检查机制,是否应该推荐?
Thread Local是一种隐式契约,相当于绕开了显式契约(方法签名method signature),这种做法是否应该推荐?
这些属于开发模式、代码风格方面的问题,正如所谓Code Smell的感觉一样,也避免不了主观的成分。所以,下面的陈述并非论断,而只是“在我看来”。
1. HTML Template Layer
HTML应该尽量只用来表示结构,排除所有逻辑。
JSP, Velocity, Freemarker等服务器端脚本逻辑,属于Java Code或者Script Code对HTML的污染。
不仅应该排除JSP, Velocity, Freemarker等服务器端脚本逻辑,而且应该排除浏览器端脚本逻辑,比如Java Script.
当然,这里不是说,不用Java Script,而是说,不要把Java Script放在HTML里面。把Java Script放在一个单独的.js文件里面,在HTML中引入,并使用CSS把HTML Element 和 Java Script Event Handler 联系起来。
这方面的资料有:
Unobtrusive Javascript
http://www.onlinetools.org/articles/unobtrusivejavascript/
http://www.kryogenix.org/code/browser/aqlists/
http://www.bobbyvandersluis.com/articles/goodpractices.php
如何排除HTML中的Server Side Logic呢?
Ajax!
Ajax fans一定反应迅速,给出答案。
没错。Ajax可以做到。Ajax的主要问题在于使用和掌握难度。
除了Ajax呢?
有几个选择,XMLC, Jivan, fastm, Wicket, Echo等。(and Tapestry?)
fastm是我做的一个模板层,具体内容可以在此下载。
https://fastm.dev.java.net/files/documents/1911/25042/fastm1.0c.zip
fastm采用 XML Comment 作为文档结构标记。注意,是结构标记,而不是逻辑标记。不含有for if else。
可见,fastm并不是毫无污染,只是没有逻辑污染。fastm只在HTML中添加了自定义的结构标签,也勉强算没有违背HTML只用来表示结构的原意。
Ajax, XMLC, Jivan 的HTML很干净,而且对HTML 结构的控制能力很强,能够用代码动态控制布局。fastm也能够动态控制布局。Site mesh, tiles taglib, jsp, velocity, freemarker等做不到。
fastm 里面的例子中,包括一个分页的例子,和一个动态组装SQL的例子
2. Java Web Layer
Taglib, Tapestry Page Component, Echo, Wicket等在Java 代码中输出HTML标签,这也是一种,而且要求在Java 代码中使用框架特殊的HTML View Model,比如Table, List, Label, Formbean 等View Object。使用了这些框架,Java 代码的编译就需要依赖于这些框架了。
这相当于HTML标签对Java 代码的污染。
JSP, Velocity, Freemarker都不存在这种情况。Java 代码只是提供POJO,然后 HTML里面的Server Side Script使用这些POJO.
fastm不支持逻辑,所以这个方面弱一些。fastm要求Java Code把if, else 等逻辑分支变成一个Map 结构。由于Map属于java的基本类,所以,fastm也不需要在Java code中使用任何特殊的框架相关的view model。
3. Web Layer - Stateless or Stateful
与其在服务器端支持 page flow, web flow, continuation,那不如在浏览器端支持状态。这种场合下,我觉得,正是使用Ajax的最佳场所。
我倾向于这样的设计,服务器端尽量无状态,如果确实需要状态,那么尽量在浏览器段保持状态。
lightweb是我做的一个Web框架。
https://lightweb.dev.java.net/files/documents/4371/25044/lightweb0.9b.zip
主要的特点是,URL-Centric, 鼓励无状态的Service Oriented设计。支持三种Service模型,
Action (like WebWork Action),
Controller (like Spring MVC Controller),
Channel (like Struts Action Dispatcher),
lightweb支持多级模块寻找,使用几条匹配规则,代替庞大的Site Map 配置文件。
lightweb查找View的时候,不是根据Site Map 文件里面的那种小型状态机的配置(success -> showIt.jsp; fail-> this page), 而是直接资源定位。lightweb继承了fastm的观点,把template看作资源,而不是一段运行脚本。
Lightweb设计为IoC友好。在各个环节都暴露出插件接口。程序员可以自己选择IoC策略。可以采用一般的做法,用一个IoC container集中管理,比如,Spring IoC, Pico, Nano等;也可以分散到各子模块,进行管理;也可以分散到每个Action Validator里面管理。
4. ORM Layer
Hibernate 动态期间篡改用户代码,JDO静态期间篡改用户代码。
令我想起黑客,木马,夹带,偷梁换柱等。这属于语义上的Pollution。就是说,从原有的Source看起来,Domain Object并没有那样的行为,运行起来却有那样的行为。
iBatis采用reflection,确实干净。但是,性能、功能上又不够强大。某些功能的实现,确实需要代码生成。
lightor是我做的一个ORM框架。
https://lightor.dev.java.net/files/documents/4370/25043/lightor0.5a.zip
lightor也需要代码生成。区别在于,lightor不修改任何代码,只是产生新的Mapper代码。Domain Object运行的时候,还是你原来的Domain Object。这就避免了语义的污染。
lightor生成的Mapper代码,可以编译期类型检查,可以阅读,跟踪,调试。这些生成的代码,也都遵循本文的原则。Java 代码里面不含有SQL片断,SQL都存在于单独的资源文件中。
(另外,不采用CGLib,也是为了实现的简单)
lightor直接使用Native SQL。Lightor的目的不是屏蔽关系数据库,而是恰好相反。lightor的目的是,帮助程序员更清楚地认识关系数据库,SQL,JDBC。lightor和JDBC不冲突,可以一起使用。
lightor努力的第一个主要方向就是效率。大数据量查询和处理的效率。
据我所知,目前只有lightor才支持大数据量批量处理,因为只有lightor不惜降低ORM的身份和封装层次,能够接受ResultSet作为参数。
lightor的缓存策略比Hibernate更前进了一步,把query cache 和 ID Cache集成在一起。以便得到更好的控制。并且,把缓存API暴露给程序员,以便智能的控制某个特定的缓存。
5. SQL
HQL, OQL在SQL中引入了OO语言的特性,似乎操作的是对象数据库,而不是关系数据库。
我感觉,这是一种语法层次上的Domain Pollution。OO语法对SQL语法的污染。
正如感觉LinkQ是数据库查询语法对OO语法的污染。
Java 代码中使用 Criteria API,也属于数据库查询领域对OO领域的污染。
SQL是一种可读性很好,应用广泛的一种DSL。
我的看法是,尽量把完整的SQL放到单独的资源文件中,可以直接Copy到SQL Client就可以运行。特殊优化过的Native SQL,也是同样的地位。
HQL, OQL阻碍了用户对SQL进行特殊优化。
如何动态拼装SQL,一直是一个难以解决的问题。
直接在Java 代码中嵌入SQL,肯定不行。前面说了,Java里面最好不要有SQL片断,最好放到另外的资源文件中。
在Java 代码中使用 Criteria API? 前面也否定了。
在SQL Template中加入动态语言脚本逻辑?比如 iBatis 使用XML if otherwise, OR Bridge中使用velocity?
这种方法还是不错。不过仍然在SQL里面引入了脚本逻辑。
我也没有好的做法。我采用fastm来处理。在SQL里面引入结构标记。这也相当于污染。因为SQL和HTML不同。SQL不是描述结构的,而是一门DSL。
这种做法的一个好处是,整段SQL还是可以Copy到SQL Client里面,稍微修改一番,就可以直接运行。
6. A Demo Forum
farum是我做的一个简单的forum demo. 使用了fastm, lightor, lightweb, 也尽量体现我前面提出的原则。
https://farum.dev.java.net/files/documents/4372/25045/farum0.5a.zip
我觉得,使用一门语言,应该尽量发挥它的特长,而不是针对它的短处,修修补补。
Java作为静态类型编译语言的好处,就是编译期类型检查。那么如何发挥这个优势?前面说的Mapper源代码生成,就是一种尝试。
另外,其它的类似的胶水粘合部分,比如,IoC, Validation等,都可以进行这方面的尝试。目前,farum就是采用这样的做法,自己写的Validator Source进行validation, value setting, service implementation injection等工作。
farum没有使用Dynamic Proxy, CGLib等动态篡改用户代码的做法。
尽量不使用Reflection,因为Reflection绕开了 Java 的类型检查机制。与其使用reflection,真不如使用动态脚本语言。
当然,reflection是非常难以避免的。farum也少许使用了reflection。
Thread Local是一种隐式契约,相当于绕开了显式契约(方法签名method signature)。WebWork的一些用法,Spring Framework的Open Session In View,等都是采用了Thread Local。
farum没有采用Thread Local,而是采用了另一种方法实现了Open Session In view。而且如果缓存命中,甚至不需要从connection pool中获取Connection。
总结
<<Web开发构想>>一文中,我提出了,理想中的Web开发架构是这样的:
开发速度快,运行速度快,结构清晰优雅。
具体到每一层。
Web框架层主要追求 开发速度快。
O/R层主要追求 运行速度快。
页面资源层和页面模板层主要追求 结构清晰优雅。
下面就是我对这些理想的初步实现。
还远远达不到完美的程度,而只是朝这个方向努力。
Template Layer
fastm
https://fastm.dev.java.net/files/documents/1911/25042/fastm1.0c.zip
Web Layer
lightweb
https://lightweb.dev.java.net/files/documents/4371/25044/lightweb0.9b.zip
ORM Layer
lightor
https://lightor.dev.java.net/files/documents/4370/25043/lightor0.5a.zip
A forum demo using fastm + lightor + lightweb
farum
https://farum.dev.java.net/files/documents/4372/25045/farum0.5a.zip
安徽新华电脑学校专业职业规划师为你提供更多帮助【在线咨询】