Apply SOA Design Patterns with WCF (2) WCF Automatic Deployment (自动化部署)

Original (原创) by Teddy’s Knowledge Base

Content (目录)

(1) WCF Configuration Centralization (WCF配置集中管理)

(2) WCF Automatic Deployment (WCF自动化部署)

(3) WCF Automatic Service Locating (WCF自动化服务定位)

(4) WCF Database Paging & Sorting (WCF数据库分页和排序)

(5) WCF Based ASP.NET DataSouce Control (基于WCF的数据源控件)

(6) 1 + 2 + 3 + 4 + 5 = ?

English Version

摘要

本文提供一种简化在多服务器和服务器群上自动化部署WCF服务的方案。

正文

什么是自动化部署?

自动化部署指自动化地部署一个应用程序到它的运行环境。一般,通过创建批处理脚本来代替手动执行部署过程。对于WCF来说,最好的部署环境是IIS,尤其是包含了WAS的IIS7。最好的IIS上的部署工具是微软即将发布的Web Deployment Tool工具。可以访问其官方网站获得更多详情。

如果使用批处理脚本来自动化部署,那么自动化部署的复杂度就依赖于脚本的复杂度。一个批处理脚本可能包含许多任务,诸如:文件复制、DLL注册、IIS设置、Windows服务设置,甚至更复杂的依赖于其他资源和服务的任务。即使我们用像Web Deployment Tool这样功能强大的部署工具,它也只是能帮你更容易的创建部署脚本,并不会减小实际需要执行的任务的复杂程度。所以说,想要真正简化部署的过程,降低部署成本,我们更应该寻找能够减少或者转移走部署过程中的各种依赖的方法。

配置集中管理对自动化部署的好处

前一章说到了配置集中管理,可能的配置包括服务的元数据、数据库连接字串、应用设置、策略等等。实际上,你很容易就能想象,绝大多数部署过程中可能有的依赖,都和这些配置有关。换句话说,配置管理越集中化,自动化部署也就能变得更简单也更轻巧。

例如。从Dev、QA、Staging到Live环境:

  • 消费和提供服务的元数据因为不同的端点地址、安全和日志策略可能不同。
  • 数据库服务器可能不同,所以,数据库连接字串也可能不同。
  • 任何关联整合的外部资源,诸如:URI,链接,缓存服务器地址等等都可能不同。

如果我们保证大多数配置都能被集中管理,那么在最理想的情况下(实际上,实践中,绝大多数情况下都能达到最理想情况),我们甚至能使得在任意环境部署的文件完全一致,不需要修改。

IIS对简化自动化部署的好处

大多数读者可能已经在IIS里至少部署过最简单的WCF演示程序了,感觉有任何难的吗?想必最可能让人觉得有一点复杂的是元数据(服务行为、端点)的配置。前面讨论过了,如果我们能从应用程序配置文件中移除这个复杂度,你是不是会说他已经足够简单了呢?这是因为IIS还给我们提供了另三个好处:

  • 自动地址绑定。

如果一个WCF服务部署在IIS里,就不需要显式指定服务发布的地址了,因为地址可以通过部署的文件夹和网站的地址和端口计算出来。

  • 自动端口共享。

网站的端口自动地被所有部署在网站中的服务共享。更进一步,在IIS7中,因为IIS7支持诸如TCP这样的更多的接口,部署在IIS7中的服务甚至可以自动共享相同的TCP端口。我们甚至可以在IIS7里让多个不同类型的端点绑定(WSHttp, NetTCP, NetPipedName)指向单一的一个完全相同的服务地址(不包含地址的前缀如’http:’部分)。

  • 自动的服务宿主管理。

在其他的WCF服务宿主环境,诸如命令行程序或Windows服务程序中,必须要写额外的代码来管理ServiceHost类的实例的生命周期;而在IIS中,我么们可以简单的写一个.svc文件,IIS通过.NET Framework提供的ServiceHostFactory类可以自动为你管理ServiceHost实例的生命周期。尽管.NET Framework提供的ServiceHostFact的默认实现只能从应用程序配置文件读取WCF服务的元数据配置,我们可以写一个自定义的ServiceHostFactory类,从其他地方,例如我们提到过的集中化的配置中心读取元数据配置。下面的代码是一个从集中化的Store读取元数据配置的自定义的ServiceHostFactory实现片断:

