技术开发 频道

Programming WCF Services:面向服务概述

    本书全面介绍了使用WCF设计与开发面向服务应用程序的相关知识。附录A则展示了我对面向服务的理解,以及面向服务的具体应用场景。但是,如果要了解面向服务的发展方向以及它在软件行业所占的地位,首先就要了解它的起源与发展,因为没有任何一种新的方法学是一蹴而就的,而应该是经历了数十年渐进的演化。在简要地介绍了软件工程的发展历程以及发展趋势之后,附录给出了面向服务应用程序的定义(不仅仅是指纯粹的架构),阐释了服务的本质,以及这种方法学的价值。接着,附录还给出了面向服务的原则,其中增加的抽象原则对于大多数应用程序而言,具有更强的实践意义与具体应用。

    软件工程简史

    二十世纪二十年代末期,世界上第一台现代计算机诞生于波兰,这是一台电子机械,大约与打字机一般大小,主要用于消息的加密。后来,这台设备被卖给了德国商业部。30年代,它被德国军方用来实现通信加密,也就是闻名遐迩的“英格玛(Enigma)”。Enigma使用机械转子(Mechanical Rotors)根据不同的密码字母改变从键到灯板的电流路径。Enigma并非通常意义上的计算机:它只能实现加密(Enciphering)与解密(Deciphering)(现在,我们将它们称之为Encryption与Decryption)。如果操作者要改变加密算法,必须通过改变转子的顺序、初始位置,以及连接键盘到灯板的线缆,改变机器的机械结构。这就导致“程序”与它要解决的问题(加密)紧密地耦合在一起,是采用机械设计的计算机。

     在上个世纪四十年代末期到五十年代期间,终于诞生了世界上第一台真正意义上的电子计算机,它主要用于国防。这些机器能够运行代码解决问题,但无法执行预先制定的任务。这类计算机执行的代码存在的硬伤,则在于“语言”是面向机器的,因而程序完全依赖于硬件。针对一台机器编写的代码无法运行在另外一台机器上。最初,这个缺陷并没有引起足够的重视,因为在当世只有屈指可数的几台计算机。随着计算机的大量生产,在六十年代初出现了汇编语言,它使得代码能够不依赖于特定的机器,可以运行在多种机器上。但是,代码却与机器的体系架构密切相关。针对8位机编写的代码不能运行在16位机上,更不用说寄存器或内存以及内存地址之前的区别。因此,维护程序的代价开始逐步增加。随着计算机被广泛的应用在民用以及政府部门,为满足有限的资源与预算,需要提供更好的解决方案。

    六十年代,诸如COBOL和FORTRAN等高级语言引入了编译器的概念。开发者可以在抽象层面上编写机器编程语言,编译器能够将它编译为实际的汇编代码。编译器开启了将代码与硬件以及硬件架构解耦的先河。第一代语言存在的问题是代码是非结构化编程的,代码内部通过使用跳转指令或go-to语句依赖于它自身的结构。即使代码结构发生微小的改变,也可能对程序的多个地方产生灾难性的影响。

    在七十年代,结构化编程语言例如C和Pascal占据了统治地位。它通过函数与结构,完全解除了代码与内部地址及内部结构的依赖。正是在七十年代,开发者与研究者首次开始将软件作为工程科学进行研究。在所属权利益的驱动下,许多公司开始考虑软件的重用,即代码段能够重用在不同的程序上下文中。例如C语言,基本的重用单元就是函数。基于函数重用的问题是函数依赖于它操作的数据,如果数据是全局的,在重用上下文中改变一个函数,就会影响不同地方使用的其它函数。

    面向对象

    上述问题的解决方案是引入面向对象,例如Smalltalk,以及之后产生的C++。面向对象语言将函数和函数操作的数据包裹在一起,放到一个对象中。函数(现在则称为方法)封装逻辑,对象则封装数据。面向对象通过类层级的形式以支持领域建模(Domain Modeling)。重用机制是基于类的,允许直接重用,或者通过继承(Inheritance)进行特化(Specialization)。但是,面向对象仍然存在自身的问题。首先,生成的应用程序(或伪代码)是单一的应用程序。类似C++的编程语言并不能识别二进制形式的生成代码。即使只是针对细微的修改,开发者每一次都必须重新部署大量的代码。这对开发过程、质量、发布时间以及成本都会产生负面影响。由于类作为重用的基本单元,这些单元会在源代码中被定义为类的格式。因此,应用程序会依赖于它使用的语言。我们不能让一个Smalltalk编写的客户端去调用或继承C++的类。而且,继承实际上是一种糟糕的重用机制,大多数情况下,它都是弊大于利,因为派生类与基类的实现密切相关,从而在类层级中引入了垂直的依赖关系。面向对象忽略了许多现实问题,例如部署与版本控制。序列化与持久化则是存在的另一个问题。大多数应用程序都无法凭空获取对象。对象包含了某些持久化状态,这些状态需要组合为对象,但却无法保证持久状态与可能的新的对象代码的兼容性。如果对象被跨进程或跨机器分发,就无法使用C++的调用方式,因为C++需要直接内存引用,并不支持分布式调用。开发者必须编写宿主进程,使用一些远程调用技术例如TCP套接字执行远程调用,但这样的调用迥异于通常的C++调用方式,从而抵消了C++语言的优势。

    面向组件

    随着时间的推移,相继产生了一些新的技术,例如静态库(.lib)与动态库(.dll),它们能够解决面向对象存在的问题。终于,在1994年人们首次提出面向组件技术,称为COM(组件对象模型)。面向组件提供了可交换的、可互操作的二进制组件。与共享源代码文件不同,客户端与服务器都支持二进制类型系统(例如IDL),以元数据的表示方式放入到封装的二进制组件中。组件在运行时被发现以及装载,例如拖动一个控件到窗体上,则该控件会在客户端机器的运行时自动被装载。客户端程序仅仅是服务的抽象与契约,称为接口。只要接口不变,服务就能够任意扩展。代理能够实现相同的接口,通过为远程调用封装底层机制实现无缝的远程调用。公共二进制类型系统的可用性使得跨语言的互操作性成为可能,这样,Visual Basic的客户端就能够调用C++的COM组件。重用的基本单元是接口,而不是组件,多态的实现是可互换的。通过为每个接口、COM对象以及类型库分配唯一的标识符可以解决版本冲突的问题。然而,作为现代软件工程学的一个根本性突破,COM在大多数开发者的眼中却如鸡肋一般,食之无味,弃之可惜。COM未必是丑陋的实现,因为它能够与操作系统顶层结合在一起,而操作系统却不用考虑COM的实现。编写COM组件所使用的非常好的语言(例如C++和Visual Baic)是面向对象的,而不是面向组件的。因为面向组件语言的编程模型过于复杂,需要框架(如ATL)来消除两种模型之间的鸿沟。正是认识到这一问题,微软于2002年发布了.NET 1.0。.NET对比COM、C++以及Windows,不仅更加简洁,而且还能够无缝地与单独的、新的面向组件运行时集成。.NET支持COM的所有优势,并实现了许多技术要素,例如类型元数据共享、序列化以及版本的统一与标准化。.NET具有更强的功能与COM协作,但COM与.NET又都存在相似的问题:

    技术与平台

    应用程序与代码依赖于技术与平台。COM与.NET都只能应用于Windows平台。它们要求客户端以及服务也应该是COM或者.NET,而无法支持与其它技术的互操作,不管它们是在Windows平台下,还是其它操作系统。虽然利用Web服务使得技术之间的互操作成为可能,但它却要求开发者放弃使用本地框架进行实现的大部分优势,从而引入了复杂性。

    并发管理

    当开发商(Vendor)发布一个组件时,并不能假定该组件不会被它的客户端多线程的并发访问。事实上,唯一安全的假设就是开发商要求组件支持多线程访问。因此,组件必须是线程安全的,同时必须包含一个同步锁。如果应用程序的开发者在构建应用程序时,聚集了多个开发商开发的多个组件,则多个锁的引入就会使得应用程序易于死锁。必须避免死锁与应用程序和组件之间的依赖。

    事务

    如果应用程序希望组件只参与到一个单独的事务中,则需要运行组件的应用程序协调事务以及组件之间的事务流,这是一个严格的编程要求。它同样会引入应用程序与组件之间关于事务协调的耦合
0
相关文章