理解面向服务的架构

 理解面向服务的架构

                                                        作者:David Walend
 
 
不幸的是,大多数的开发者虽然知道面向服务的架构近在咫尺,但仍然无法找到跟上这股潮流的近路。Forests的提出的三个字母的缩写标准已经发芽、开花并且发展壮大,但是我们还没有学习的足够深入来决定是否它们适合我们自己的项目。SOA标准在我吸引我们的注意力和忠诚而努力。然而,大多数的文章和表述集中在三个缩写字母的细节,以及怎么使得“已有系统”适应某些人所喜欢的WEB服务框架。“已有系统”的意思是“到现在还在持续运转的使得系统正常工作和对商务活动有用的软件”
1995年,我开始开发一个分布式的消息系统,所以能够理解大多数的文章;但是我发现很多的宣传多度并且使人畏惧,而且很大程度上不切合实际。这篇文章关注的是逃离SOA,使得软件的开发和维护简单;并且使得你的业务运行的更好。以下是我们可以避开面向服务架构的一些方面:
     更好的理解已经开发好的系统
     更好的组织、更好的关注新增的开发
     易于和其他系统的集成
     方便测试的定义良好的边界
     通过重复使用服务来更大规模的重用代码
     通过创建系统的系统来扩大规模
     通过可重启的服务来获取更好的可靠性
     开发集中在可执行的商务计划本身
上面的清单是一个巨大的许诺,有很多的广告成分就不足为奇。不幸的是,清单并没有支持我们在编码方面的任何好处。除了能够重启服务以外,没有任何技术方面的细节。面向服务的架构将业界从19世纪八十年代到九十九代所有的良好的方法细节集中到了一起,从而从一些简单的部分产生一个复杂的系统。面向服务的架构拥有如下的一些特性:
     服务拥有强大的软件契约
     服务拥有良好的封装性
     消息是文档化的
     服务分享一个消息总线
     服务是松散耦合的
     服务拥有生命周期
     服务的系统可以在运行期内任意装配
     服务可以被查询到
     服务的系统可以升级为系统的系统
 
服务拥有强大的软件契约
JAVA的JavaDoc 为软件契约提供了一个很好的开始。通过使用JavaDoc,特别是package.html 文件,你可以指引给其他开发者你的代码的重要部分,并且分项重要细节。如果开发者需要察看什么文档的话,绝大多数的人会寻找JavaDoc。所以在那里向他们描述你的契约是最好不过的了。WSDL 文件包括了一些精确的服务的编程接口的描述,然而,它是在机器能阅读的方式下描述的。而且,WSDL文件是和代码分开的(所以它们很难被发现)并且不好理解(这样一些重要的细节将被我们遗漏掉)。在我的系统里,我喜欢创建Java interfaces 来保留我的服务的细节的详细信息。JDK 5的annotations 也能帮助我们创建契约,特别是来自JSR-181 的实现,这将成为JDK 6的标准。Annotations能够被机器阅读,也可以在JavaDoc里得到,并且被嵌入到代码里面。我认为它是创建软件契约的一个最好选择,并且不可能被硬的描述取代。
 
服务是封装的
服务是封装的。一个服务仅仅在它们的契约里暴露它们的行为,服务外面不可能看到服务的内部状态和状态转化。服务是怎样实现契约的仍然被隐藏起来。但是没有服务能获得理想的封装,即使是一个实现硬件功能的服务,也要在网络上分享有限的带宽。处理这些细节的一个最好的方法是把它们添加到契约中去。如果某些事情和服务的其他部分相关但又没有写入到契约中去,那么服务的契约是不完整的。修改你的契约的bug要比修改你的封装的bug容易得多。留心和加强封装可以帮助我们防止复杂度的增加和维护的噩梦。
 
消息内容是原子化的文档
面向服务的架构包含面向文档的架构。就像文档一样,在系统里传递的消息的内容是定义良好的数据结构。有效的发送一个消息就是拷贝一个数据结构。一旦发送,消息将不可能被半道退回来修改。消息的数据结构必须是完整的和被压缩的,并且有固定的规格来维护引用的完整性。你可以很容易和直接的从这种文档数据结构的一部分引用到另外一部分,但是如果引用结构外的一些东西,那么意味着需要一个外健的额外工作。Java Data Object(Java数据对象)标准的6.3部分很好的描述了这两种情况。设计良好的JDO first-class对象就像文档,由从属的second-class 对象组成。XML使用了一些更加含蓄的方法。XML文件是由为内部反射的useIDs和IDREFs或XPath 表达式组成的文档。服务的大多数系统有一个小型的内部服务,它的责任是使用外健找到正确的文档。
很明显,一个服务需要能够理解一个由其他服务收到的消息。有一致的句法是一个容易的方法,如果你在两端都能使用Java的话,试试Java的系列化。除此之外,可以尝试XML。如果你使用XML,决定你的系统将如何处理文档内部的引用的完整性。XStream 提供了一个简单的解决方案来使用XMLSchema处理havoc。JAXB 2.0 也是一个不错的解决方法。如果你需要取得这样一些服务:这些服务在第一次由分布式的组件沟通,你肯定希望使用一些额外的时间来使得这些服务彼此理解。挑选出这些语义和偏僻的用例比决定句法更加的困难。
 