1     public class WcfServiceHostFactory : ServiceHostFactory
2     {
3         //
4 
5         protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
6         {
7             var serviceContracts = WcfServiceHelper.GetServiceContracts(serviceType);
8 
9             var host = new ServiceHost(serviceType, BuildBaseAddresses(serviceContracts));
10             foreach (var serviceContract in serviceContracts)
11             {
12                 var endpoints = EndpointStore.GetServerEndpoints(serviceContract);
13                 if (endpoints.Count == 0)
14                     return host;
15 
16                 foreach (var endpoint in endpoints)
17                 {
18                     var address = WcfServiceHelper.BuildAddress(endpoint);
19                     if (address == default(Uri)) continue;
20 
21                     var binding = WcfServiceHelper.BuildBinding(serviceContract, endpoint);
22 
23                     if (binding == null) continue;
24 
25                     if (!IsBehaviorConfigured<ServiceMetadataBehavior>(host))
26                     {
27                         var smb = new ServiceMetadataBehavior();
28                         host.Description.Behaviors.Add(smb);
29                     }
30 
31                     if (endpoint.MexBindingEnabled)
32                     {
33                         host.AddServiceEndpoint(typeof (IMetadataExchange), new CustomBinding(binding), "mex");
34                     }
35 
36                     if (!IsBehaviorConfigured<ServiceThrottlingBehavior>(host))
37                     {
38                         var serviceThrottle = new ServiceThrottlingBehavior();
39                         if (endpoint.MaxConcurrentCalls.HasValue)
40                             serviceThrottle.MaxConcurrentCalls = endpoint.MaxConcurrentCalls.Value;
41                         if (endpoint.MaxConcurrentInstances.HasValue)
42                             serviceThrottle.MaxConcurrentInstances = endpoint.MaxConcurrentInstances.Value;
43                         if (endpoint.MaxConcurrentSessions.HasValue)
44                             serviceThrottle.MaxConcurrentSessions = endpoint.MaxConcurrentSessions.Value;
45                         host.Description.Behaviors.Add(serviceThrottle);
46                     }
47 
48                     if (!IsBehaviorConfigured<ServiceDebugBehavior>(host) && endpoint.IncludeExceptionDetailInFaults.HasValue && endpoint.IncludeExceptionDetailInFaults.Value)
49                     {
50                         var serviceDebug = new ServiceDebugBehavior
51                                                {
52                                                    IncludeExceptionDetailInFaults =
53                                                        endpoint.IncludeExceptionDetailInFaults.Value
54                                                };
55                         host.Description.Behaviors.Add(serviceDebug);
56                     }
57 
58                     host.AddServiceEndpoint(serviceContract, binding, address);
59                 }
60             }
61 
62             return host;
63         }
64 
65     }

提示

  • 一般,在.svc文件中指定的Type是服务的实现类的类型,ServiceHostFactory的CreateHostHost方法会将这个类型作为参数传给ServiceHost类的构造函数。但是,也可以只在.svc文件里指定服务的接口类型,然后通过使用诸如Unity和Castle这样的IoC容器,在你的自定义ServiceHostFactory的实现中,先从服务接口类型获取服务的实现类型,然后再传给ServiceHost的构造函数。
  • 在自定义的ServiceHostFactory实现中,要小心捕获创建ServiceHost的过程中的错误并记录日志,因为任何在ServiceHost的创建过程中抛出的的异常的详细信息都不会反映给服务的客户端。客户端永远不会知道是为什么,只会收到一个连接被拒绝的消息。不过我们还是可以像调试其他Web应用程序一样,用VS2008的远程调试工具来调试自定义的ServiceHostFactory的。

参考

(1) SOA Design Pattern Catalog: http://www.soapatterns.org/

 

//我是结尾符,待续…

posted @ 2009-03-25 23:14  Teddy's Knowledge Base  Views(2602)  Comments(1Edit  收藏