协作开发时,接口的不一致性会导致效率低下。一致性大一点来讲,就是一些共同约定的东西,成文的或者不成文的。比如Asp.Net MVC,所有的控制器都被命名被*Controller,如果你不这样命名,Asp.Net MVC很可能不能为你自动创建*Controller实例,无法使控制器按你的意图工作。小一点来说,比如Client与Web Service的协同,如果Web Service的返回具有不一致性,则会造成更多的特殊化代码。当下,与其他组的协作,就遇到这问题。
首先,这Web Service的实现就不是标准化的。一般来说,Web Service有RESTFul、SOAP两种方式,他们都有自己的标准工作模型,这在维基百科或者一般的书籍里都会看到。很不辛,我们遇到的Web Service就是基于非标准的SOAP建立的。对方在设计这些服务时,显然考虑到了安全性,在SOAP头部中增加了用户名密码验证。SOAP Body中加入了一些自定义的标签,文档里没有明确说明,可能是内部做了一些转换。对于这样的非标准的、或者非惯例的设计方式,Visual Stdio的Service Reference已经罢工,无法自动添加SOAP服务引用。那么我们只能自己构造SOAP请求,利用HTTP协议将方法发送出去。
在构造HTTP请求与SOAP请求体时,再次遇到了问题——文档的缺乏。只能依赖对方的示例以及直接的沟通。虽然最终确定了SOAP格式,但依然有几个问题没有弄清楚。
- 对方至今也没有说清楚SOAP头中用户名、密码的编码/加密格式。在构造SOAP头时,我也只是简单的复制了起编码/加密后的信息。
- SOAP体的完整说明。好在他们的定义具有一致性,只需要改变一些XML node 名,可以很平滑地请求到其他方法。
可是当我自己构造HTTP HEAD & BODY (SOAP HEAD & SOAP BODY)时,我感觉自己使用的是RESTFul风格的Service,并没有感受到类似WCF那种透明的SOAP/RPC体验。在构造HTTP Request时,我需要指定Request URL,方法名、方法参数,我需要为每个请求构造这些,体验真的很差。虽然尽量提取了一些公共部分用来构造请求内容,但仍然觉得自己做了很多无用功。
再说说返回结果的不一致性。但就添加记录一项来说,RESTFul会直接返回状态码来标记添加结果。比如201 Created,406 Not Accepted等。SOAP就更好用了,直接返回bool,甚至可以抛出异常。很可惜,我一向好处都没有享受到,还需要写一些额外的代码来解析响应结果。响应结果天然应该是具有一致性的,无非True OR False,再加上一个Error Message。再次很不幸,得益于队友设计能力的薄弱,返回结果变得没有了一致性。可以说,这些Web Service暴露的都不是服务,甚至将数据库表的暴露给了我...我需要知道这些干嘛呢,非要我去理解他们的系统。
<!-- 比较一致的返回内容 --> <Result> <Status>true</Status> <Message>Created</Message> </Result>
从惯例来说,添加记录是不应该返回什么额外信息的。可惜Service一端需要Client一端的辅助来维护两张表的外键关系,他们需要在某一次Created之后,返回一个唯一的Key。此Key由此次插入时,Service产生,用于下一次作为主键写入到另外一张表。是吧,这么稀烂的设计都能弄出来。说着我都觉得累,明明不关我的事,却还让我去操心这个Key。这很明显是设计的失败,Client的职责是提供自己所有的内容,而不应该去维护服务端的东西——至少是尽最大努力的。从具体内容来看,很明显没有。涉及到工作隐私,不便明说。
即使真的需要有返回值,那也是可以接受的。只可惜,再一次打破了我的认知.... 在结果解析时,需要更加特殊化的去考虑。当前的返回在上面的XML上添加了一个Node,用来作为返回信息。
<!-- 有额外参数返回时 --> <Result> <Status>true</Status> <StudentId>111</StudentId> <Message>Created</Message> </Result>
从XML的角度来说,这并没有什么问题。但作为解析来说,这就太特殊化了。我是这样解析的:
var xdoc = new XmlDocument(); xdoc.LoadXml(@""); //Response BODY (XML format.) var node = xdoc.SelectSingleNode("Result"); var result = new { Status= Convert.ToBoolean( node.SelectSingleNode("Status").InnerText), StudentId= node.SelectSingleNode("StudentId").InnerText, Message = node.SelectSingleNode("Message").InnerText }; return result;
很明显,在 StudentId= node.SelectSingleNode("StudentId").InnerText中参数NodeName是特殊化的,无法将此代码作为一个通用的结果解析函数。结果解析函数关注了具体内容,这不是它的职责。有多少个特殊的返回ID,就需要写入多少个特殊函数。在这种功能性的上下文中,我认为更好的解决方法是将值的具体意义忘记掉。值只对使用者才有意义,他们知道怎么使用,而中间过程不需要。
<!-- 改进的有额外参数返回时 --> <Result> <Status>true</Status> <Tag>111</Tag> <Message>Created</Message> </Result>
将返回结果的额外信息抽象为Tag之后,就可以用统一的结果解析函数了 :)
var result = new { Status= Convert.ToBoolean( node.SelectSingleNode("Status").InnerText), Tag= node.SelectSingleNode("Tag").InnerText, Message = node.SelectSingleNode("Message").InnerText }; return result;
没有额外信息则不用关心Tag字段,需要关注的则可以从Tag获取到想要值——相关者一定会知道Tag内容所代表的具体含义。一点点改变,就能带来工作效率的提升。