服务分享同一个消息总线
在一个共享的消息总线的基础上,服务通过发送消息和彼此通讯。SOA禁止消息消除来自共享一个呼叫栈的消息的耦合。如果呼叫服务失败,那么呼叫服务不得不等待返回。服务使用三种主要类型的消息:请求/答复,命令,和事件。在请求/答复模式中,一个服务发送一个请求到另外一个服务,并且提供资源,通常是一个线程来等待答复。当共享一个呼叫栈的时候,这种模式导致了一个紧密地耦合。一个服务能够给其他服务发送命令:一个fire-and-forget指令来做某些事情。命令模式导致了一个适度了耦合:命令服务期望其他服务最终完成命令,但是不提供资源来确定其他服务是否做到。一个服务仅仅是发布一些事件:通知有一些值得关注的事情发生,但是不依赖任何的服务是否收到该事件。服务的基于事件的系统是非常松散的耦合,能够大幅度增加消息总线的局限。对消息总线的监控提供了一个洞察系统运行好坏的方法,而且为你的管理提供了一个可靠的图画。
我最喜欢的消息API是Java Messaging Service 标准。有很多关于这个API的实现,其中的大多数关注在企业级规模的环境下的可靠性,一些包括了非Java环境的API。一些JMS实现使用了点对点的技术,来适应Internet和无线网络的短时间。我创建了SomnifugiJMS 来用于小规模系统的单一Java过程的线程之间的通讯。如果你希望在更小规模的Java系统中使用消息,那么请试着使用java.util.concurrent 中的BlockingQueue。对于不可靠的网络上的大规模的系统,试着使用sJXTA 来创建一个可靠的网络系统。
 
服务是松散耦合的
理想情况下,服务只通过消息总线在它们需要履行契约的时候交互。你设计的系统越接近这种理想情况,它就越好。尽量的去发现和记录隐藏的依赖,如共享呼叫栈、单态的静态隧道、共享的处理器、存储器、磁盘、数据库和网络。任何的依赖都可能隐藏在后台,等待打断你的系统。要知道,服务的耦合是通过消息总线实现的。一个服务发出一个命令或者请求的条件是假设一些其他的服务将响应这个命令或者请求。即使是只期望发送事件的服务也假定一些其他的服务将要发布这个事件。尽力的去纪录潜在的问题,把你全部的精力集中到解偶一些特定的故障区,在它们发生问题之前就做。服务之间的松散耦合使得用一个服务替换另一个服务成为可能。当你的经理询问可否在系统中集成一个可以替换的服务,你可以给出肯定地回答。
一个服务对消息的反应是一个原子行为,所以服务能够作为记录和尝试解决问题的最佳事务引擎。服务之间的两相提交式事务和这些服务耦合在一起。幸运的是,大多数的系统都比数据库大,很少需要回滚事务。而实际上,系统的一个解决问题的服务需要警告这些问题,使得这些问题能够被挑选出来。这种解决问题的服务非常适应大多数的业务工作流,因为业务希望重新路由问题事务,而不是把它去掉。
 
服务有独立的生命周期
每一个服务都有和其他服务不同的、属于它自己的生命和生命周期。单个的服务可以被停止、重新启动、和替换掉,而不需要停止整个系统。批量服务就像一天结束的处理器一样能够被启动和处理所有挂起的消息,最后停止。一个服务能够短暂的加入到无线网络上,当一个连接被获取,最后也能够没有损害的断掉连接。你甚至可以不需停工就能用一个新版本的服务替换一个已经废弃的服务。我还没有发现一个开关服务的标准,但是它不难建立。在Runnable的主要循环中,简单的可变标志就能很好的做到这些。'sW3C 对可能的状态和事务有一个描述,但是对于我来说好像太麻烦了。如果你开始创建你自己的精准系统,那么请遵从W3C的标准。你需要的特性是在不停止整个系统的情况下,停止一个服务,并且开始另外一个服务来替换它的能力。
 
