接触过非常多电商的WebService,有种一看就蛋疼的设计,今天要从这个反例说一说 WebService 的设计。

[WebMethod]
public string QueryOrderDetail(string xml)
{
...
}

如上代码输入是一个XML,输出也是一个XML,方法内部自己在做序列化和反序列化。放着成熟的SOAP标准不用,自己再实现一套数据标准。
反而XML成为一个黑盒,调用两方不得不依赖于接口文档,真是吃力不讨好。

因此好的WebService接口,应该从以下几个方面细致考虑:

一. 參数
(1) 參数应该直接使用简单的数据类型(POCO、POJO),甚至时间类型都能够考虑用string,仅仅要两方约束好时间字符串的格式。
(2) 假设參数个数超过3个,那就须要考虑设计一个Class了,避免參数列表过长,当然这没有硬性规定。
(3) 设计统一的參数规则。比方对外提供的查询接口就要考虑分页相关的数据。保证相似的接口都有统一的參数定义,形成习惯是提升效率最好方式。
      业务參数和非业务參数应该分开,比方分页的数据就能够抽象出基类。

二. 异常
(1) 使用框架中定义的Exception类型,比方:SoapException, FaultException(WCF)。
(2) 尽量避免将异常定义在返回值中,通过返回值定义错误那么不管服务端还是client都要写非常多if ... else 分支。
(3) 系统异常和业务异常要区分好,比方使用 SoapException 能够用 Code 来区分,比方:System.Error 表示系统错误,Bussiness.Error 表示业务错误。
(4) 补充:.net framework  假设没有包装那么默认有两种 fautCode:  soap:Client 和 soap:Server。假设client传入BadRequest 基本就是 soap:Client 错误,其它 没有自己定义code的则就是 soap:Server 的错误。

三. 安全
不管何时都要保证系统的安全性,我认为安全也分系统安全和业务安全两种:
(1) 系统安全主要是指client的认证授权,调用次数(须要考虑会不会拖垮业务系统) 等
(2) 业务安全主要是指数据查询/操作权限,当然这个主要是从业务角度考虑的。

四. 日志
日志能够方便排查错误,还能够通过日志来分析服务基本信息(比方:调用次数,失败次数等),必要时还能够通过日志来进行重试。
另外要考虑开发的便捷,设计统一的日志拦截处理。

以 WebService Application (.NET 3.5) 为例,记录几种经常使用的编程技巧。
原始的 WebService 例如以下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Services;
using WebService1.Entity;
using WebService1.Service;
using System.Web.Services.Protocols; namespace WebService1
{
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
public class Service1 : System.Web.Services.WebService
{
[WebMethod]
public PageResult<Order> QueryOrder(Query<OrderCondition> queryInfo)
{
OrderService service = new OrderService();
return service.Query(queryInfo);
}
}
}

PageResult<T>, Query<T>  将统一的业务部分抽取出来,这样定义其它的业务对象就能简化了。

using System;
using System.Collections.Generic; namespace WebService1.Entity
{
[Serializable]
public class PageResult<T>
{
public int PageNo { get; set; }
public int PageSize { get; set; }
public int TotalCount { get; set; }
public int PageCount { get; set; }
public bool HasNextPage { get; set; }
public List<T> Data { get; set; }
}
}
using System;
using System.Collections.Generic; namespace WebService1.Entity
{
[Serializable]
public class Query<T>
{
public int PageNo { get; set; }
public int PageSize { get; set; }
public T Condition { get; set; }
}
}

跳过业务处理部分,来关注一下应用框架考虑的日志和安全拦截。能够利用 .NET framework 的 Soap Extensions (msdn)  非常easy地实现对 WebMethod 的 AOP。
Soap Extensions 能够通过两种方式“注入”: 自己定义Atrribute 或者通过 Web.config 里的 soapExtensionTypes 进行声明。

