从某维护系统的架构改造谈起–分层的理解(旧)

这是刚到公司时写的~

公司的项目是一个维护有好几年的大规模电信项目,并且经过无数人的摧残,代码极其混乱。我们想改变这个层次不清代码混成一团的现状,引入业界广为使用的三层架构开发模式。如下图所示: 三层结构

最近小组一直在探讨如何从现有架构上逐渐迁移为分层清晰的状况,虽然我们主要的精力还是放在新增的功能上,但是在这个整个过程还是令我对分层有了更深的理解。这里主要说说对于通用分层的理解:

  1. 虽然大家都或多或少知道Action,Service,Dao干的是什么,不过实践起来的时候,有很多细节问题需要考虑(我们的系统有些比较蹩脚的调用接口,又不能抛弃)
  2. Action主要关注大的流程处理,其中可能会包括多个Service的处理(像我们需要与大量外围系统交互更是如此),一个Service方法代表一个具有完整事务边界的流程。
  3. Action和Service的边界主要由参数来决定,Service不需要知道调用方是一个GUI还是web请求,所以调用参数不应该出现request/response/session之类的对象, 应该限制为java基本类型,基本集合类型,简单的值对象(用于避免长参数调用),或者是具有特殊业务意义的变量(例如用户等信息)。
  4. Service和Dao的区分主要是业务相关性,Service不需要关心Dao究竟调用DB还是其他的数据源获得的(多数据源在大型系统中非常常见), 所以从调用参数和返回值上看,参数和返回值都避免具体的Dao实现有关。针对某些Dao调用返回值需要进一步处理,这部分的工作放在Dao还是Service取决于处理工作更靠近业务还是靠近具体Dao实现协议。
  5. 从关注对象上看,Action关注request、response、session等; Service关注java基本类型,java集合类型,值对象等;Dao关注Sql,ResultSet等底层数据结构。
  6. 从关注的异常处理看,Action关注特定的业务异常(BusinessException),并统一业务异常处理流程, Service只关心Dao是否发生异常(例如统一的Dao异常接口DataAccessException),记录并转化为相应的业务异常交个业务处理;Dao关注底层的异常,通常也无法恢复,只好转化成统一接口交给上层处理。

晚了,下一次就谈谈关于这个项目的异常处理的改造或者Dao的改造情况吧~~

从某维护系统的架构改造谈起–异常处理(旧)

这是刚到公司时写的~

Java 提供了两类主要的异常:runtime exception和checked exception。 所有的checked exception是从java.lang.Exception类衍生出来的, 而runtime exception则是从java.lang.RuntimeException或java.lang.Error类衍生出来的。

如果你希望强制你的类调用者来处理异常,那么就用Checked Exception; 如果你不希望强制你的类调用者来处理异常,就用UnChecked。 那么究竟强制还是不强制,权衡的依据在于从业务系统的逻辑规则来考虑, 如果业务规则定义了调用者应该处理,那么就必须Checked,如果业务规则没有定义,就应该用UnChecked。

至于类调用者catch到NoSuchUserException和PasswordNotMatchException怎么处理,也要根据他自己具体的业务逻辑了。 或者他有能力也应该处理,就自己处理掉了;或者他不关心这个异常,也不希望上面的类调用者关心,就转化为RuntimeException; 或者他希望上面的类调用者处理,而不是自己处理,就转化为本层的异常继续往上抛出来。 根据上面的一些观点在现有的系统上建立基于三层架构的异常处理模型,主要有以下做法:

  • Dao层次关注底层异常,当很多SQLException无法恢复,转换成统一的RuntimeException交给Service处理
  • Service层关心某些有业务含义的业务处理,并转化成相应的业务异常(同样也是RuntimeException)交给Action处理
  • Action层可能对某些特定的业务异常感兴趣,感兴趣就处理(或许尝试修复),不感兴趣就交给统一业务异常处理器处理
  • 统一业务异常处理器会记录exception log,并负责相应的错误展现页面

实现思路(待续):

  • exception util:提供一些异常处理的工具,例如转换异常
  • exception wrapper:作为统一的异常类,可以用来保存原始的exception信息
  • exception collector:可以保存多个exception信息的异常类,用于某些特殊场合
  • exception handler:用于处理exception的统一处理,主要是日志与错误页面

从某维护系统的架构改造谈起–公用代码库(旧)