Orchestrators 在运行期内将批量服务发布到系统
Orchestrators是一些特殊的服务,用来在运行期内将服务收集和装配到大型的系统上。一些orchestrators被用来寻找匹配已注册服务的批量发布工具。Orchestrators被用来启动他们所需要的服务。通过这种方式装配一个服务很像Simulink 的Blocks。你选择一些部分,把它们连接到一个消息总线,有时候通过一个初始的消息来启动系统。BPEL 和工作流引擎例如Dalma 许诺支持批量发布大型系统,但是我还没有使用它们中的任何一个来做一个哪怕是简单的实验。java.net网站使用Dalma来合作注册项目。对于我实际创建的几个系统,我使用简单的,功能全面的工具像JythonBeanshell脚本,和简单的java代码来批量发布服务。虽然我希望,但是到现在我还没有看到除了它,还有其他的语言能让我简单的发布工作流。
 
Orchestrators能够动态的发现其他服务
服务经常使用service locator来注册他们,orchestrator能够使用service locator来找到服务,而且这种查询是动态的、在运行期内的。这种方法和使用Java Naming and Directory Interface (JNDI)来查找数据库连结相似。我发现service locator在一个单独的过程比在一个大型系统中更有用。我想这是因为小型的系统更可能拥有很多的容易变化的部分,而我设计的大型系统一般都是用事件,这意味着它们需要较少的查找。那些分布式的服务经常出没的地方,如战场、因特网、无线网,需要大规模的可靠的service locators。JNDI工作就像小规模的service locators。Jini运行在比JNDI更大一些规模的系统上,Jini可以找到实现了指定JAVA接口的服务,这就和通过契约找到服务很接近了。另一个报道成功的是Universal Description, Discovery, and Integration (UDDI),当一些服务不在JAVA系统时。
 
服务的系统成长为系统的系统
一个以服务组成的系统能够以它自己的方式维护。越多的消息以事件的方式出现,而不是以请求的方式,那么这个系统越能组成大规模的系统。通过这种方式扩展系统在商业计划上将是一颗技术宝石,而且在学术和技术上也是一个很有趣的方向。我们软件工程师不需要对这些太过于着迷,我们仍然需要重点关注手头的一些问题。然而,在大规模上的兼容性和稳定性将会使得我们的下一个项目更加优雅。
确定你的服务组成的系统是在它的职责范围内的服务,同时也需要确定大型的系统所拥有的特性,服务也都有这些特性。大型系统应该拥有一个强的服务契约、良好的封装性、使用原子型消息通讯、和其他服务是解耦的、有独立的生命周期、和可以被查询到的。如果上面的要求被实现的话,那么更大规模的orchestrator就能够使用你的系统作为服务,来组成一些更大规模的系统。
 
标准园里的生命
一个面向Java开发者的、巨大的标准园已经形成了。如果你能使用这些标准,那么你将你自己的人力。而且更幸运的是,你的选择将会使得你的系统和一些现在还没有被发现、将来会被发现的一些服务兼容。WSDL和Java接口用来帮助定义软件契约和封装。共享的消息总线可能出自于ESB、JMS或者java.concurrent.BlockingQueues。文档化的消息可能出自XML或系列化的对象。对这些服务的运行期的装配可能出自于商业语言或脚本。加上像JNDI、jini或UDDI这样的服务定位器。新的标准每个礼拜层出不穷。我以JavaOne Online为例,自从这些天以来,Google耗尽了很多的页数来试图向你贩卖什么东西。
封装、良好设计的服务来满足一些新的标准相对容易一些。标准工具取得了飞跃式的发展。我自己的经历就展示了这样发展。从1998年到2000年,我的项目组自己设计了一个SOA框架。需要三天的时间来解释我们的工作为公司做出的贡献。在2001年,我在一个缩编的小组使用JMS、perl和数据库来创建一个面向服务的系统,在2003年,一个新的工作里,我们使用WSIF, JMS和Jython来编写我们自己的WSDL,仅仅用了一个月的时间就把这些集成到面向服务的示范系统中去了。而转化一个已有的系统却花了我们大约一个月的时间。我们最后将WSDL向着有利于JAVA接口的方面开发,并且停止使用WSIF。重新架构相当简单,而且随着时间的发展越来越简单。2005年6月(post-JSR-181),开发了一个包括添加Sun的参考实现的JARs包和添加@webmethod注释到现有代码的Java web service。当JDK6提供JSR-181的时候,我们通过增加注释来获得它支持的部分。
 
包装
面向服务的架构是一个从上世纪八十年代开始到九十年代还没有消亡的观点和模式的集合。那些在SOA后面的概念都是可靠的、坚固的、被测试过和被证明过的。SOA的观点直接应用于有关你如何发布、扩展和发展你的系统的商业计划。开发封装的、松散耦合的软件需要一个清晰的需求原则的契约,而且立即产生效益。获取一些新的WEB SERVICE后面的松散耦合的、封装的软件会变得很容易。
 
David Walend 1994alpha 3就开始学习Java,起因是他听了Tufts University的一个和蔼的计算机科学教授有关于分布式模拟、内存管理、多线程代码和气象学者的问题。
 
阅读更多
换一批

没有更多推荐了,返回首页