在阅读本文之前,你需要先对前面的背景有些了解,下面是本系列的前两篇文章:
每天都在谈SOA和微服务,但你真的理解什么是服务吗?
经过前面的热身,我相信后面两章我们谈S++不会那么生涩了,尤其是第一篇发表后一位叫lpy的架构师指出:“除非有必要,我们不要创造新的概念”,这就是著名的 奥卡姆剃刀定律“如无必要,勿增实体” 。本章主要谈S++一些基本的特性和基础应用,看看我们新增的基础概念是否该被剃刀无情的剔除。然后我们在下一章重点探讨S++如何解决微服务带来的问题以及---SOA的终极目标也就是业务敏捷性。
我们先简单回顾一下S++的定义: 服务的本质就是行为(业务活动)的抽象 。与SOA和微服务相比较,S++不仅仅进行了简单抽象,更重要的是完成了服务的 业务与技术 分离 以及服务的 多态建模 。
那么,S++作为SOA的更高级版本,为了完成SOA的终极目标也就是业务敏捷性,必须具有以下几个特征:
在S++中,通过引入 服务元数据 的概念来描述业务要素,服务元数据的引入是实现 业务与技术 分离的重要手段。通过服务元数据的全局唯一性来保证业务要素的全局唯一性,举例说明如果两个不同的服务中都包含元数据A的引用,那么在这两个服务中的这个业务要素是一致的,所谓一致是指的在同一个业务流程的上下文中,这两个字段的取值是相同的。下面大家来看个简单的例子来理解一下如何用元数据来表达服务:
元数据定义表,元数据ID全局唯一,不可重复。
服务定义树:
我们看到,由于元数据全局唯一,所以决定了要素一致的服务是相同的。
服务要素元数据化后,元数据的 时空唯一性 就决定了服务在时空上也是唯一的。当任意两个服务,如果其定义中引用的所有元数据集合完全一致的时候,无论这两个服务的结构上有什么差异,这两个服务都是同一个服务。
服务的 时空稳定性 是指,服务的一旦被严肃的定义出来,那么在任何使用环境中、任何时刻其已经明确的业务要素不会被改变,任何要素的改变意味着一个新的服务被定义。比如打球含有两个要素:人和球,无论在任何时间任何地点打球都需要这两个要素;假设某个场景需要去掉球这个要素,那么需要定义一个新的服务,这个服务可以只有人这个要素,但新的服务可能就是打架了。
服务多态性是指,服务可以在运行时刻动态的转变为另外一个服务的特性。
所有与服务的具体实现者无关的服务属性,都属于服务的内涵范畴。除了服务的基本信息之外,主要还包含三大类:
服务的SDA(Service Definition Agreement),服务的定义部分。
服务的SLA(Service Level Agreement),对服务提供者的服务水平的约束部分。
服务的OLA(Service Operation Agreement)的一部分,对服务的操作层面的描述。
从服务的内涵来看,服务具有唯一性,只要具有相同要素的服务,无论你是否愿意承认,他们都是一种行为。而且,服务不但具有空间的唯一性,还具有时间的唯一性,比如古代的餐饮服务,和现代的餐饮服务,其实在要素上几乎没有差别。我可以推测,马车时代的修车服务和现代的修车服务在要素上也不会有什么差异,甚至可以产生完全一致的抽象。
自然地每一个服务具体的实现者都是服务的外延,为方便起见我们也可以把服务的消费者也看作服务的外延。每一个服务的外延都有各自的不同,事实上传统大服务的WebService和微服务的RESTful都可以看作服务的不同外延,我们管这种外延叫服务的接口。
服务通过一致的内涵来统一服务的功能,通过多样化的外延来适应不同的消费需求。其实,正是通过服务的多样性外延我们才可以体验服务在空间和时间上的一致性。在这里我们再次去体会第一章骑大象的例子,同样一个事情,从不同的角度看就具有不同的要求。那么上面那个简单的例子里,服务一样需要外延来表达,比如下面的接口定义:
画红圈的部分就是一个特定的实现需要额外添加的技术要素,这些技术要素是实现者因为技术或管理原因要求的。比如序列号,是因为系统需要标识每一笔交易不能重复;柜员号是因为从管理上要求知道每一笔交易是哪个业务员操作的,以便未来的绩效、查错等。而这些技术要素就没有绑定业务元数据,所以就不会和业务相混淆。
传统的,如果我们把WebServices或者RESTful当作服务来看的话,那么服务一定是有版本的。但是,根据S++对服务内涵的定义,服务具有空间和时间上的一致性,所以服务本身是不需要版本的。服务的变迁是因为我们对服务的抽象过程的缺陷造成的,一旦最新的抽象形成了,那么这个抽象必然完全满足时间和空间上的所有外延实现(大家可以回顾打球和打架的例子)。这就是说,我们只需要保存最新的内涵抽象就够了,那么历史的版本在哪里呢?其实,历史的版本在已经使用的外延中帮你保存了,当然为了更好的记录变迁,你可以对服务的变更过程做一些批注记录。
其实,传统服务的多版本对SOA甚至是有坏处的,比如在服务组合流程当中。当我们认定服务在空间和时间上的唯一性后,服务的组合中调用的原子服务就具备唯一性,一旦版本被引入后破坏了服务在时间上的唯一性,那么服务组合就要决定其调用的原子服务在不同时间点的副本,这本身和业务流程毫无关系,从而破坏了业务流程的完整性造成流程不稳定。
业务与技术分离是个老生常谈的话题,在S++中服务的业务和技术分离包含两个方面:
通过上面元数据与元结构的概念说明,可以看到服务对外表达是通过一个结构化的树状数据结构完成的,这种表达有利于在技术上帮助服务的消费者通过结构化的报文完成服务的调用。而元结构的结构点可以和业务元数据绑定,意味着任何一种结构的数据都可以转化成仅关注业务要素的服务内涵。
这种分离带来两个好处:就是可以在不改变服务内涵的前提下任意的修改服务对外的技术表达形式和结构( 对相同内涵的任意表达 );或者在保证不修改服务对外技术表达的前提下,通过更改业务要素元数据的方法来改变服务的内涵( 对存量表达的任意兼容 )。这种分离给系统带来巨大的灵活性。
为了进一步的完成业务与技术的分离,在S++中将服务的最基本元结构作为服务内涵和缺省外延(缺省对外技术接口)的同时,又进一步的给服务增加更多的独立接口作为满足不同需求的技术外延。
在接口中,可以描述不同结构的、经过裁剪和定制的服务要素,并且可以携带与服务内涵无关的技术要素。这样就可以使服务的抽象定义满足各种不同场景的技术需求,提高系统的灵活性和适应性。
前面举的只是一个很简单的例子,其实在服务的接口中可以描述非常丰富的技术信息,包括报文的类型、数据的Format、数据的边界检查、与元数据不同的数据类型(方便数据转换),甚至数据的拆分合合并等等。实际上,接口就是一个从服务实现到服务的一个报文映射和翻译规则描述文件。
服务的多态性是S++的一个重要特性,也是实现SOA业务敏捷性的一个关键。
服务的多态性是指的业务流程在开发态所引用的服务,在运行态会根据业务要素的内容动态的变化为与开发态所定义的服务不同的、但具有一定继承关系的服务。
举个例子说明,开发态流程编写中会调用缴费服务,但是在运行态会根据“待缴费ID”的内容进行判断,动态的变化为调用诸如缴电话费、缴水费、缴煤气费等等不同的服务。
服务多态具有以下特征:
服务多态必须是运行态提供的能力,开发态只需要关注服务的抽象定义,从而实现业务流程的稳定性。
服务多态必须对业务透明。即,在业务流程中不应该出现与运行时动态变化相关的代码。如果在业务流程中需要判断一些要素,根据要素由业务来完成服务的动态变化,那么就不是服务多态。
服务多态必须支持跨系统的远程服务调用。当业务流程中需要调用外部服务的时候,服务动态变化的实现必须是自动的,不需要本地业务系统进行支撑。
服务多态必须自动的支持业务要素的重载和映射转换。当业务流程调用的父类抽象服务时,其业务要素可以被子类服务的业务要素重载,例如缴费服务中一个业务要素“待缴费标识号”,缴电话费子类中就会被重载为“待缴费电话号码”,那么这个重载映射的过程必须由系统来完成,应用应该完全透明。
服务多态性主要为解决系统间相互访问产生的耦合性问题,由于面向对象方法引入的强耦合性,造成了传统的系统间调用必须手工的维护业务分支,从而进一步导致业务流程不稳定难以维护。
服务多态主要面向系统间相互访问,系统内更高效的方法还是对象的多态性。
服务多态用于解决系统扩展问题,防止业务流程中服务节点的扩展对流程本身造成影响,从而导致需要人工修改流程的问题。
服务多态可以屏蔽同类业务分支,例如缴费服务存在缴电话、缴水费等这些种情况;对于不同类型的业务分支无法利用多态性,例如当账户余额充足时缴费,不足时返回异常,这样的分支是无法利用多态性进行简化的。
服务多态不能解决应用架构问题,就如面向对象一样,在不同应用架构下服务多态的实现方法可能有很大的差异,甚至某些架构下服务多态性不一定能优雅的实现。
S++的出现最初就是要解决流程稳定性问题,所以业务和技术分离第一个主要的应用场景就是消除掉传统业务流程编排过程中和业务无关的(造成流程不稳定的)环节。举个例子来说明:
在某流程X中,先后调用服务A和B,那么传统的任何要在A节点和B节点之间要传递的数据,我们会定义一个技术节点,这个技术节点大体上做的事情就是赋值,比如B.field1=A.return1; B.field2=A.return2….
假如未来A和B服务发生了变化,比如增加了字段,那么就需要修改这个赋值节点,增加诸如B.fieldx=A.returnx这样的赋值映射,这个过程就会造成流程的不稳定。也就是说任何服务的变更都会影响已经做好的流程的改变,这样的话我们就不能大规模的利用流程编排来开发业务系统,这也是传统SOA和微服务架构无法避免的问题,这个问题导致了SOA理念无法完整的实施落地。
S++通过元数据隔离了元结构之间的差异性,在服务治理的过程中,通过定义B.fieldx=Metadatax;A.returnx=Metadatax,将元数据Metadatax与具有相同业务要素的元节点绑定,这样在上述的业务流程中,系统就会自动的动态的生成形如B.field1=A.return1; B.field2=A.return2这样的代码,如果服务A和B发生上述变更,编译器会动态的生成代码B.fieldx=A.returnx,从而自动的维护了流程的稳定性。
我们知道,服务的抽象定义是一个过程,谁也不能保证定义的完美无缺,但是服务的修改往往会带来所有相关系统的修改,非常麻烦。举个虚构的例子:
我们定义一个取款服务,其中账号定义为AccountNo,我们为账号这个元节点绑定一个元数据(假设也叫AccountNo)。
我们又定义了一个存款服务,其中账号也被定义为AccountNo(有可能这个两个服务不是一个人定义的),并且也被缺省的绑定了同名元数据AccountNo。
随后有个需求(假设叫转账服务)需要定义一个流程,这里需要先进行取款操作,然后将取出的款项存入另外一个账号。那么会为转账服务定义一个转出账号AccountNo_Out,一个转入账号AccountNo_In,显然这两个业务要素是不同的,所以分别为他们绑定不同的同名元数据。
问题来了,由于当初定义取款和存款服务的时候,没有考虑到这两个服务的组合情况,所以如果按上一小节中同名元数据映射的方式,存款和取款账号作为同一个业务要素就具有相同的数值,显然就无法完成转账业务了。
传统的解决方案是在转账流程中增加手工的映射节点,但是这样显然就影响了流程的稳定性,而且增加了开发人员的工作量和出错的概率。但是如果我们去改变服务的结构,比如把取款服务中的AccountNo节点改名为AccountNo_Out,存款服务中的节点改名为AccountNo_In,就会造成现存的服务访问者需要变更程序,显然也是不可取的。
针对这个问题,S++的解决办法是保持存、取款服务对外的结构不变,AccountNo这个节点的名称维持不变,但是将AccountNo所绑定的同名元数据变更为AccountNo_Out和AccountNo_In。这样,在转账服务的流程中系统就会自动的完成正确的数据映射,同时服务对外的接口不发生任何变化,也不会引起存量的服务消费者变更程序。
通过S++的业务与技术分离,可以简洁的、完全透明的解决服务变更引起的外围变更,从而达成了服务在时空上的相对稳定性。
S++服务的时空唯一性和稳定性可以通过接口的形式来体现,当服务的外围系统(包括消费者或提供者)发生变化时,比如升级或更换系统,那么在不修改服务内涵的前提下就可以通过定义新的服务接口来适应外围变更,从而减小甚至消除变更对整体带来的影响。
其实,从某种意义上,服务的定义可以看作一种缺省的接口,服务内涵仅关注和定义无结构的、扁平化的元数据作为业务要素。那么不同的接口可以看成用不同方式来定义的服务,用于适应不同的服务消费者和服务提供者,接口就像一个翻译工具,可以通过接口把不同的语言和方言翻译成按元数据语言描述的服务。
服务的业务与技术分离以及多态性,带来一个意想不到的可能性,也就是说我们可以建立与业务和技术都完全无关的 抽象的流程模型 。我也没有深入思考过的这个问题,下面只是简单描述一下 抽象流程模型 的概念。
我们知道,做任何稍微复杂的事情一般都会有一定的步骤,我们社会中有很多做事的步骤是相类似的,这是因为长期的社会实践积累了大量的经验,这些经验和知识往往都包含了经过实践验证的合理的做事步骤。这种步骤是非常抽象的,可以适用于非常多的不同的社会活动。举个例子,比如购物这种商业行为,无论你是购买的有型的商品,还是购买无形的服务,我们都可以用一个高度抽象的商业流程来概括完成,如下:
首先我们需要一个商品检索挑选的流程
然后我们需要一个维护购物车的流程
接下来需要一个下订单的流程
然后是支付的流程
然后是物流送货流程
最后是售后流程
事实上,淘宝之类的购物系统已经完成了这种什么都可以卖的需求,但我并不知道他的内部是否是用一种高度抽象的流程来完成所有类似的商业行为的。传统的业务流程编排工具是无法完成的,因为传统的流程工具在编排流程的过程中需要关注业务要素,就比如我们前面举得例子,需要完成B.fieldx=A.returnx这样的工作,所以这样的流程就无法满足各种不同的服务A和服务B的编排(因为A和B的要素会随着业务的不同而发生变化)。同时,由于传统的流程引擎无法解决多态问题,所以必须在流程中引用业务服务的具体实现,所以无法运行一个高度抽象的流程。
S++通过业务与技术分离的技术手段,消除了流程引擎对业务要素的依赖,在被编排的流程中只需要引用服务的名称就可以,不需要关注所调用的服务的要素细节,所以这个抽象的流程就可以运行在任何一个服务系统中,只要能把特定的业务服务与流程中的节点关联上,就可以由S++系统自动的完成业务要素的匹配工作。同时,用于S++流程中可以调用和具体实现完全无关的抽象服务,这样就可以不用在开发时刻就决定流程节点的具体服务实现,让抽象服务在运行时刻再去决定真实的实现。
S++的这种特性,决定了传统流程工具无法实现的抽象流程模型成为可能。由于本文主要关注S++的特性,所以感兴趣的读者可以自己去研究如何用抽象流程实现跨行业统一的业务流程。
回顾本章,我们从S++的基本概念推导出几个有价值的推论,再基于这几个推论解决了我们在面向服务的实践中遇到的几个棘手问题,稍微有点儿烧脑…
这几个推论是:
服务具有时空唯一性和稳定性
服务无需维护版本
在不变更服务的前提下,对同一个服务可以有无数种不同的表达(接口)
服务的变更不会影响已经存在的任何一个表达(接口)
基于这几个推论,我们幸运的发现新增的概念还是能解决一些问题的,大家已经看到:
简化业务流程,稳定开发成果。
屏蔽服务变更和外围变更对系统稳定性的影响,降低系统维护成本。
想通过增加基础概念来解决实际问题,其实是非常困难的一件事,稍不留神就会被奥姆剃刀无情的剃成秃子,白白的烧脑。
下一章节我们主要探讨微服务的业务流程问题,我们所说的业务流程不仅仅是狭义的工作流,而是包含了原本业务系统用硬编码完成的对象间交互的业务逻辑,同时我们还会通过S++所支撑的算法解决一个效率问题。
李东,14岁开始学习计算机语言,作为课外兴趣自学了BASIC和汇编,利用放假期间编写了贪吃蛇、打飞碟等游戏。高中、大学期间继续自学软件编程,曾将C和汇编结合使得从高级语言中能够调用绘图功能,并模仿Borland C++开发了一套适合学校机器的图形化开发环境的原型。
93年大学毕业后在西门子合资公司作为交换机软件安装人员工作两年,然后来到JInfonet公司先后参与4GL的研发和JReport的研发。作为JReport的第一代主要研发人员,编写了从原型一直到3.0版本的核心引擎部分。2000年与合伙人一起创建了Bi-Soft公司,主营业务是商业智能软件Bi-Pilot,负责整个产品的研发及管理工作,从最基本的查询一直到多维分析模型和引擎都是产品的涵盖范围。
2007年Bi-Pilot被神州信息收购合并,李东开始在神州信息研发SmartESB产品,用SOA的方法论为客户提供底层产品服务。
感谢郭蕾对本文的审校。
给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家通过新浪微博(@InfoQ,@丁晓昀),微信(微信号: InfoQChina )关注我们。