本文目的
需求变化是软件开发过程中的一大难题,我们经常扼腕叹息:面对变化,我们的软件为何如此不堪一击?我们常常在众多需求变化导致的功能爆炸中疲于奔命,甚至迷失自我!这到底为什么?面对"拥抱变化"这种得呐喊,我们的感受应该是震耳欲聋,还是振聋发聩?如果你仍在困惑,可以来看一看WCF是如何摆脱这种困境的!
序幕
小王效力于北京的一家系统集成公司,该公司内部有一个WCF服务为各个部门所共用,小王便是WCF服务的开发和维护人员。起初大家在一个办公楼工作,共用一个局域网,WCF运行的非常好,小王也因此获得了公司上下的一致好评。此时他们的WCF的使用状态如下图所示:
起初小王以为公司都在一个局域网之中,考虑到性能,WCF最早采用的是NetTcpBinding的绑定方式,契约部分是根据自己的业务需要开发的, 而他为服务公布的最早的地址为:net.tcp://127.0.0.1:6547/Service。他最早是用代码方式将服务托管到一个Console程序中的,托管代码如下:
NetTcpBinding bind = new NetTcpBinding();
Uri address = new Uri( " net.tcp://127.0.0.1:6547/Service " );
host.AddServiceEndpoint( typeof (Contracts.IService), bind, address);
而此时大家所使用的客户端代码均为:
NetTcpBinding bind = new NetTcpBinding();
EndpointAddress address = new EndpointAddress( " net.tcp://127.0.0.1:6547/Service " );
Proxys.IService ws = new Proxys.ServiceClient(bind, address);
这样一种情况下,小王的WCF服务与各个部门之间可谓是其乐融融。可好景不长。各位看官,请看下文:
使用范围扩大
随着近几年公司业务的持续发展,公司在唐山的业务量非常大,所以在唐山成立了一个办事处,而该办事处同样需要使用小王的WCF服务,可原本出乎小王意料的事情发生了:唐山办事处和公司总部处于不同的网络,如果还使用tcp这种传输方式,就不适用了。公司头们为这事也挺犯愁,本来以为还要劳烦小王大改一通,没想到小王却说:"小意思,马上解决问题!"他首先和公司网络管理人员联系,将WCF服务所在主机配置1个公网IP为202.120.120.3,然后再托管程序的代码中增加几行
Uri address = new Uri( " http:// 202.120.120.3:80/Service " );
host.AddServiceEndpoint( typeof (Contracts.IService), bind, address);
然后将客户端程序场景中的代码修改如下:
EndpointAddress address = new EndpointAddress( " http:// 202.120.120.3:80/Service " );
Proxys.IService ws = new Proxys.ServiceClient(bind, address);
然后将托管程序重新发布了一下。而将修改之后的客户端部署到办事处一台服务器上面,经过测试,新的客户端运行正常,而总部那些客户端也没受任何影响。由此可见,双方相安无事,小王也松了一口气,原本另大家比较头疼的变化问题便迎刃而解了。下面是使用范围变化之后,WCF服务与客户端的工作情况:
停电了,殃及池鱼
昨天请假了,今天兴致勃勃来上班,没想到刚来,却被经理劈头盖脸的数落了一顿:" 昨天停电,来电后,你的服务没有启动,打你电话也不接,下次注意亚!",小王抓抓了头皮,不好意思的笑笑说:"好,我马上想办法,防止出现类似问题",回到座位上,小王就想到了用iis来替代console作宿主,这样机器重新启动后,服务便会自动加载,可转念一想,不对,iis支持http的访问方式,而且基地址必须和服务所在网站地址一致,我们局域网内部采用的是tcp,排除了iis,小王很快就想到了windows service,恩,就它了。所以问题也便成了下图所示的情形:
经过查证,小王先弄明白了下面的原理:
不仅如此,在不同宿主中寄宿的方法也一致:都是采用ServiceHost来进行托管。知道上面的知识,小王将原本Console宿主中 的托管代码拷贝到Windows服务的代码中,只不过,Console中托管代码写在Main方法,而Windows Service写在OnStart方法中。
秀才遇见兵
原本只是公司内部使用的WCF服务,前段时间也开放给了公司的几个大的合作商,和他们的平台作整合,前几家,合作的非常好,小王正津津乐道之时,却遇到了一个刺头,他是公司一个非常重要的合作商,但要求也非常特殊,服务中原本有个数据契约,用于描述公司订单,契约的定义如下:
而且其他的合作商,对此都没有任何的异议,小王将代理已经发出去10几家了,可现在遇到这位要求必须再上面的基础上再提供订单的创建时间,小王打电话过去,和他解释:"目前我们的系统已经被很多厂商使用,而且别人的代理目前也运行的很好,你的这个需求是正确的,但我们如果修改数据契约会影响其它客户端的使用,请理解",本以为自己苦口婆心的解释,人家会买账,谁想,对方根本不听解释,一个劲地说:"这是我们的需求,必须的,必须的!"。气得小王直想摔电话。可"用户是上帝,胳膊扭不过大腿么",后来一高人指点道:"WCF在设计的时候已经考虑到了版本管理的问题,这种问题已经非常容易解决,你可以用IExtensibleDataObject",经查阅还真有此类问题的解决方案,小王将上面的契约代码更改如下:
然后新生成一个代理,发给了刁蛮用户,而其他客户的代理版本却不用任何变动。新的用户的代理运行和很正常,而老的也很正常,真爽!小王此时方体会到WCF的强大之处。
凡是符合下面的变动情形的,均可视为非重大更改:
- 更改成员名称,且将DataMemberAttribute的Name属性和旧版本保持一致
- 大多数添加和移除数据成员的操作
- 将成员的特性IsRequired从True更改为False
- 实现IExtensibleDataObject接口
- 更改枚举成员名称,但用EnumMemberAttribute将协定名称和老版本保持一致
- 大多数数据集合的更改
服务也会累么?
问题是一个接一个,解决完上面的几个问题没多长时间,这个WCF又有新问题了,现在客户端越来越多,很多客户反映,最近服务端很不稳定,有时还连接不上去,小王检察了半天业务代码,发现没有问题,而且之前人数少得时候,运行的挺好的亚,此时他经过仔细的查阅,终于找到了一些用的信息:WCF为了改善性能,是有限流(Throttling)措施的。主要包括三种
- maxConcurrentCalls :最大并发数,默认为16
- maxConcurrentSessions :最大的会话数,主要针对于PerSession的情况,默认为10
- maxConcurrentInstances:最大实例数,默认为Int.MaxValue
默认的设置比较保守,最大并发数仅为16,非常容易造成排队或者超时现象。修改或者添加限流也非常简单,只需要在服务的配置中修改或者增加ServiceBehavior中的
后记
拥抱变化,拥抱变化,我们口号喊得非常响亮,可为什么面对变化的时候,我们有时会暴跳如雷呢?一边骂客户bt,一边还要通宵达旦的加班来完成工作呢?问题的原因不在于用户,不在于变化本身,更多的是我们一味的将一堆垃圾改造成另外一堆垃圾,要想真正解决问题,做好架构,做好基础是关键。我们常常因为时间,人力等因素,刹不住车似的的制造garbage,却忘记了什么叫细节,什么叫品质。无论针对产品还是市场,当前更重视不是你做了多少,而是做了多少有意义的事情,多少别人没有做过或者做不了的事情。品质,细节,ThinkPad,Google。总之我会毫不犹豫地转向WCF,因为它有品,也更能适应变化