级别: 中级 Mikhail Genkin (genkin@ca.ibm.com), 认证 IT 架构师, IBM
2007 年 12 月 20 日 在本文中,我们将了解如何在 SOA 环境中使用服务接口来报告服务错误。面向服务的体系结构(Service-Oriented Architecture,SOA)强调企业内不同系统间的松散耦合。应用程序彼此间的通信只能通过设计良好的服务接口进行,并不会考虑彼此的实现。服务接口结构在 SOA 中是最重要的。设计糟糕的服务接口可能会对需要使用其的所有应用程序造成负面影响。通过本文可了解设计良好的服务接口如何帮助加速项目计划的执行,从而提高您的 SOA 解决方案对业务需求的响应能力。
引言
本系列的第 1 部分重点讨论了服务接口设计的最佳实践,包括设计与开发方法、服务粒度、同步设计与异步设计的比较以及操作签名。
在本文中,将重点介绍相关最佳实践,以说明服务应该如何向服务使用者应用程序报告错误。除了成功响应外,生产质量级的系统还必须能够处理各种错误情况。服务向服务使用者应用程序报告错误的方式会影响服务接口的结构,从而也会影响服务使用者应用程序的构造方式。
用于报告错误的 WSDL 构造
第 1 部分讨论了描述服务接口的最佳方式就是使用 Web 服务描述语言(Web Services Description Language,WSDL)。
WSDL 规范包括如下规定:
- 服务接口(端口类型)包含一系列操作。
- 每个操作可以为请求-响应型,也可以为单向操作。
- 每个请求-响应操作可以定义单个输入消息、单个响应消息和任意数量的错误消息。
- 单向操作不能定义响应消息或错误消息。
WSDL 规范还进一步定义消息可以包括一个或多个部分。每个部分的值可以为简单 XSD 类型,也可以为用户定义的复杂类型。请参见参考资料,其中提供了指向 WSDL 规范的链接。
错误类型
服务需要报告的错误实际上可归入两类:
- 系统级别
- 表示属于服务实现的一部分的运行时软件组件、硬件和网络通信协议的错误。这些错误代表不与所执行的业务逻辑或数据相关的错误。
例如,如果服务实现需要访问的 RDBMS 服务器崩溃,由于应用程序服务器无法找到该服务器的 Java™ Database Connectivity (JDBC) 连接,因此您的服务器无法处理请求消息,则将出现典型的系统级错误。
- 业务级别
- 由于服务违反业务逻辑或数据相关规则而引发的错误。例如,如果请求消息表示请求预订旅程,而回程日期字段的日期早于出发日期,则服务实现将可能引发业务级别的错误。
|
最佳实践:对于具有请求-响应操作的同步体系结构,请在操作签名中定义系统和业务错误。应该使用不同的 XSD 类型描述系统错误和业务错误。 | |
|---|
服务使用者应用程序通常需要以不同的方式响应系统级别和业务级别的错误。对于系统级别的错误,服务使用者应用程序可能希望等待并使用原始数据再次尝试请求。对于业务级别的错误,应用程序可能希望将消息发送回最终用户,要求对输入数据进行更正。
在请求-响应操作中使用 WSDL 错误
错误仅能与请求-响应操作一起使用。图 1 显示了可以如何设计 WSDL 端口类型的结构,以向客户机应用程序返回业务级别和系统级别的错误。此操作基于经常使用的 StockQuote 示例。StockQuote 服务包含单个 StockQuote 端口类型(接口)。此端口类型包含名为 getQuote() 的操作。此操作接收作为 getQuoteRequest 消息的一部分的输入消息。结果作为 getQuoteResponse 消息的一部分返回。
图 1. 示例端口类型
getQuote() 操作还定义了另外两个消息,用于向服务使用者应用程序返回错误。
-
getQuoteBusinessFault 返回与不正确使用服务相关的错误,如服务使用者应用程序提供了未知或空的股票代码。
-
getQuoteSystemFault 返回系统导致的错误,如数据连接问题等。
按照第 1 部分中所述的最佳实践,在独立的 XSD 文件中定义错误类型。此 XSD 包含复杂类型,用于表示 SystemError 和 BusinessError。
|
最佳实践:错误消息应该包含单个 WSDL 部分。此部分的值是复杂 XSD 类型,其中包含错误的完整描述。 | |
|---|
上面 图 1 中的示例端口类型显示了使用错误返回错误的请求-响应操作。此操作的结构按照 IBM® WebSphere® Integration Developer 6.0 或 IBM Rational® Application Developer 6.0 及 7.0 中提供的 WSDL 编辑器中所示的方式显示。
清单 1 显示了 XSD 文件中定义的复杂类型的结构。您可以下载示例代码,其中说明了复杂错误类型的定义方式。BusinessErrorType 和 SystemErrorType 均通过扩展公共基类型 BaseErrorType 派生而来。BaseErrorType 定义两个元素:
faultName 和 message 元素对可以由 getQuote 操作引发的任何错误类型都是通用的,因此定义为所有错误的基类型。
清单 1. ErrorTypes.xsd 摘录
<complexType name="BaseErrorType" abstract="true">
<sequence>
<element name="faultName"
type="string" minOccurs="1" maxOccurs="1">
</element>
<element name="message"
type="string" minOccurs="0" maxOccurs="1">
</element>
</sequence>
</complexType>
<complexType name="BusinessErrorType">
<complexContent>
<extension base="tns:BaseErrorType">
<sequence>
<element name="errorCode" type="tns:BusinessErrorCodeType"
minOccurs="1" maxOccurs="1">
</element>
</sequence>
</extension>
</complexContent>
</complexType>
<complexType name="SystemErrorType">
<complexContent>
<extension base="tns:BaseErrorType">
<sequence>
<element name="errorCode" type="tns:SystemErrorCodeType"
minOccurs="1" maxOccurs="1">
</element>
<element name="originatingError" type="string"
minOccurs="1" maxOccurs="1">
</element>
<element name="trace" type="string" minOccurs="0"
maxOccurs="unbounded"></element>
</sequence>
</extension>
</complexContent>
</complexType>
|
SystemError 和 BusinessError 类型都定义 errorCode 元素。此元素包含一个字母数字代码,唯一地标识服务可以返回的每个错误类型。服务将字母数字代码与描述错误的文本信息一起返回非常重要,因为客户机应用程序(服务使用者)可能会希望以编程方式检查此代码,并以不同的方式定向后续调用(根据错误代码所指示的错误类型)。
|
最佳实践:错误消息应该包含错误代码,以允许服务使用者应用程序(客户机)以编程方式处理错误。应该将错误代码作为 WSDL 服务定义不可或缺的部分发布,以便于服务使用者应用程序理解所报告的错误的含义。 | |
|---|
清单 2 显示了用于将错误代码传递到服务使用者应用程序的简单类型的定义。两个简单类型 BusinessErrorCodeType 和 SystemErrorCodeType 都通过限制 (restriction) 派生自 xsd:string 类型。限制中的枚举定义可以为业务级别错误和系统级别错误返回的错误代码。XSD 中嵌入的注释可为服务使用者(客户机)应用程序开发人员描述错误代码的含义。
清单 2. 定义
<simpleType name="BusinessErrorCodeType">
<restriction base="string">
<enumeration value="SQ00010"/>
<!-- Invalid symbol string format -->
<enumeration value="SQ00020"/>
<!-- Symbol not found -->
</restriction>
</simpleType>
<simpleType name="SystemErrorCodeType">
<restriction base="string">
<enumeration value="SQ001001"/>
<!-- Authentication error -->
<enumeration value="SQ001002"/>
<!-- Data base connection not available -->
</restriction>
</simpleType>
|
此示例将业务级别的错误和系统级别的错误归入不同的类型,以明确地对二者进行区分,而且可供服务使用者应用程序方便地以编程方式处理错误代码(如果必要)。在很多情况下,只需定义单个简单类型,就足以包含所有描述业务级别和系统级别的错误语义的错误代码。不过,将业务和系统错误代码分组到独立的编号段的做法(例如,业务级别的错误从 00010 到 00100,而系统级别的错误从 001000 到 010000)非常明智,可便于对这些代码进行编程处理。
对于即使在例外路径上性能也非常关键的情况,您可能会希望将错误代码字段设置为 int 或 short 类型,因为数字型字段比字符串字段处理起来更快。
|
最佳实践:应该将系统和业务错误的数字值分组为不同的段(请参见下载),以便服务使用者应用程序代码以编程方式进行检查。 | |
|---|
SystemErrorType 类型包含可选元素,可用于捕获关于导致问题的错误的信息。例如,StockQuote 服务可能在尝试与后端数据库进行通信时捕获 java.sql.SQLException。为了对此进行响应,我们的服务将构造并引发 SystemErrorType。SytemErrorType 应该包含原始异常的名称,还可以选择性地提供导致问题的 java.sql.SQLException 异常中的额外消息堆栈跟踪信息。
在大多数情况下,如果服务使用者和服务提供者应用程序属于相同的组织,服务开发人员可能会选择不向服务使用者应用程序报告原始错误。这是一个非常好的做法,可促进问题确定和解决。
在操作响应中返回错误
使用 WSDL错误的另一个方法是将错误信息作为常规响应消息的一部分返回。应用程序开发人员趋向于采用此方法,特别在涉及到使用 Web 服务进行批处理时。这种情况下的批处理应用到的操作的输入消息包含多个不同的输入,而响应消息可能包含多个不同的结果(每个均与某个输入对应)。例如,StockQuote 服务的输入消息可能包含多个股票代码,而响应消息也将包含多个结果。此方法具有一系列优缺点。(本系列的后续文章将更详细地讨论与批处理相关的问题。)
在非错误响应中返回错误代码的主要好处在于,此方法允许服务使用者应用程序(客户机)按照与处理非错误响应相同的方式处理错误信息。图 2 显示了如何采用此方式返回错误信息。
这样,客户机应用程序就可以获取 Web 服务返回的响应 Java 对象,并直接将其传递到表示层(通常为 JavaServer Pages),以向最终用户显示。这就避免了创建用于将服务使用者应用程序的表示层与 Web 服务层分离的转换对象的过程中涉及到的额外编程工作。
图 2. 作为响应消息的一部分返回错误信息的单向操作与请求-响应操作
此方法的主要缺点有:
由于存在这些缺点,因此从请求-响应操作返回错误和消息的最佳做法是依赖于 WSDL 错误构造,并使用前面描述的方法返回错误。
在异步拓扑中返回错误
WSDL 规范(请参见参考资料)并不允许为单向操作定义错误。不过,遵照异步模型构建的应用程序的确需要访问错误信息的功能。
可以采用以下三种方式之一设计异步调用的结构:
-
无响应的单向异步调用
发布-订阅交互就是此方式的一个示例。当应用程序实现没有响应的单向异步调用时,由于 WSDL 规范不允许为所使用的单向操作定义错误,因此没有办法向服务使用者应用程序提供错误反馈。服务使用者应用程序也对稍后获取响应没有兴趣,因此没有办法使用响应消息向客户机提供错误信息。在此情况下,您必须非常细心地处理基础设施配置,以确保请求消息不会在发往预期目的地的过程中丢失。
-
稍后发出响应请求的异步调用
涉及到执行两个请求-响应操作(虽然大部分处理工作都是由目标服务以异步方式执行)。例如:
- 服务使用者应用程序调用
submitStockQuoteRequest() 操作。此操作向服务使用者应用程序返回事务确认编号。
-
stockQuoteService 实现执行股票值的查询操作。
- 服务使用者应用程序稍后调用
getStockQuoteResponse() 操作,并传入 submitStockQuoteRequest() 返回的事务编号。此操作返回的响应操作包含股票值结果和错误信息(如果有)。
图 3 显示了在这种情况下应该如何设计操作签名的结构。请求操作可能会导致系统错误。这些错误仅仅传递与输入消息交互及异步处理初始化相关的错误信息。用于检索响应消息的操作应该按照上面所述的常规请求-响应操作的方式引发系统和业务错误。
图 3. StockQuoteAsynch 端口类型的结构
-
使用回调返回请求的异步调用
服务使用者应用程序提供相关信息,以允许服务将响应消息发送回客户机(服务使用者)应用程序定义的接口。如果在异步处理阶段发生业务错误,服务实现将在查询响应消息时引发业务错误。
|
最佳实践:当使用请求-响应操作进行异步处理时,在请求操作上仅定义系统错误,而在用于检索响应消息的操作上同时定义系统级别和业务级别的错误。 | |
|---|
处理客户机应用程序中的错误
清单 3 显示了一个示例,说明了客户机应用程序将如何处理 StockQuote 所引发的业务和系统错误。
Java API for XML-based RPC (JAX-RPC) 和 Java API for XML Web Services (JAX-WS) 规范说明了 WSDL 和 XSD 所描述的类型如何转换为客户机所使用的 Java 对象。由于我们定义了不同的复杂类型来表示这些错误,因此 Rational Application Developer Web 服务工具会生成不同的 Java Bean 来表示它们。这些 Bean 派生自 Java Exception 类,可以在不同的 catch 块中进行处理。
|
最佳实践:调用 Web 服务时,捕获所有已声明的错误,并在独立的 catch 块中处理系统和业务错误。 | |
|---|
客户机应用程序调用 Web 服务时,必须处理生成的代理的方法签名中定义的异常。除了这些异常外,客户机堆栈还可能会引发与序列化和通信相关的其他错误。通常,除了处理代理方法签名中定义的异常外,最好也对基础 java.lang.Throwable 进行处理。这可确保客户机应用程序代码足够强壮,能够处理客户机为了响应各种情况(如格式存在问题的响应消息——序列化类型错误)而引发的非预期异常。
清单 3. 请求-响应处理的客户端错误处理示例
public class SQFacade {
private int retryCount = 0;
private StockQuoteProxy sqProxy = new StockQuoteProxy();
public String getQuote(String symbol) throws UIError
{
String result = null;
UIError error = new UIError();
try {
result = sqProxy.getQuote(symbol);
} catch (BusinessErrorType e) {
// Check the code and return appropriate action to user
BusinessErrorCodeType errorCode = e.getErrorCode();
if (errorCode != null)
{
if (errorCode.getValue().equals(BusinessErrorCodeType._SQ00010))
{
error.setMessage("Invalid input format!");
error.setAction("Please re-enter the symbol value correctly.");
}
else if(errorCode.getValue().equals(BusinessErrorCodeType._SQ00020))
{
error.setMessage("Stock symbol not found!");
error.setAction("Please enter a different stock symbol.");
}
else // Just in case!
{
error.setMessage("User error!");
error.setAction("Please contact site administrator.");
}
}
else // Just in case!
{
error.setMessage("User error!");
error.setAction("Please contact site administrator.");
}
throw error;
}
catch(SystemErrorType e)
{
// Check the code and return appropriate action to user
SystemErrorCodeType errorCode = e.getErrorCode();
if (errorCode != null)
{
if (errorCode.getValue().equals(SystemErrorCodeType._SQ001001))
{
error.setMessage("Authentication problem!");
error.setAction("Please please contact the site administrator.");
}
else if(errorCode.getValue().equals(SystemErrorCodeType._SQ001002))
{
// Retry 3 times to see if db connection problem clears up
if (retryCount < 3)
{
retryCount++;
this.getQuote(symbol);
}
retryCount = 0;
error.setMessage("Data access problems!");
error.setAction("Please try again at a later time.");
}
else // Just in case!
{
error.setMessage("System error!");
error.setAction("Please contact site administrator.");
}
}
else // Just in case!
{
error.setMessage("System error!");
error.setAction("Please contact site administrator.");
}
throw error;
}
catch (Throwable e){
e.printStackTrace();
error.setMessage("System error!");
error.setAction("Please contact site administrator.");
throw error;
}
return result;
}
}
|
|
最佳实践:捕获系统和业务错误时,还要捕获基础错误类型 java.lang.Throwable,以处理客户机堆栈引发的任何非预期错误情况。 | |
|---|
清单 3 显示了实现本文所述最佳实践的一个方法。我们假想的客户机应用程序定义了一个名为 SQFacade 的简单 facade 类,其中包含 getQuote() 方法。此方法的签名与 Web 服务代理定义的 getQuote() 方法的签名非常类似,但会引发不同的简单错误类型 (UIError),用于将 UI 代码与生成的代理代码区分开来,从而返回对用户更有意义的信息。
SQFacade 类封装了异常处理逻辑。在出现系统级别数据库连接问题时,它会使用递归方法三次重新尝试进行调用。
总结
Web 服务提供用于实现 SOA 的理想技术。在本文中,我们了解了关于服务应该如何向服务使用者应用程序报告错误信息的最佳实践。在执行请求-响应处理时,应用程序应该尽量利用 WSDL 规范所提供的错误机制,而在执行异步调用时,应尽量使用响应消息来报告错误。
下载 | 描述 | 名字 | 大小 | 下载方法 |
|---|
| Error handling code | ar-servdsgn2code.zip | 3,113KB | HTTP |
|---|
参考资料 学习
获得产品和技术
讨论
关于作者  | 
|  | Mikhail Genkin 是 IBM Integrated Software and Services for WebSphere 的一位认证 IT 架构师。他与主要 IBM 客户协作,帮助他们使用最新的 IBM 产品实现业务集成解决方案和面向服务的体系结构。他还参与了 VisualAge for Java 企业版、WebSphere Application Server 企业版和 WebSphere Application Developer 集成版、WebSphere Business Integration Server Foundation 以及 WebSphere Process Server 的多个版本发布工作。Mikhail 撰写了很多关于 Web 服务、Java Connector Architecture 和流程编排的行业文章,而且经常参加各种业界会议。 |
对本文的评价
|