这是刚到公司时写的~

有时候真是不看不知道,这样一个每月数亿业务量,数十亿营收的大型系统,原来也就是这样的质量。 我们可以想象这种系统一开始都是美好的,只是渐渐的变味了。就拿我们这边java的来说,原来也应该是有公用代码库的,只是那么久每人去维护,项目赶工, 自立山寨的情况多了,这些东西的反向价值就越来越明显了。典型的表现有:

  • 自立门户的现象很突出,经常可以找到n个方法是做同样事情的;
  • 神级的类很多,例如某个类可以提供字符串,时间,web,业务相关的操作;
  • 公用类代码质量良莠不齐,有很多明显写得不好的地方(曾经有个数千行的类就给我砍倒1/3代码);
  • 缺乏公用库的document和quickstart

这样造成浪费,花了很多冤枉时间去实现已有功能,缺乏统一规划。大家都有体会,维护一个杂乱无章的系统是多么的困难, 在这种系统想写出漂亮的代码是很考水平的。现在代码库又特别庞大,怪不得很多同事慢慢地就给系统同化了, 缺乏持续改进的动力。在上次retro会议的时候,针对缺乏统一的公用代码库的问题,针对质量和维护的问题,我提了一些做法:

公共代码库迁移

  1. 方向是在保留现有逻辑的基础上,针对新添加的功能和修改的功能,逐渐迁移到新的公用库。
  2. 至于公用库的选材,主要是从各大现有公用库进行提炼,对神级类进行拆分,并结合开发人员提需求的做法,形成我们的公用库。
  3. 为了避免山寨的出现,培训和文档是必须有的。
  4. 针对如何让程序员接受并找到新公用库的问题,建立新的source目录,并在文档上通过目录-包结构-类的层次做好文档(javadoc)。
  5. 除了必要的培训之外,主要的审查方式还是通过每天的code diff和定期的code review来做到大家对公用库的认同和理解。
  6. 维护是要靠大家去推动的,就像上面说的,由程序员反馈需求或提供补丁的方式,保证公用库的持续改进。

现在工作进展还算顺利,过段时间再看看效果怎样,或许咨询一下顾问看看有没有改进的地方。

avtivemq和规则引擎资料(旧)

10年时接触activemq和drools时收集的一些资料

activeMQ资料

activeMQ官方网站
JMS消息类型模型
实战activeMQ
ActiveMQ4.1 +Spring2.0的POJO JMS方案(上)整理版
ActiveMQ4.1 +Spring2.0的POJO JMS方案(下)整理版
ActiveMQ5.0实战一: 安装配置ActiveMQ5.0
ActiveMQ5.0实战二: 基本配置
ActiveMQ5.0实战三:使用Spring发送,消费topic和queue消息
ActiveMQ测试报告,这是一个很棒的报告
ActiveMQ 高级特性
Articles on ActiveMQ, Messaging and JMS 来自官网的文章集锦

客户端

关于客户端都可以在相应的官网上找到,这里要说的一个客户端是openamq-jms,简单来说,这是针对java JMS的客户端, 可用于OpenAMQ和其他AMQP服务器。还有一个是StompConnect,当然这是走stomp协议的,特色就是把JMS弄成一个Stomp Broker, 具体是怎样的,还没实践过。不过,要跨语言消息交互,更好的选择是使用已经提供的本地Stomp支持。另外,关于stomp的erlang客户端可以选择gigix推荐的stomperl

书籍

其实官方文档已经非常丰富,书籍的意义并没有那么大~~另一方面,关于ActiveMQ的书籍暂时还没有上市,只有本期书(activeMQ in action), 现在可以在网上找到这本书的手稿版~确实找不到的话,可以找我要,呵

activemq和rabbitmq

activemq和rabbitmq都是非常有名的开源消息队列。activemq是java写的,而rabbitmq是erlang写的。 activemq支持的是stomp和openwire,xmpp等协议,而rabbitmq支持的是amqp(Advanced Message Queuing Protocol)。 stomp是一个简单的容易实现的基于文本的协议,openwire是一种快速的二进制协议,activemq计划在amqp协议1.0版本完成的时候提供amqp协议的支持。 通过这些协议,activemq可以支持多种客户端,如C, C++, C#, Ruby, Python, Perl, PHP等等。而amqp同时定义了消息中间件的语意层面和协议层面, 并且是语言中立的,它可以让MQ成为一个可编程的消息中间件,可以想象这样的场景: 你使用java写的消息通过Erlang编写的中间件来和一个C编写的遗留系统进行消息交互。所以amqp前途是光明的~~