TraceExtension 的实现:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.IO;
using System.Web.Services.Protocols;
using log4net;
using System.Xml; namespace WebService1.Common
{
public class TraceExtension : SoapExtension
{
private ILog logger = LogManager.GetLogger(typeof(TraceExtension)); Stream oldStream;
Stream newStream; public override System.IO.Stream ChainStream(System.IO.Stream stream)
{
oldStream = stream;
newStream = new MemoryStream();
return newStream;
} public override void ProcessMessage(SoapMessage message)
{
switch (message.Stage)
{
case SoapMessageStage.BeforeDeserialize: log4net.ThreadContext.Properties["ip"] = HttpContext.Current.Request.UserHostAddress;
log4net.ThreadContext.Properties["action"] = message.Action; WriteInput(message);
break;
case SoapMessageStage.AfterDeserialize:
break;
case SoapMessageStage.BeforeSerialize:
break;
case SoapMessageStage.AfterSerialize:
WriteOutput(message);
break;
default:
throw new Exception("Invalid Stage");
}
} public override object GetInitializer(Type serviceType)
{
return null;
} public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attr)
{
return null;
} public override void Initialize(object initializer)
{
//filename = (string)initializer;
} public void WriteOutput(SoapMessage message)
{
string soapString = (message is SoapServerMessage) ? "SoapResponse" : "SoapRequest";
string content = GetContent(newStream);
// 为了Format XML,假设从性能考虑应该去掉此处的处理
if (!string.IsNullOrEmpty(content))
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(content);
using (StringWriter sw = new StringWriter())
{
using (XmlTextWriter xtw = new XmlTextWriter(sw))
{
xtw.Formatting = Formatting.Indented;
xmlDoc.WriteTo(xtw);
content = sw.ToString();
}
}
} logger.Info(soapString + ":\n" + content); Copy(newStream, oldStream);
} public void WriteInput(SoapMessage message)
{
Copy(oldStream, newStream); string soapString = (message is SoapServerMessage) ? "SoapRequest" : "SoapResponse";
string content = GetContent(newStream);
logger.Info(soapString + ":\n" + content);
} void Copy(Stream from, Stream to)
{
TextReader reader = new StreamReader(from);
TextWriter writer = new StreamWriter(to);
writer.WriteLine(reader.ReadToEnd());
writer.Flush();
} string GetContent(Stream stream)
{
stream.Position = 0;
TextReader reader = new StreamReader(stream);
string content = reader.ReadToEnd();
stream.Position = 0;
return content;
} } }

TraceAttribute 实现例如以下:

using System;
using System.Web.Services.Protocols; namespace WebService1.Common
{
[AttributeUsage(AttributeTargets.Method)]
public class TraceAttribute : SoapExtensionAttribute
{
private int priority = 0;
public override Type ExtensionType
{
get { return typeof(TraceExtension); }
} public override int Priority
{
get { return priority; }
set { priority = value; }
}
}
}

当中 TraceExtension 利用 log4net 来记录调用 WebMethod 的Request 和 Response,还包含 ip 和 Action(Action事实上相应的 WebMethod)
相应的 log4net 配置例如以下:

	<log4net>
<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
<param name="File" value="F:\Programming\VSProject2008\WebServiceSample\WebService1\WebService1\Logs\service.log"/>
<param name="DatePattern" value=".yyyy-MM-dd'.log'" />
<param name="AppendToFile" value="true"/>
<param name="MaxSizeRollBackups" value="10"/>
<param name="MaximumFileSize" value="5MB"/>
<param name="RollingStyle" value="Date"/>
<param name="StaticLogFileName" value="false"/>
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="%d [%t] %-5p [%property{ip}] [%property{action}] - %m%n"/>
</layout>
</appender>
<root>
<level value="DEBUG"/>
<appender-ref ref="RollingFileAppender"/>
</root>
</log4net>

那么 WebMethod 仅仅要加上 [Trace] 特性,就能够开启日志记录功能。

        [WebMethod]
[Trace]
public PageResult<Order> QueryOrder(Query<OrderCondition> queryInfo)
{
OrderService service = new OrderService();
return service.Query(queryInfo);
}

输出日志例如以下:

