大量互联网公司都在拥抱SOA和服务化,但业界对SOA的不少讨论都比较偏向高大上。本文试图从稍微不一样的角度,以相对接地气的方式来讨论SOA,集中讨论SOA在微观实践层面中的缘起、本质和具体操做方式,另外也用至关篇幅介绍了当今互联网行业中各类流行的远程调用技术等等,比较适合从事实际工做的架构师和程序员来阅读。php
为了方便阅读,本话题将分为两篇展示。本文是上篇,着眼于微观SOA的定义,并简单分析其核心原则。html
亚马逊CEO杰夫·贝佐斯:不为人知的SOA大师java
因为SOA有至关的难度和门槛,不妨先从一个小故事提及,从中能够管窥一点SOA的大意和做用。程序员
按照亚马逊前著名员工Steve Yegge著名的“酒后吐槽”,2002年左右,CEO贝佐斯就在亚马逊强制推行了如下六个原则(摘自酷壳):web
听说,亚马逊网站展现一个产品明细的页面,可能要调用200-300个Service,以便生成高度个性化的内容。正则表达式
Steve还提到:shell
Amazon已经把文化转变成了“一切以Service第一”为系统架构的公司,今天,这已经成为他们进行全部设计时的基础,包括那些毫不会被外界所知的仅在内部使用的功能。数据库
那时,若是没有被解雇的的恐惧他们必定不会去作。我是说,他们今天仍然怕被解雇,由于这基本上是那儿天天的生活,为那恐怖的海盗头子贝佐斯工做。不过,他们这么作的确是由于他们已经相信Service这就是正确的方向。他们对于SOA的优势和缺点没有疑问,某些缺点还很大,也不疑问。但总的来讲,这是正确的,由于,SOA驱动出来的设计会产生出平台(Platform)。编程
今天,咱们都知道亚马逊从世界上最大图书卖场进化为了世界上最成功的云平台……json
贝佐斯的六原则展现出高度的远见和超强的信念,即便放到十几年后的今天,依然以为振聋发聩……想起一句老话:“不谋万世者,不足以谋一时;不谋全局者,不足以谋一隅。”
固然,像贝佐斯这种将神性与魔性集于一身的专横人物,既可能创造划时代的进步,也可能制造史无前例的灾难。
SOA漫谈:宏观与微观
SOA即面向服务架构,是一个特别大的话题。
为了方便讨论,我在此先草率的将SOA分为两个层面(大概模仿宏观和微观经济学,但这里的划分没有绝对界限):
不少业界专家都认为SOA概念过于抽象,不接地气,我认为主要是宏观SOA涉及面太广,常常须要作通盘考虑,而其中不少方面距离通常人又比较远。而在微观层面的SOA更容易达到涛哥过去提出的“三贴近”:贴近实际、贴近生活、贴近群众。
同时,宏观SOA要取得成功,一般的前提也是SOA在微观层面的落地与落实,正如宏观经济学通常要有坚实的微观基础(好比大名鼎鼎的凯恩斯主义曾广受诟病的一点就是缺少微观基础)
所以,咱们着眼于SOA落地的目的,着重来分析微观SOA,也算是对业界主流探讨的一个小小的补充。
SOA定义
按照英文维基百科定义:SOA是一种“软件”和“软件架构”的设计模式(或者叫设计原则)。它是基于相互独立的软件片断要将自身的功能经过“服务”提供给其余应用。
什么是“服务”?按照OASIS的定义:Service是一种按照既定“接口“来访问一个或多个软件功能的机制(另外这种访问要符合“服务描述”中策略和限制)
Service示例(代码一般以java示例)
public interface Echo { String echo(String text); } public class EchoImpl implements Echo { public String echo(String text) { return text; } }
可能每一个开发人员天天都在写相似的面向对象的Service,难道这就是在实施SOA吗?
SOA设计原则
既然SOA是设计原则(模式),那么它包含哪些内容呢?事实上,这方面并无最标准的答案,多数是听从著名SOA专家Thomas Erl的概括:
标准化的服务契约 Standardized service contract 服务的松耦合 Service loose coupling 服务的抽象 Service abstraction 服务的可重用性 Service reusability 服务的自治性 Service autonomy 服务的无状态性 Service statelessness 服务的可发现性 Service discoverability 服务的可组合性 Service composability ....
这些原则总的来讲要达到的目的是:提升软件的重用性,减小开发和维护的成本,最终增长一个公司业务的敏捷度。
可是,业界著名专家如Don Box,David Orchard等人对SOA又有各自不一样的总结和侧重。
SOA不但没有绝对统一的原则,并且不少原则自己的内容也具有至关模糊性和宽泛性:例如,所谓松耦合原则须要松散到什么程度才算是符合标准的呢?这就比如一我的要帅到什么程度才算是帅哥呢?一栋楼要高到多少米才算是高楼呢?可能不一样人心中都有本身的一杆秤……部分因为这些理论上的不肯定因素,不一样的人理解或者实施的SOA事实上也可能有比较大的差异。
浅析松耦合原则
SOA原则比较多,真正的理解每每须要逐步的积累和体会,因此在此不详细展开。这里仅以服务的松耦合为例,从不一样维度来简单剖析一下这个原则,以说明SOA原则内涵的丰富性:
实现的松耦合:这是最基本的松耦合,即服务消费端不须要依赖服务契约的某个特定实现,这样服务提供端的内部变动就不会影响到消费端,并且消费端将来还能够自由切换到该契约的其余提供方。
时间的松耦合:典型就是异步消息队列系统,因为有中介者(broker),因此生产者和消费者没必要在同一时间都保持可用性以及相同的吞吐量,并且生产者也不须要立刻等到回复。
位置的松耦合:典型就是服务注册中心和企业服务总线(ESB),消费端彻底不须要直接知道提供端的具体位置,而都经过注册中心来查找或者服务总线来路由。
版本的松耦合:消费端不须要依赖服务契约的某个特定版原本工做,这就要求服务的契约在升级时要尽量的提供向下兼容性。
SOA与传统软件设计
咱们能够认为:SOA ≈ 模块化开发 + 分布式计算
将二者传统上的最佳实践结合在一块儿,基本上能够推导出SOA的多数设计原则。SOA从软件设计(暂不考虑业务架构之类)上来说,自身的新东西其实不算不少。
SOA原则的应用
基于SOA的原则,也许咱们很难说什么应用是绝对符合SOA的,可是却能剔除明显不符合SOA的应用。
用上述标准化契约,松耦合和可重用这几个原则来尝试分析一下上面Echo示例:
Echo的服务契约是用Java接口定义,而不是一种与平台和语言无关的标准化协议,如WSDL,CORBA IDL。固然能够抬杠,Java也是行业标准,甚至全国牙防组一致认定的东西也是行业标准。
Java接口大大加剧了与Service客户端的耦合度,即要求客户端必须也是Java,或者JVM上的动态语言(如Groovy、Jython)等等……
同时,Echo是一个Java的本地接口,就要求调用者最好在同一个JVM进程以内……
Echo的业务逻辑虽然简单独立,但以上技术方面的局限就致使它没法之后在其余场合被轻易重用,好比分布式环境,异构平台等等。
所以,咱们能够认为Echo并不太符合SOA的基本设计原则。
透明化的转向SOA?
修改一下上面的Echo,添加Java EE的@WebServices注解(annotation)
@WebServices public class EchoImpl implements Echo { public String echo(String text) { return text; } }
如今将Echo发布为Java WebServices,并由底层框架自动生成WSDL来做为标准化的服务契约,这样就能与远程的各类语言和平台互操做了,较好的解决了上面提到的松耦合和可重用的问题。按照通常的理解,Echo彷佛就成为比较理想的SOA service了。
可是……即便这个极端简化的例子,也会引出很多很关键的问题,它们决定SOA设计开发的某些难度:
面向对象和面向服务的对比
面向对象(OO)和面向服务(SO)在基础理念上有大量共通之处,好比都尽量追求抽象、封装和低耦合。
但SO相对于OO,又有很是不一样的典型应用场景,好比:
多数OO接口(interface)都只被有限的人使用(好比团队和部门内),而SO接口(或者叫契约)通常来讲都不该该对使用者的范围做出太多的限定和假设(能够是不一样部门,不一样企业,不一样国家)。还记得贝佐斯原则吗?“团队必须作好规划与设计,以便将来把接口开放给全世界的程序员,没有任何例外”。
多数OO接口都只在进程内被访问,而SO接口一般都是被远程调用。
简单讲,就是SO接口使用范围比通常OO接口可能普遍得多。咱们用网站打个比方:一个大型网站的web界面就是它整个系统入口点和边界,可能要面对全世界的访问者(因此常常会作国际化之类的工做),而系统内部传统的OO接口和程序则被隐藏在web界面以后,只被内部较小范围使用。而理想的SO接口和web界面同样,也是变成系统入口和边界,可能要对全世界开发者开放,所以SO在设计开发之中与OO相比其实会有不少不一样。
小结
在前述比较抽象的SOA大原则的基础上,咱们可尝试推导一些较细化和可操做的原则,在具体实践中体现SO的独特之处。请关注本系列文章的下篇!
==========================================================
在上一篇文章中,我说到SOA是一个特别大的话题,不但没有绝对统一的原则,并且不少原则自己的内容也具有至关模糊性和宽泛性。虽然咱们能够说SOA ≈ 模块化开发 + 分布式计算,但因为其原则的模糊性,咱们仍然很难说什么应用是绝对符合SOA的,只能识别出哪些是不符合SOA的。
本篇将对8种可操做的服务设计原则进行细化的分析,做为SOA实践的参考。
服务设计原则1:优化远程调用
这里的远程调用特指RPC(Remote Procedure Call)。固然更面向对象的说法应该是远程方法调用或者远程服务调用等等。
因为SO接口一般要被远程访问,而网络传输,对象序列化/反序列化等开销都远远超过本地Object访问几个数量级,因此要加快系统的响应速度、减小带宽占用和提升吞吐量,选择高性能的远程调用方式常常是很重要的。
可是远程调用方式每每又要受限于具体的业务和部署环境,好比内网、外网、同构平台、异构平台等等。有时还要考虑它对诸如分布式事务,消息级别签名/加密,可靠异步传输等方面的支持程度(这些方面一般被称为SLA:service level agreement),甚至还包括开发者的熟悉和接受程度等等。
所以,远程调用方式每每须要根据具体状况作出选择和权衡。
以Java远程Service为例分析不一样场景下,传输方式的某些可能较好选择:
简单来讲,从性能上讲,tcp协议 + 二进制序列化更适合内网应用。从兼容性、简单性上来讲,http协议 + 文本序列化更适合外网应用。固然这并非绝对的。另外,tcp协议在这里并非限定远程调用协议必定只能是位于OSI网络模型的第四层的原始tcp,它能够包含tcp之上的任何非http协议。
因此,回答上面提到的问题,WebServices (经典的WSDL+SOAP+HTTP)虽然是最符合前述SOA设计原则的技术,但并不等同于SOA,我认为它只是知足了SOA的底线,而未必是某个具体场景下的最佳选择。这正如一个十项全能选手在每一个单项上是很难和单项冠军去竞争的。更理想的SOA Service最好能在能够支持WebServices的同时,支持多种远程调用方式,适应不一样场景,这也是Spring Remoting,SCA,Dubbo,Finagle等分布式服务框架的设计原则。
远程调用技术解释:HTTP + JSON适合SOA吗?
JSON简单易读,通用性极佳,甚至能很好支持浏览器客户端,同时也常被手机APP使用,大有取代XML之势。
但JSON自己缺少像XML那样被普遍接受的标准schema,而通常的HTTP + JSON的远程调用方式也缺少像Thrift,CORBA,WebServices等等那样标准IDL(接口定义语言),致使服务端和客户端之间不能造成强的服务契约,也就不能作好比自动代码生成。因此HTTP + JSON在下降了学习门槛的同时,可能显著的增长复杂应用的开发工做量和出错可能性。
例如,新浪微博提供了基于HTTP + JSON的Open API,但因为业务操做比较复杂,又在JSON上封装实现了各类语言的客户端类库,来减小用户的工做量。
为了解决这方面的问题,业界有不少不一样方案来为HTTP + JSON补充添加IDL,如RSDL、JSON-WSP、WADL、WSDL 2.0等等,但事实上它们的接受度都不太理想。
另外值得一提的是,JSON格式和XML同样有冗余,即便作GZIP压缩之类的优化,传输效率一般也不如不少二进制格式,同时压缩、解压还会引入额外的性能开销。
远程调用技术解释:Apache Thrift多语言服务框架
Thrift是最初来自facebook的一套跨语言的service开发框架,支持C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, JavaScript, Node.js, Smalltalk, Delphi等几乎全部主流编程语言,具备极好的通用性。
Thrift被facebook,twitter等巨头以及开源社区都普遍使用,是很是成熟的技术。
Thrift的服务契约经过相似以下形式的IDL定义:
struct User { 1: i32 id, 2: string name, 3: string password } service UserService { void store(1: User user), UserProfile retrieve(1: i32 id) }
很是相似于C语言,易读易写,比WSDL简单明了得多。比用java之类的编程语言也更方便,有时候能够把全部相关的接口和数据结构定义放到同一个文件,发布出去的时候不用再打一个压缩包之类,甚至能够直接粘贴到文档中
Thrift还提供工具,能够基于IDL自动生成各类语言对应的服务端和客户端代码:
[lishen@dangdang thrift]thrift --gen java user.thrift [lishen@dangdang thrift]$ thrift --gen cpp user.thrift [lishen@dangdang thrift]$ thrift --gen php user.thrift [lishen@dangdang thrift]$ thrift --gen csharp user.thrift
我认为thrift是比WebServices更简单高效的技术,是在SOA中对WebServices最具备替代性的技术之一。
远程调用技术解释:多路复用的TCP长链接
这是一种追求极致高性能高伸缩的方式,这里只作简要介绍。
比较典型的是twitter的Mux RPC协议以及google的SPDY协议,在其中多个请求同时共用同一个长链接,即一个链接交替传输不一样请求的字节块。它既避免了反复创建链接开销,也避免了链接的等待闲置从而减小了系统链接总数,同时还避免了TCP顺序传输中的线头阻塞(head-of-line blocking)问题。
另外,国内比较著名的开源dubbo框架的默认RPC协议,以及业界许多小型开源RPC框架也都是相似的思路。
采用多路复用机制后,通常就要求服务器端和客户端都支持额外的相似于会话层(即OSI网络模型第六层)的语义,致使它们必需要依赖于同一套RPC框架。
其余不少RPC机制都是使用TCP短链接。即便有些RPC使用了长链接,但一个链接同一时间只能发送一个请求,而后链接就处于闲置状态,来等待接收该请求的响应,待响应完毕,该链接才能被释放或者复用。
HTTP 1.1也支持一种基于pipeline模式的长链接,其中多个HTTP请求也可共用一个链接,但它要求响应(response)也必须按照请求(request)的顺序传输返回,即FIFO先进先出。而在彻底多路复用的链接中,哪一个的响应先ready就能够先传输哪一个,不用排队。
固然,短链接、长链接和多路复用长链接之间不存在绝对的好坏,须要取决于具体业务和技术场景,在此不详细展开了。
远程调用技术解释:Java高效序列化
最近几年,各类新的Java高效序列化方式层出不穷,不断刷新序列化性能的上限,例如Kryo,FST等开源框架。它们提供了很是高效的Java对象的序列化和反序列化实现,相比JDK标准的序列化方式(即基于Serializable接口的标准序列化,暂不考虑用诸如Externalizable接口的定制序列化),在典型场景中,其序列化时间开销可能缩短20倍以上,生成二进制字节码的大小可能缩减4倍以上。
另外,这些高效Java序列化方式的开销也显著少于跨语言的序列化方式如thrift的二进制序列化,或者JSON等等
远程调用技术解释:RMI/IIOP和分布式事务
RMI/IIOP是Java EE中标准的远程调用方式,IIOP是CORBA的协议,只有IIOP上的RMI才支持两阶段提交的分布式事务,同时提供和CORBA的互操做。
固然,严格的两阶段提交事务并不高效,还可能严重影响系统伸缩性甚至可用性等等,通常只应用在很是关键的业务中。
远程调用技术解释:Google ProtocolBuffer跨语言序列化
ProtocolBuffer是google开发的跨语言的高效二进制序列化方式,其序列化性能和thrift比较相似。事实上thrift最初就是ProtocolBuffer的仿制品。但它和thrift最大的不一样是他没有自带的RPC实现(由于google没有将RPC部分开源,但有大量第三方实现)。
因为不和RPC方式耦合,反而使得ProtocolBuffer能被方便的集成进大量已有的系统和框架中。在国内它也被百度、淘宝等普遍的应用在Open API中,和HTTP搭配做为一种高效的跨平台跨组织的集成方式。
服务设计原则2:消除冗余数据
一样因为service的远程调用开销很高,因此在它的输入参数和返回结果中,还要尽可能避免携带当前业务用例不须要的冗余的字段,来减小序列化和传输的开销。同时,去掉冗余字段也能够简化接口,避免给外部用户带来没必要要的业务困惑。
好比article service中有个返回article list的方法
List<Article> getArticles(...)
若是业务需求仅仅是要列出文章的标题,那么在返回的article中就要避免携带它的contents等等字段。
这里经典解决方案就是引入OO中经常使用的Data Transfer Object (DTO)模式,专门针对特定service的用例来定制要传输的数据字段。这里就是添加一个AriticleSummary的额外数据传输对象:
List<ArticleSummary> getArticleSummaries(...)
额外的DTO确实是个麻烦,而通常OO程序一般则可直接返回本身的包含冗余的业务模型。
服务设计原则3:粗粒度契约
一样因为远程调用开销高,同时service的外部使用者对特定业务流程的了解也比不上组织内部的人,因此service的契约(接口)一般须要是粗粒度的,其中的一个操做就可能对应到一个完整的业务用例或者业务流程,这样既能减小远程调用次数,同时又下降学习成本和耦合度。
而OO接口一般能够是很是细粒度的,提供最好的灵活性和重用性。
例如,article service支持批量删除文章,OO接口中能够提供
deleteArticle(long id)
供用户本身作循环调用(暂不考虑后端SQL之类优化),但SO接口中,则最好提供
deleteArticles(Set<Long> ids)
供客户端调用,将可能的N次远程调用减小为一次。
例如,下订单的用例,要有一系列操做
addItem -> addTax -> calculateTotalPrice -> placeOrder
OO中咱们彻底可让用户本身来灵活选择,分别调用这些细粒度的可复用的方法。但在SO中,咱们须要将他们封装到一个粗粒度的方法供用户作一次性远程调用,同时也隐藏了内部业务的不少复杂性。另外,客户端也从依赖4个方法变成了依赖1个方法,从而大大下降了程序耦合度。
顺便值得一提的是,若是上面订单用例中每一个操做自己也是远程的service(一般在内网之中),这种粗粒度封装就变成了经典的service composition(服务组合)甚至service orchestration(服务编排)了。这种状况下粗粒度service一样可能提升了性能,由于对外网客户来讲,屡次跨网的远程调用变成了一次跨网调用 + 屡次内网调用。
对这种粗粒度service封装和组合,经典解决方案就是引入OO中经常使用的Facade模式,将原来的对象屏蔽到专门的“外观”接口以后。同时,这里也极可能要求咱们引入新的service参数/返回值的数据结构来组合原来多个操做的对象模型,这就一样用到前述的DTO模式。
一个简单Facade示例(FooService和BarService是两个假想的本地OO service,façade将它们的结果值组合返回):
class FooBarFacadeImpl implements FooBarFacade { private FooService fooService; private BarService barService; public FooBarDto getFooBar() { FooBarDto fb = new FooBarDto(); fb.setFoo(fooService.getFoo()); fb.setBar(barService.getBar()); return fb; } }
固然,有的时候也能够不用facade和DTO,而在是FooService和BarService以外添加另外一个本地service和domain model,这要和具体业务场景有关。
服务设计原则4:通用契约
因为service不假设用户的范围,因此通常要支持不一样语言和平台的客户端。但各类语言和平台在功能丰富性上有很大差别,这就决定了服务契约必须取常见语言、平台以及序列化方式的最大公约数,才能保证service普遍兼容性。由此,服务契约中不能有某些语言才具有的高级特性,参数和返回值也必须是被普遍支持的较简单的数据类型(好比不能有对象循环引用)。
若是原有的OO接口不能知足以上要求,则在此咱们一样须要上述的Facade和DTO,将OO接口转换为通用的SO契约。
例如原有对象模型
class Foo { private Pattern regex; }
Pattern是Java特有的预编译好的,可序列化的正则表达式(可提升性能),但在没有特定框架支持下,可能很差直接被其余语言识别,因此可添加DTO:
class FooDto { private String regex; }
服务设计原则5:隔离变化
虽然OO和SO都追求低耦合,但SO因为使用者范围极广,就要求了更高程度的低耦合性。
好比前述的article service,OO中能够直接返回article对象,而这个article对象在OO程序内部可能作为核心的建模的domain model,甚至做为O/R mapping等等。而在SO若是还直接返回这个article,即便没有前面所说的冗余字段,复杂类型等问题,也可能让外部用户与内部系统的核心对象模型,甚至O/R mapping机制,数据表结构等等产生了必定关联度,这样一来,内部的重构常常都会可能影响到外部的用户。
因此,这里再次对Facade和DTO产生了需求,用它们做为中介者和缓冲带,隔离内外系统,把内部系统变化对外部的冲击减小到最小程度。
服务设计原则6:契约先行
Service是每每涉及不一样组织之间的合做,而按照正常逻辑,两个组织之间合做的首要任务,就是先签定明确的契约,详细规定双方合做的内容,合做的形式等等,这样才能对双方造成强有力的约束和保障,同时你们的工做也可以并行不悖,不用相互等待。所以SOA中,最佳的实践方式也是契约先行,即先作契约的设计,能够有商务,管理和技术等不一样方面的人员共同参与,并定义出相应的WSDL或者IDL,而后在开发的时候再经过工具自动生成目标语言的对应代码。
对于WSDL来讲,作契约先行的门槛略高,若是没有好的XML工具很难手工编制。但对于Thrift IDL或者ProtocolBuffer等来讲,因为它们和普通编程语言相似,因此契约设计相对是比较容易的。另外,对于简单的HTTP + JSON来讲(假设不补充使用其余描述语言),因为JSON没有标准的schema,因此是无法设计具备强约束力的契约的,只能用另外的文档作描述或者用JSON作输入输出的举例。
可是,契约先行,而后再生成服务提供端的代码,毕竟给service开发工做带来了较大的不便,特别是修改契约的时候致使代码须要重写。所以,这里一样可能须要引入Facade和DTO,即用契约产生的都是Facade和DTO代码,它们负责将请求适配和转发到其余内部程序,而内部程序则能够保持本身的主导性和稳定性。
另外,契约先行可能会给前面提到的多远程调用支持带来一些麻烦。
固然契约先行也许并非能被普遍接受的实践方式,就像敏捷开发中“测试先行”(也就是测试驱动开发)一般都是最佳实践,但真正施行的团队却很是之少,这方面还须要不断摸索和总结。但咱们至少能够认为Echo中Java2WSDL并不被认为是SOA的最佳实践。
服务设计原则7:稳定和兼容的契约
因为用户范围的普遍性,因此SO的服务契约和Java标准API相似,在公开发布以后就要保证至关的稳定性,不能随便被重构,即便升级也要考虑尽量的向下兼容性。同时,若是用契约先行的方式,之后频繁更改契约也致使开发人员要不断重作契约到目标语言映射,很是麻烦。
这就是说SO对契约的质量要求可能大大高于通常的OO接口,理想的状况下,甚至可能须要专人(包括商务人员)来设计和评估SO契约(无论是否用契约先行的方式),而把内部的程序实现交给不一样的人,而二者用Facade和DTO作桥梁。
服务设计原则8:契约包装
前述原则基本都是针对service提供端来说的,而对service消费端而言,经过契约生成对应的客户端代码,常常就能够直接使用了。固然,若是契约自己就是Java接口之类(好比在Dubbo,Spring Remoting等框架中),能够略过代码生成的步骤。
可是,service的返回值(DTO)和service接口(Facade),可能被消费端的程序处处引用到。
这样消费端程序就较强的耦合在服务契约上了,若是服务契约不是消费端定义的,消费端就等于把本身程序的部分主导权彻底让渡给了别人。
一旦契约作更改,或者消费端要选择彻底不一样的service提供方(有不一样的契约),甚至改由本地程序本身来实现相关功能,修改工做量就可能很是大了。
另外,经过契约生成的客户端代码,常常和特定传输方式是相关的(好比webservices stub),这样给切换远程调用方式也会带来障碍。
所以,就像在一般应用中,咱们要包装数据访问逻辑(OO中的DAO或者Repository模式),或者包装基础服务访问逻辑(OO中的Gateway模式)同样,在较理想的SOA设计中,咱们也能够考虑包装远程service访问逻辑,因为没有恰当的名称,暂时称之为Delegate Service模式,它由消费端本身主导定义接口和参数类型,并将调用转发给真正的service客户端生成代码,从而对它的使用者彻底屏蔽了服务契约,这些使用者甚至不知道这个服务究竟是远程提供的的仍是本地提供的。
此外,即便咱们在消费端是采用某些手工调用机制(如直接构建和解析json等内容,直接收发JMS消息等等),咱们一样能够用delegate service来包装相应的逻辑。
delegate service示例1:
// ArticlesService是消费端自定义的接口 class ArticleServiceDelegate implements ArticlesService { // 假设是某种自动生成的service客户端stub类 private ArticleFacadeStub stub; public void deleteArticles(List<Long> ids) { stub.deleteArticles(ids); } }
delegate service示例2:
// ArticlesService是消费端自定义的接口 class ArticleServiceDelegate implements ArticlesService { public void deleteArticles(List<Long> ids) { // 用JMS和FastJson手工调用远程service messageClient.sendMessage(queue, JSON.toJSONString(ids)); } }
从面向对象到面向服务,再从面向服务到面向对象
总结上面的几个原则,虽然只是谈及有限的几个方面,但大体也可看出OO和SO在实际的设计开发中仍是有很多显著的不一样之处,并且咱们没有打算用SO的原则来取代过去的OO设计,而是引入额外的层次、对象和OO设计模式,来补充传统的OO设计。
其实就是造成了这种调用流程:
service提供端:OO程序 <- SOA层(Facade和DTO)<- 远程消费端
service消费端:OO程序 -> Delegate Service -> SOA层(Facade和DTO 或者 其余动态调用机制)-> 远程提供端
Facade、DTO和Delegate Service负责作OO到SO和SO到OO的中间转换。
如今,能够回答Echo示例中的问题:经过“透明的”配置方式,将OO程序发布为远程Service,虽然可能较好的完成了从本地对象到远程对象的跨越,但一般并不能较好的完成OO到SO的真正跨越。
同时,透明配置方式也一般没法直接帮助遗留应用(如ERP等)转向SOA。
固然,在较为简单和使用范围肯定颇有限应用(好比传统和局部的RPC)中,透明式远程service发布会带来极大的便利。
另外,上面对SO的全部讨论都集中在RPC的方式,其实SO中也用message的方式作集成,它也是个大话题,暂时不在此详论了。
为何不能放弃面向对象?
SO是有它的特定场景的,好比远程的,范围不定的客户端。因此它的那些设计原则并不能被借用来指导通常性的程序开发,好比不少OO程序和SO原则彻底相反,常常都要提供细粒度接口和复杂参数类型以追求使用的使用灵活性和功能的强大性。
就具体架构而言,我认为SOA层应该是一个很薄的层次(thin layer),将OO应用或者其余遗留性应用加以包装和适配以帮助它们面向服务。其实在一般的web开发中,咱们也是用一个薄的展示层(或者叫Web UI层之类)来包装OO应用,以帮助它们面向浏览器用户。所以,Façade、DTO等不会取代OO应用中核心的Domain Model、Service等等 (这里的service是OO中service,未必是SO的)。
综合起来,造成相似下面的体系结构:
理想和现实
须要特别指出的是,上面提到的诸多SO设计原则是在追求一种相对理想化的设计,以达到架构的优雅性,高效性,可重用性,可维护性,可扩展性等等。
而在现实中任何理论和原则均可能是须要做出适当妥协的,由于现实是千差万别的,其状况远比理论复杂,很难存在放之四海而皆准的真理。
并且不少方面彷佛原本也没有必要追求完美和极致,好比若是有足够能力扩充硬件基础设施,就能够考虑传输一些冗余数据,选择最简单传输方式,并多来几回远程调用等等,以减轻设计开发的工做量。
那么理想化的原则就没有意义了吗?好比领域驱动设计(Domain-Driven Design)被普遍认为是最理想的OO设计方式,但极少有项目能彻底采用它;测试驱动开发也被认为是最佳的敏捷开发方式,但一样极少有团队能完全采用它。可是,恐怕没有多少人在了解它们以后会否定它们巨大的意义。
理想化的原则能够更好的帮助人们理解某类问题的本质,并作为好的出发点或者标杆,帮助那些能够灵活运用,恰当取舍的人取得更大的成绩,应付关键的挑战。这正如孔子说的“取乎其上,得乎其中;取乎其中,得乎其下;取乎其下,则无所得矣”。
另外,值得一提的是,SOA从它的理念自己来讲,就带有一些的理想主义的倾向,好比向“全世界”开放,不限定客户端等等。若是真愿意按SOA的路径走,即便你是个土豪,偷个懒比浪费网络带宽重要,但说不定你的不少用户是土鳖公司,浪费几倍的带宽就大大的影响他们的利润率。
延伸讨论:SOA和敏捷软件开发矛盾吗?
SOA的服务契约要求至关的稳定性,一旦公开发布(或者双方合同商定)就不该该有常常的变动,它须要对不少方面有极高的预判。而敏捷软件开发则是拥抱变化,持续重构的。软件设计大师Martin Fowler把它们归结为计划式设计和演进式设计的不一样。
计划理论(或者叫建构理论)和演进理论是近代哲学的两股思潮,影响深远,派生出了好比计划经济和市场经济,社会主义和自由主义等等各类理论。
可是,计划式设计和演进式设计并不绝对矛盾,就像计划经济和市场经济也不绝对矛盾,非此即彼,这方面须要在实践中不断摸索。前面咱们讨论的设计原则和架构体系,就是将SOA层和OO应用相对隔离,分而治之,在SOA层须要更多计划式设计,而OO应用能够相对独立的演进,从而在必定程度缓解SOA和敏捷开发的矛盾。
延伸讨论:SOA和REST是一回事吗?
从最本质的意义上讲,REST(Representational State Transfer)实际是一种面向资源架构(ROA),和面向服务架构(SOA)是有根本区别的。
例如,REST是基于HTTP协议,对特定资源作增(HTTP POST)、删(HTTP DELETE)、改(HTTP PUT)、查(HTTP GET)等操做,相似于SQL中针对数据表的INSERT、DELETE、UPDATE、SELECT操做,故REST是以资源(资源能够类比为数据)为中心的。而SOA中的service一般不包含这种针对资源(数据)的细粒度操做,而是面向业务用例、业务流程的粗粒度操做,因此SOA是以业务逻辑为中心的。
可是在实际使用中,随着许多REST基本原则被不断突破,REST的概念被大大的泛化了,它每每成为不少基于HTTP的轻量级远程调用的代名词(例如前面提到过的HTTP + JSON)。好比,即便是著名的Twitter REST API也违反很多原始REST的基本原则。
在这个泛化的意义上讲,REST也能够说是有助于实现SOA的一种轻量级远程调用方式。
SOA架构的进化
前面讨论的SOA的全部问题,基本都集中在service自己的设计开发。但SOA要真正发挥最大做用,还须要不断演进成更大的架构(也就是从微观SOA过渡到宏观SOA),在此略做说明:
第一个层次是service架构:开发各类独立的service并知足前面的一些设计原则,咱们前面基本都集中在讨论这种架构。这些独立的service有点相似于小孩的积木。
第二个层次是service composition(组合)架构:独立的service经过不一样组合来构成新的业务或者新的service。在理想状况下,能够用一种相似小孩搭积木的方式,充分发挥想象力,将独立的积木(service)灵活的拼装组合成新的形态,还可以自由的替换其中的某个构件。这体现出SOA高度便捷的重用性,大大提升企业的业务敏捷度。
第三个层次是service inventory(清单)架构:经过标准化企业服务清单(或者叫注册中心)统一的组织和规划service的复用和组合。当积木愈来愈多了,若是还满地乱放而没有良好的归类整理,显然就玩不转了。
第四个层次是service-oriented enterprise架构……
总结
至此,咱们只是简要的探讨了微观层面的SOA,特别是一些基本设计原则及其实践方式,以期可以略微展现SOA在实践中的本质,以有助于SOA更好的落地,进入平常操做层面。
最后,打个比方:SOA不分贵贱(不分语言、平台、组织),不远万里(经过远程调用)的提供服务(service),这要求的就是一种全心全意为人民服务的精神……
来源:
http://kb.cnblogs.com/page/505537/
http://www.infoq.com/cn/articles/micro-soa-1