量互联网公司都在拥抱SOA和服务化,但业界对SOA的很多讨论都比较偏向高大上。本文试图从稍微不同的角度,以相对接地气的方式来讨论SOA,
集中讨论SOA在微观实践层面中的缘起、本质和具体操作方式,另外也用相当篇幅介绍了当今互联网行业中各种流行的远程调用技术等等,比较适合从事实际工作
的架构师和程序员来阅读。
亚马逊CEO杰夫•贝佐斯:鲜为人知的SOA大师
- 所有团队的程序模块都要以通过Service Interface 方式将其数据与功能开放出来。
- 团队间的程序模块的信息通信,都要通过这些接口。
- 除此之外没有其它的通信方式。其他形式一概不允许:不能使用直接链结程序、不能直接读取其他团队的数据库、不能使用共享内存模式、不能使用别人模块的后门、等等,等等,唯一允许的通信方式只能是能过调用 Service Interface。
- 任何技术都可以使用。比如:HTTP、Corba、Pubsub、自定义的网络协议、等等,都可以,贝佐斯不管这些。
- 所有的Service Interface,毫无例外,都必须从骨子里到表面上设计成能对外界开放的。也就是说,团队必须做好规划与设计,以便未来把接口开放给全世界的程序员,没有任何例外。
- 不这样的做的人会被炒鱿鱼。
Amazon已经把文化转变成了“一切以Service第一”为系统架构的公司,今天,这已经成为他们进行所有设计时的基础,包括那些绝不会被外界所知的仅在内部使用的功能。那
时,如果没有被解雇的的恐惧他们一定不会去做。我是说,他们今天仍然怕被解雇,因为这基本上是那儿每天的生活,为那恐怖的海盗头子贝佐斯工作。不
过,他们这么做的确是因为他们已经相信Service这就是正确的方向。他们对于SOA的优点和缺点没有疑问,某些缺点还很大,也不疑问。但总的来说,这
是正确的,因为,SOA驱动出来的设计会产生出平台(Platform)。
SOA漫谈:宏观与微观
-
宏观SOA:
面向高层次的部门级别、公司级别甚至行业级别;涉及商业、管理、技术等方面的综合的、全局的考
虑;架构体系上包括服务治理(governance,如服务注册,服务监控),服务编排(orchestration,如BPM,ESB),服务协同
(choreography,更多面向跨企业集成)等等。我认为SOA本身最主要是面向宏观层面的架构,其带来益处也最能在宏观高层次上体现出来,同时大
部分SOA的业界讨论也集中在这方面。 - 微观SOA:面向有限的、局部的团队和个人;涉及独立的、具体的服务在业务、架构、开发上的考虑。
SOA定义
Service示例(代码通常以java示例)public
interface Echo { String echo(String text);}public class EchoImpl
implements Echo { public String echo(String text) { return
text; }}
在将Echo发布为Java
WebServices,并由底层框架自动生成WSDL来作为标准化的服务契约,这样就能与远程的各种语言和平台互操作了,较好的解决了上面提到的松耦合
和可重用的问题。按照一般的理解,Echo似乎就成为比较理想的SOA service了。
- 将一个普通的Java对象通过添加注解“透明的”变成WebServices就完成了从面向对象到面向服务的跨越?
- 通过Java接口生成WSDL服务契约是好的方式吗?
- WebServices是最合适远程访问技术吗?
面向对象和面向服务的对比
- 多
数OO接口(interface)都只被有限的人使用(比如团队和部门内),而SO接口(或者叫契约)一般来说都不应该对使用者的范围作出太多
的限定和假设(可以是不同部门,不同企业,不同国家)。还记得贝佐斯原则吗?“团队必须做好规划与设计,以便未来把接口开放给全世界的程序员,没有任何例
外”。 - 多数OO接口都只在进程内被访问,而SO接口通常都是被远程调用。
是SO接口使用范围比一般OO接口可能广泛得多。我们用网站打个比方:一个大型网站的web界面就是它整个系统入口点和边界,可能要面对
全世界的访问者(所以经常会做国际化之类的工作),而系统内部传统的OO接口和程序则被隐藏在web界面之后,只被内部较小范围使用。而理想的SO接口和
web界面一样,也是变成系统入口和边界,可能要对全世界开发者开放,因此SO在设计开发之中与OO相比其实会有很多不同。
小结
微观SOA:服务设计原则及其实践方式(下篇)
上一篇文章中,我说到SOA是一个特别大的话题,不但没有绝对统一的原则,而且很多原则本身的内容也具备相当模糊性和宽泛性。虽然我们可以说 SOA ≈
模块化开发 + 分布式计算,但由于其原则的模糊性,我们仍然很难说什么应用是绝对符合SOA的,只能识别出哪些是不符合SOA的。
服务设计原则1:优化远程调用
具体的业务和部署环境,比如内网、外网、同构平台、异构平台等等。有时还要考虑它对诸如分布式事务,消息级别签名/
加密,可靠异步传输等方面的支持程度(这些方面通常被称为SLA:service level
agreement),甚至还包括开发者的熟悉和接受程度等等。
- 内网 + 同框架Java客户端 + 大并发:多路复用的TCP长连接 + kryo (二进制序列化) (kryo也可以用Protostuff,FST等代替)
- 内网 + 不同框架Java客户端:TCP + Kryo
- 内网 + Java客户端 + 2PC分布式事务:RMI/IIOP (TCP + 二进制)
- 内网 + Java客户端 + 可靠异步调用:JMS + Kryo (TCP + 二进制)
- 内网 + 不同语言客户端:thrift(TCP + 二进制序列化)
- 外网 + 不同语言客户端 + 企业级特性:HTTP + WSDL + SOAP (文本)
- 外网 + 兼顾浏览器、手机等客户端:HTTP + JSON (文本)
- 外网 + 不同语言客户端 + 高性能:HTTP + ProtocolBuffer (二进制)
从性能上讲,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本身缺乏像XML那样被广泛接受的标准schema,而一般的HTTP +
JSON的远程调用方式也缺乏像Thrift,CORBA,WebServices等等那样标准IDL(接口定义语言),导致服务端和客户端之间不能形成
强的服务契约,也就不能做比如自动代码生成。所以HTTP + JSON在降低了学习门槛的同时,可能显著的增加复杂应用的开发工作量和出错可能性。
远程调用技术解释:Apache Thrift多语言服务框架
是最初来自facebook的一套跨语言的service开发框架,支持C++, Java, Python, PHP, Ruby, Erlang,
Perl, Haskell, C#, JavaScript, Node.js, Smalltalk,
Delphi等几乎所有主流编程语言,具有极好的通用性。
struct
User { 1: i32 id, 2: string name, 3: string password}service
UserService { void store(1: User user), UserProfile retrieve(1:
i32 id)}
便值得一提的是,如果上面订单用例中每个操作本身也是远程的service(通常在内网之中),这种粗粒度封装就变成了经典的service
composition(服务组合)甚至service
orchestration(服务编排)了。这种情况下粗粒度service同样可能提高了性能,因为对外网客户来说,多次跨网的远程调用变成了一次跨网
调用 + 多次内网调用。
这种粗粒度service封装和组合,经典解决方案就是引入OO中常用的Facade模式,将原来的对象屏蔽到专门的“外观”接口之后。同时,这
里也很可能要求我们引入新的service参数/返回值的数据结构来组合原来多个操作的对象模型,这就同样用到前述的DTO模式。
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; }}
如前述的article
service,OO中可以直接返回article对象,而这个article对象在OO程序内部可能做为核心的建模的domain
model,甚至作为O/R
mapping等等。而在SO如果还直接返回这个article,即使没有前面所说的冗余字段,复杂类型等问题,也可能让外部用户与内部系统的核心对象模
型,甚至O/R mapping机制,数据表结构等等产生了一定关联度,这样一来,内部的重构经常都会可能影响到外部的用户。
服务设计原则6:契约先行
是往往涉及不同组织之间的合作,而按照正常逻辑,两个组织之间合作的首要任务,就是先签订明确的契约,详细规定双方合作的内容,合作
的形式等等,这样才能对双方形成强有力的约束和保障,同时大家的工作也能够并行不悖,不用相互等待。因此SOA中,最佳的实践方式也是契约先行,即先做契
约的设计,可以有商务,管理和技术等不同方面的人员共同参与,并定义出相应的WSDL或者IDL,然后在开发的时候再通过工具自动生成目标语言的对应代
码。
于WSDL来说,做契约先行的门槛略高,如果没有好的XML工具很难手工编制。但对于Thrift
IDL或者ProtocolBuffer等来说,由于它们和普通编程语言类似,所以契约设计相对是比较容易的。另外,对于简单的HTTP +
JSON来说(假设不补充使用其他描述语言),由于JSON没有标准的schema,所以是没法设计具有强约束力的契约的,只能用另外的文档做描述或者用
JSON做输入输出的举例。
是,契约先行,然后再生成服务提供端的代码,毕竟给service开发工作带来了较大的不便,特别是修改契约的时候导致代码需要重写。因此,这里
同样可能需要引入Facade和DTO,即用契约产生的都是Facade和DTO代码,它们负责将请求适配和转发到其他内部程序,而内部程序则可以保持自
己的主导性和稳定性。
服务设计原则7:稳定和兼容的契约
服务设计原则8:契约包装
此,就像在通常应用中,我们要包装数据访问逻辑(OO中的DAO或者Repository模式),或者包装基础服务访问逻辑(OO中的
Gateway模式)一样,在较理想的SOA设计中,我们也可以考虑包装远程service访问逻辑,由于没有恰当的名称,暂时称之为Delegate
Service模式,它由消费端自己主导定义接口和参数类型,并将调用转发给真正的service客户端生成代码,从而对它的使用者完全屏蔽了服务契约,
这些使用者甚至不知道这个服务到底是远程提供的的还是本地提供的。
//
ArticlesService是消费端自定义的接口class ArticleServiceDelegate implements
ArticlesService { // 假设是某种自动生成的service客户端stub类 private
ArticleFacadeStub stub; public void deleteArticles(List<Long>
ids) { stub.deleteArticles(ids); }}
//
ArticlesService是消费端自定义的接口class ArticleServiceDelegate implements
ArticlesService { public void deleteArticles(List<Long> ids) {
// 用JMS和FastJson手工调用远程service
messageClient.sendMessage(queue, JSON.toJSONString(ids)); }} 从面向对象到面向服务,再从面向服务到面向对象
为什么不能放弃面向对象?
具体架构而言,我认为SOA层应该是一个很薄的层次(thin
layer),将OO应用或者其他遗留性应用加以包装和适配以帮助它们面向服务。其实在通常的web开发中,我们也是用一个薄的展现层(或者叫Web
UI层之类)来包装OO应用,以帮助它们面向浏览器用户。因此,Façade、DTO等不会取代OO应用中核心的Domain
Model、Service等等 (这里的service是OO中service,未必是SO的)。
理想和现实
么理想化的原则就没有意义了吗?比如领域驱动设计(Domain-Driven
Design)被广泛认为是最理想的OO设计方式,但极少有项目能完全采用它;测试驱动开发也被认为是最佳的敏捷开发方式,但同样极少有团队能彻底采用
它。但是,恐怕没有多少人在了解它们之后会否认它们巨大的意义。
延伸讨论:SOA和敏捷软件开发矛盾吗?
是,计划式设计和演进式设计并不绝对矛盾,就像计划经济和市场经济也不绝对矛盾,非此即彼,这方面需要在实践中不断摸索。前面我们讨论的设计原则
和架构体系,就是将SOA层和OO应用相对隔离,分而治之,在SOA层需要更多计划式设计,而OO应用可以相对独立的演进,从而在一定程度缓解SOA和敏
捷开发的矛盾。
延伸讨论:SOA和REST是一回事吗?
如,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的基本原则。
SOA架构的进化
- 第一个层次是service架构:开发各种独立的service并满足前面的一些设计原则,我们前面基本都集中在讨论这种架构。这些独立的service有点类似于小孩的积木。
- 第二个层次是service composition(组合)架构:
独立的service通过不同组合来构成
新的业务或者新的service。在理想情况下,可以用一种类似小孩搭积木的方式,充分发挥想象力,将独立的积木(service)灵活的拼装组合成新的
形态,还能够*的替换后其中的某个构件。这体现出SOA高度便捷的重用性,大大提高企业的业务敏捷度。 - 第三个层次是service inventory(清单)架构:通过标准化企业服务清单(或者叫注册中心)统一的组织和规划service的复用和组合。当积木越来越多了,如果还满地乱放而没有良好的归类整理,显然就玩不转了。
- 第四个层次是service-oriented enterprise架构……
总结