SOA实践之:Java服务接口设计的一些实践准则
在SOA领域,一些OO领域的接口设计原则具有很好的借鉴意义,但随着SOA化带来的分布式特性,有些东西就需要进行特殊处理。下面就我们进行SOA化过程中接口设计过程中,曾经比较容易出现过问题的地方进行部分总结。
【术语说明】
-
服务与业务组件的定义:
- 业务组件:最小的业务逻辑单元,例如:密码验证组件、登录组件。
- 服务:业务模块提供给外部的核心业务逻辑,服务组合业务组件。例如:修改密码服务,会使用密码组件的验证服务,也会使用登录组件的修改密码服务。
【说明:】可否密码验证组件的功能合并到登录组件哪?可以的,此处之所以单独分离,与我们提供多种密码验证机制有关,登录组件对应的业务并未完全涵盖密码验证组件的功能。
- 服务接口的方法,禁止重名(overload特性)。大多数序列化工具【例如:XFire】对重名方法进行处理时,都不可能避免的存在或多或少的问题。为了减少麻烦,还是放弃OO的这一特性,或者在面向服务领域,重载本身就不是一个特性。
- 服务接口的方法,避免返回异常【我们是禁止异常,包括业务异常、系统异常】。这个要求看起来违反了一些对象设计的原则,从实际使用角度来看,具有如下好处:很多工具对异常序列化很容易出现错误;一不小心,外部使用者还会产生异常依赖;需要外部使用者自己把异常翻译为业务语义【从业务角度来说不是件好事情】。
- 服务接口必须有清晰的文档说明,对于常量性质的参数,要列出支持的常量值。
- 服务接口尽可能把查询和业务方法分离【非必须】。好处是使用AOP时简单方便,对于查询方法进行特殊处理时也比较方便,例如:与cache整合、与搜索引擎整合、与特殊权限要求进行整合(例如,对返回信息按照权限进行过滤)等。
- 服务接口参数排列按照如下原则:操作主体对象或ID,业务参数{…},环境参数。
例如:verifyPassword(String operatorId, String password, ServiceContext serviceContext)
operatorId是操作主体的ID。
Password是密码。
serviceContext是环境参数。 - 业务处理类接口,返回值可以采用统一对象模式。例如:ServiceResult:
public class ServiceResult<T> implements Serializable {
private static final long serialVersionUID = 760567785564620157L;
private boolean isSuccess;
private String resultCode;
private T resultObject;
public boolean isSuccess() {
return isSuccess;
}public void setSuccess(boolean isSuccess) {
this.isSuccess = isSuccess;
}public String getResultCode() {
return resultCode;
}public void setResultCode(String resultCode) {
this.resultCode = resultCode;
}public T getResultObject() {
return resultObject;
}public void setResultObject(T resultObject) {
this.resultObject = resultObject;
}
}
采用这种模式,实际是按照协议的角度进行设计,所有服务按照统一的包装模式返回。 - 服务接口的参数避免使用Object类型,集合类型要指定具体对象类型。原因:当使用Object时,使用者获取到WSDL文件时,不知道该传递什么业务数据,如果是其它语言的调用者,更无法理解了;这类接口设计违反了业务SOA的原则,暗含着如下潜台词:业务上不明确的,不清晰的。
- 服务接口避免使用集合类型,尽可能使用数组类型。
- 服务接口中使用枚举的原则:如果是对外界发布,避免使用枚举;如果使用者众多,避免使用枚举;如果是内部几个专用系统使用,可以考虑使用枚举,可以参考《枚举产生的系统耦合性》。【文档的清晰化,可以化解常量参数不明确的问题】
- 服务接口避免设计成面向数据的操作,例如:updateStatus。这类接口,通常包含太多的业务含义,甚至没有业务含义,需要外部使用者去理解业务内部的细节,否则无法进行操作。这也是我们设计服务接口时,需要特别注意的地方,不要让外部使用理解业务内部是如何存储的,结构是什么样的,这样会导致业务耦合度过高,服务内部发生重构,变化时,外部使用也需要进行变化。
- 服务接口层建议设计一个统一的模板框架,来处理性能分析、特殊参数处理(例如:ServiceContext)、事务处理、异常处理、日志处理、事件处理、审计处理等具有共性的行为。
- 服务接口的方法名避免以:is,get开头,用find,check等来代替之,避免Java序列化工具把该类当作Java Bean进行处理。
- 业务组件中异常的要求:所有的业务组件,如果发现违法业务原则的地方,需要中断程序的执行,那么throw统一业务异常,该业务异常可以借鉴如下方式设计:
public class ComponentException extends Exception {private static final long serialVersionUID = -6900280471536579015L;
private ErrorCodeEnum code = null;
public ComponentException ErrorCodeEnum code) {
super();
this.code = code;
}public ComponentException ErrorCodeEnum code, Throwable e) {
super(e);
this.code = code;
}public ErrorCodeEnum getCode() {
return code;
}public void setCode(ErrorCodeEnum code) {
this.code = code;
}
}坏处:从异常名看不出明确的业务语义。
好处:避免服务层需要捕获多种异常;业务语义通过ErrorCodeEnum返回到业务层。 - 业务组件内部尽可能少的进行事务处理【除非有特殊的需求】、异常处理【除外业务组件自身组合了外部的业务服务,此时需要处理异常】。
好处:统一由服务层进行处理,可以避免事务处理的错误。不进行异常处理,可以避免异常转义时,出现信息丢失;此外,如果使用了统一异常,那么就更不能在业务组件内部进行异常处理了,否则会发生原始业务限制规则被丢失的可能。
对于一些基本原则,可以参考《SOA实践之:在程序层,面向服务的设计准则探讨》一文。
使用方,推荐使用代理模式来使用外部服务。
1、可以进行一些特殊处理,例如:网络异常处理,远程性能评估等
2、对内部提供重业务层面的方法,屏蔽环境参数
3、外部服务接口发生变化时,或许修改一处就能够满足变化
4、如果要对传输层、安全进行定制时,会非常方便
【参考资料】
模块分解原理的探索
Interface Design — Best Practices in Object-Oriented API Design in Java
Part 1: Exploring the development, interfaces, and operation semantics of services
Part 2: Exploring the development, interfaces, and operation semantics of services
4 Responses to “SOA实践之:Java服务接口设计的一些实践准则”
By samliwa on Aug 13, 2008 | Reply
您好,能不能详细谈谈关于异常处理的细节,比如您上面说到的避免返回异常!感觉异常处理是一个比较重要的方面。这方面不知道作者能不能单独开博来谈谈自己在这方面的经验。谢谢
By 邓芝 on Aug 14, 2008 | Reply
在异常处理这方面,我们也在不断探索和实践。此外内部、外部(对外开放部分)是否采用相同的机制,也值得考虑。我会即时把这方面的一些成果反馈到BLOG上。
By samliwa on Aug 15, 2008 | Reply
好的,thanks for reply.
I am looking forward to the excellent articles.