2014-05-25 22:05:02,292 [8] INFO  [127.0.0.1] [http://tempuri.org/QueryOrder] - SoapRequest:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tem="http://tempuri.org/">
<soapenv:Body>
<tem:QueryOrder>
<!--Optional:-->
<tem:queryInfo>
<tem:PageNo>1</tem:PageNo>
<tem:PageSize>1</tem:PageSize>
<!--Optional:-->
<tem:Condition>
<!--Optional:-->
<tem:StartTime>?</tem:StartTime>
<!--Optional:-->
<tem:EndTime>?</tem:EndTime>
<!--Optional:-->
<tem:ShopId>?</tem:ShopId>
<!--Optional:-->
<tem:ProductId>?</tem:ProductId>
</tem:Condition>
</tem:queryInfo>
</tem:QueryOrder>
</soapenv:Body>
</soapenv:Envelope> 2014-05-25 22:05:02,357 [8] INFO [127.0.0.1] [http://tempuri.org/QueryOrder] - SoapResponse:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<QueryOrderResponse xmlns="http://tempuri.org/">
<QueryOrderResult>
<PageNo>1</PageNo>
<PageSize>1</PageSize>
<TotalCount>3</TotalCount>
<PageCount>1</PageCount>
<HasNextPage>false</HasNextPage>
<Data>
<Order>
<Id>1</Id>
<OrderDate>2014-05-25 22:05:02</OrderDate>
<ShopId>SHOP001</ShopId>
<ProductId>PRD001</ProductId>
<Quantity>1</Quantity>
<Price>59</Price>
</Order>
...
</Data>
</QueryOrderResult>
</QueryOrderResponse>
</soap:Body>
</soap:Envelope>

接下来利用 SoapHeader 实现最主要的 Basic Authentication 校验,当然你不想每个 WebMethod 去做相同的Check,相同我们实现一个 Soap Extension。

Authentication (SoapHeader) 的定义:

using System;
using System.Web.Services.Protocols; namespace WebService1.Common
{
public class Authentication : SoapHeader
{
public string UserName { get; set; }
public string Password { get; set; }
}
}

AuthCheckExtension 的实现:在 SoapMessage AfterDeserialize 这个阶段,取出client传的 SoapHeader 验证 UserName 和 Password 在服务端是否存在。
假设不存在或者错误则抛出 no auth ! 的错误。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.IO;
using System.Web.Services.Protocols;
using WebService1.Config; namespace WebService1.Common
{
public class AuthCheckExtension : SoapExtension
{
public override void ProcessMessage(SoapMessage message)
{
if (message.Stage == SoapMessageStage.AfterDeserialize)
{
foreach (SoapHeader header in message.Headers)
{
if (header is Authentication)
{
var authHeader = header as Authentication;
var isValidUser = true;
var users = AuthConfiguration.AuthSettings.Users;
if (users != null && users.Count > 0)
{
isValidUser = users.Any(u => u.UserName == authHeader.UserName && u.Password == authHeader.Password);
} if (!isValidUser)
throw new BizException("no auth !");
}
}
}
} public override object GetInitializer(Type serviceType)
{
return null;
} public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
{
return null;
} public override void Initialize(object initializer)
{
// 初始化 AuthSettings
AuthConfiguration.Config();
}
} }

然后给 WebMethod 加上 [SoapHeader("Authentication"), AuthCheck] 就OK了。

using System;
using System.Collections.Generic;
using System.Web;
using System.Web.Services;
using WebService1.Entity;
using WebService1.Service;
using System.Web.Services.Protocols;
using WebService1.Common; namespace WebService1
{
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
public class Service1 : System.Web.Services.WebService
{
public Authentication Authentication { get; set; } [WebMethod]
[Trace]
[SoapHeader("Authentication"), AuthCheck]
public PageResult<Order> QueryOrder(Query<OrderCondition> queryInfo)
{
OrderService service = new OrderService();
return service.Query(queryInfo);
} }
}

最后我们拿 SoapUI 来測试一下:

再来看看错误处理,假设有益输错 UserName:

顺便要赞一下 SoapUI,真是 WebService 调试的利器,还能够生成 .NET / Java 代码,推荐大家使用。我们用 SoapUI 生成一下 Java 代码。
Java client我决定用 CXF 来实现。所以要先配置一下 SoapUI:

JAVA CXF Client 代码:

public static void main(String[] args) {
try { Service1 service1 = new Service1();
Service1Soap service1Soap = service1.getService1Soap();
BindingProvider provider = (BindingProvider)service1Soap; List<Header> headers = new ArrayList<Header>();
Authentication authentication = new Authentication();
authentication.setUserName("fangxing");
authentication.setPassword("123456");
Header authHeader = new Header(ObjectFactory._Authentication_QNAME, authentication,
new JAXBDataBinding(Authentication.class)); headers.add(authHeader);
provider.getRequestContext().put(Header.HEADER_LIST, headers); QueryOfOrderCondition queryInfo = new QueryOfOrderCondition();
queryInfo.setPageNo(1);
queryInfo.setPageSize(1000); OrderCondition condition = new OrderCondition();
condition.setShopId("SHOP001");
condition.setStartTime("2014-05-01 00:00:00");
condition.setEndTime("2014-05-10 23:59:59"); queryInfo.setCondition(condition); PageResultOfOrder result = service1Soap.queryOrder(queryInfo);
System.out.println("get order size: " + result.getData().getOrder().size()); } catch (Exception e) {
e.printStackTrace();
} }

演示样例代码下载,下载请阅 Readme.txt

WebService 设计总结的更多相关文章

  1. 如何开发Domino中的WebService

    在domino中写webservice可以使用LotusScript,也可以使用java,由于LotusScript API提供的功能多数都是操作domino数据库中文档的,在web service中 ...

  2. WebService的几种验证方式

    转 http://www.cnblogs.com/yoshiki1895/archive/2009/06/03/1495440.html WebService的几种验证方式 1.1      WebS ...

  3. 浅谈WebService的版本兼容性设计

    在现在大型的项目或者软件开发中,一般都会有很多种终端, PC端比如Winform.WebForm,移动端,比如各种Native客户端(iOS, Android, WP),Html5等,我们要满足以上所 ...

  4. 软件架构设计学习总结(15):远程通信(RPC,Webservice,RMI,JMS、EJB、JNDI的区别)对比

    总结这些概念都是易混淆,最基本概念定义复习和深入理解,同时也是架构师必备课程   RPC(Remote Procedure Call Protocol) RPC使用C/S方式,采用http协议,发送请 ...

  5. Webservice银行报文接口设计

      Preface: 合理的软件架构设计其好处是不言而喻的,系统具有清晰的软件结构,良好的可扩展性,类的职能单一明确,系统的复杂度底.此前的一个实际项目中总结了些关于OO设计的实际应用,主要是围绕'高 ...

  6. 设计管理员表;webservice用于网络安全的高端内提供服务的

    admin表设计.你应该有角色表,管理员属于一个样的作用,另一个接口选项,以查看表.角色有更多的选择的能力. 角色和选项代表了许多关系,因此,我们必须保持这种关系有一个表 版权声明:本文博客原创文章, ...

  7. 架构设计:远程调用服务架构设计及zookeeper技术详解(下篇)

    一.下篇开头的废话 终于开写下篇了,这也是我写远程调用框架的第三篇文章,前两篇都被博客园作为[编辑推荐]的文章,很兴奋哦,嘿嘿~~~~,本人是个很臭美的人,一定得要截图为证: 今天是2014年的第一天 ...

  8. 架构设计:一种远程调用服务的设计构思(zookeeper的一种应用实践)

    在深入学习zookeeper我想先给大家介绍一个和zookeeper相关的应用实例,我把这个实例命名为远程调用服务.通过对这种应用实例的描述,我们会对zookeeper应用场景会有深入的了解. 远程调 ...

  9. Atitit webservice发现机制 WS-Discovery标准的规范attilax总结

    Atitit webservice发现机制 WS-Discovery标准的规范attilax总结 1.1. WS-Discovery标准1 1.2. 一.WS-Discovery1 1.2.1.   ...

随机推荐

  1. bzoj4525: [Usaco2016 Jan]Angry Cows

    二分. #include<cstdio> #include<algorithm> #include<cstring> using namespace std; + ...

  2. windows8安装xna4.0不能开发Xbox和PC端游戏的解决办法

    vs2012安装wp8后,只能开发手机端的xna游戏程序,没有xbox和pc端的,看来官方是不打算更新了,不过我们还是有办法的. 前提条件下,您得安装了vs2010和xna4.0 game studi ...

  3. Spring data rest 如何显示主键

    How to expose the resourceId with Spring-Data-Rest? Spring-Data-Rest is a quite new project in the S ...

  4. OK335xS LAN8710 phy driver hacking

    /******************************************************************** * OK335xS LAN8710 phy driver h ...

  5. RPi 2B 自动发送获取的IP到固定邮箱

    /************************************************************************* * RPi 2B 自动发送获取的IP到固定邮箱 * ...

  6. 设计模式Day01

    一.工厂模式 1.工厂模式的关键点就是如何描述好这两个角色之间的关系,分为四种情况: (1)单一产品系,工厂生产一种类型的产品: (2)多产品系,特征相同.工厂生产多种类型的产品: (3)多产品系,部 ...

  7. MVC Action Filter

    ASP.NET MVC Framework支持四种不同类型的Filter: Authorization filters – 实现IAuthorizationFilter接口的属性. Action fi ...

  8. Windows版词汇小助手V3.0发布了

    欢迎使用词汇小助手 作者:IT小小龙 电子邮箱:long_python@126.com 个人博客:http://blog.sina.com.cn/buduanqs 一款跨平台词汇查询记忆学习软件. 已 ...

  9. hive内部表与外部表区别

    1.在Hive里面创建一个表: hive> create table wyp(id int,    > name string,    > age int,    > tele ...

  10. NOIP2014 生活大爆炸版石头剪刀布

    生活大爆炸版石头剪刀布 (rps.cpp/c/pas) [问题描述] 石头剪刀布是常见的猜拳游戏:石头胜剪刀,剪刀胜布,布胜石头.如果两个人出拳一样,则不分胜负.在<生活大爆炸>第二季第8 ...