2009-01-15附加

activemq的failover功能配置很简单~例如简单修改brokerURL为”failover:(tcp://caixiaojian.hnjk.com:61616?wireFormat.maxInactivityDuration=0)”,正常情况是会出现下面的信息:

ActiveMQ Task 15/01 16:52:43,672 DEBUG [failover.FailoverTransport]-802 Waiting 5120 ms before attempting connection.
ActiveMQ Task 15/01 16:52:45,569 DEBUG [failover.FailoverTransport]-593 urlList connectionList:[tcp://caixiaojian.hnjk.com:61616?wireFormat.maxInactivityDuration=0]
ActiveMQ Task 15/01 16:52:45,569 DEBUG [failover.FailoverTransport]-712 Attempting connect to: tcp://caixiaojian.hnjk.com:61616?wireFormat.maxInactivityDuration=0
ActiveMQ Task 15/01 16:52:45,570 DEBUG [failover.FailoverTransport]-753 Connect fail to: tcp://caixiaojian.hnjk.com:61616?wireFormat.maxInactivityDuration=0, reason: java.net.ConnectException: Connection refused
ActiveMQ Task 15/01 16:52:45,570 DEBUG [ tcp.TcpTransport]-458 Stopping transport tcp://null:0

2009-01-16附加

结合spring和activemq的时候,发现启动挺慢的。因为老是会去网上寻找xsd文件,把xml里边的schemaLocation换成下面这个样子就可以了: http://activemq.apache.org/schema/core http://activemq.apache.org/schema/core/activemq-core.xsd ActiveMQ的Virtual Destinations和Mirrored Queues非常有价值,5.3新增的Message Groups也是很有用的特性来的

规则引擎资料

Java规则引擎与其API(JSR-94) 介绍jsr94标准
使用 Drools 规则引擎实现业务逻辑 一个简单的例子
在你的企业级java应用中使用Drools 提供web层和dao层的简单继承,不过rule用的xml格式~
drools和spring的集成 集成spring好像是必备功能似的
规则引擎实现探讨 一个小讨论,还看到一段Erlang代码
应用规则引擎组织业务规则的注意要点 里边提到一些建议和规范
看惯中文的,感觉这个blog还行,但是内容是基于drools3的
一个小学题目的解: 采用规则引擎Drools实现 一个喝汽水的智力题,一个比较不错的例子
springside里边用的jboss rules
另外还有drools官方文档,关于最重要的rule language和example部分是在Drools Expert上

其他想法

我对规则引擎理解不深,从drools的例子可以看到一些AI,workflow,专家系统的影子,看似用途很广很强大,可是规则引擎不是这么个用法的把
按照我的想法,规则引擎应该用在局部复杂的流程控制上(局部复杂的if else…),应该是基于业务走向的判断。
如上面yimlin提到的,我觉得规则引擎是来简化工作的,所以要避免任何具有实际意义的业务操作,很容易让人混淆。
怎么说这些规则也不大可能是客户来维护的了,因为可能涉及事实库的修改,dsl顶多也是对程序员友好的方式。
哪些业务规则适合抽象出来由规则引擎处理,规则引擎和业务系统的交互方式是主要问题。

引入规则引擎真的会带来很多好处么?对很多系统来说,这种java应用中嵌入groovy的方案还算满经济的。

maven学习要点(旧)

我感觉maven这东西比ant容易上手(ant每个项目规则都不一样),看那个100页的迷你书+例子(推荐basecrm或spring3.0)基本就ok了~~ 哎,本来想写的很多,发现写出来的却很少。多多包涵!

  • maven是一个项目管理工具,跟ant相比亮点在于它遵守了COC规则,只要遵守就好处多多
  • maven可以分为三部分:maven本身(提供基本功能),仓库(提供jar),maven插件(提供更多的集成功能)
  • maven的配置文件是settings.xml,maven项目的配置文件是pom.xml
  • settings.xml主要是配置仓库镜像,本地仓库,网络访问设置等信息,常用配置:
 <localRepository>D:/repository</localRepository> --本地仓库
   <proxy>  --网络代理
     <id>optional</id>
     <active>true</active>
     <protocol>http</protocol>
     <username>proxyuser</username>
     <password>proxypass</password>
     <host>proxy.host.net</host>
     <port>80</port>
     <nonProxyHosts>local.net|some.host.com</nonProxyHosts>
   </proxy>
  <mirror>  --仓库镜像
    <id>nexus</id>
    <mirrorOf>*</mirrorOf>
    <name>Mirror for maven central.</name>
    <url>http://10.137.27.223:8080/nexus/content/groups/public</url>
  </mirror>
  • pom.xml主要分为三部分:项目本身的信息,使用的插件,依赖的jar
  • 项目本身的信息,用于唯一标识(groupid-artifactId-version, 通常是这3部分),例如
 <modelVersion>4.0.0</modelVersion> --maven3.x都是使用4.0.0
 <groupId>com.huawei.boss</groupId>
 <artifactId>boss-common</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 <packaging>jar</packaging>

类似这个,默认就会被打包成boss-common-0.0.1-SNAPSHOT.jar。同样,项目的依赖库也是通过这种坐标在仓库中寻找的。

  • artifactId通常推荐使用项目名当前缀,当项目是打包成war的时候,通常需要制定发布后的命名:
 <build>
   <finalName>bossbase</finalName>
 </build>
  • 可以通过properties定义一些常量,并通过${spring.version}之类来引用
 <properties>
       <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
       <spring.version>2.5.6</spring.version>
 </properties>
  • jar包依赖常用的有如下配置(仅举例说明):
 <dependencies>
     <dependency>
           <groupId>org.springframework</groupId>
               <artifactId>spring-core</artifactId>
             <version>${spring.version}</version>  --常量引用,在引用同个项目多个模块的时候相当有效
           <type>jar</type>  --默认就是jar
             <scope>compile</scope>  --默认是compile,表示对编译,测试,运行都有效
       </dependency>
    <dependency>
          <groupId>geronimo-spec</groupId>
            <artifactId>geronimo-spec-jta</artifactId> --这个是为了不依赖sun的专用东东
           <version>1.0.1B-rc4</version>
                <type>pom</type>
          <scope>provided</scope>  --在运行的时候不依赖,我们常见的时候j2ee规范之类的api,如servlet-api
  </dependency>
       <dependency>
             <groupId>junit</groupId>
               <artifactId>junit</artifactId>
           <version>4.8.2</version>
             <type>jar</type>
               <scope>test</scope>  --只在测试的时候生效,另外还有个ruuntime的scope,用于运行时依赖(如jdbc驱动等)
      </dependency>
   <dependency>
     <groupId>org.hibernate</groupId>
     <artifactId>hibernate</artifactId>
     <version>3.2.5.ga</version>
     <exclusions>  --这里是排除依赖,因为jta官方版是在maven仓库中找不到的
       <exclusion>
         <groupId>javax.transaction</groupId>
         <artifactId>jta</artifactId>
       </exclusion>
     </exclusions>
   </dependency>
 </dependencies>
  • 当项目是分模块进行的时候,通常会考虑使用modules,如:
   <modules>
       <module>basecrm-parent</module>
       <module>common</module>
       <module>systemmgr</module>
       <module>prodmgr</module>
   </modules>

这个时候至少会有两级pom.xml,这样像properties,dependency等都是可以继承的(可参考basecrm项目或者spring3.x)

  • plugin资源非常多,简单举几个常用插件(配置请上网找)
    maven-compiler-plugin 主要是配置编译方面的选项(例如使用什么版本的jdk)
    maven-surefire-plugin 跟测试有关的,例如配置测试失败是否继续,是否跳过测试等
    build-helper-maven-plugin 用于配置项目的目录结构,例如配置多个源代码目录等
    maven-shade-plugin 我只知道可以用来给打包加点料~~
    tomcat-maven-plugin 顾名思义,用于集成tomcat
    maven-jetty-plugin 顾名思义,用于集成jetty

  • others
    maven仓库代理推荐nexus(网内已经有个现成的:http://10.137.27.223:8080/nexus)
    eclipse中的maven插件推荐m2eclipse(update-site: http://m2eclipse.sonatype.org/sites/m2e)
    maven实战迷你书(http://www.infoq.com/cn/minibooks/maven-in-action)