SOA设计原则
SOA在互联网,用的是客户端/服务器的架构。实现逻辑为:客户端通过网络向服务器发送请求,服务器响应请求。
客户端/服务器架构之上的进一步抽象是面向服务的范式,这是将服务器中的信息组织成服务的模式,这个服务可以被发现、进行交互或用作已知的语义。这也就意味着该服务具有确定的行为,在给定相同的条件时,总会产生同样的结果。
这就好比你去麦当劳点餐,麦当劳提供了从“穷鬼”到“地主”级别的不同套餐,这些套餐就是这家麦当劳门店所能提供的服务,然后你为选择的服务支付费用。麦当劳生产这个订单后,员工会按照麦当劳定义好的流程,一个负责炸薯条,另一个负责制作汉堡,有条不紊地做着自己该做的事情。无论是谁、无论何时点的套餐,顾客最终都会得到他想要的食物。麦当劳的这些服务都是具有预先确定的行为和已知的术语,并且会产生可预知的结果。以下为七个具体设计原则:
一、抽象化
抽象化是SOA非常重要的一个设计原则,是指使用抽象层来隐藏网络拓扑、通信和实现的复杂性。如果不利用抽象化,而让客户端知道实现该服务的所有细节,那客户端使用该服务的方式将会严重制约服务的演化。
对于顾客而言,他们并不需要知道麦当劳内部是什么样的流程机制,里面的员工是如何进行合作分工。他们仅仅需要关心麦当劳能提供什么服务,以及对应的服务结果是什么。换言之,我们定义服务的接口时(相当于菜单),也仅仅需要把服务接口实现方式(顾客消费方式)和接口调用的结果(食物照片)定义清楚。
为了确保遵循该原则,服务的公开状态应该尽可能地少。此外,只应规定服务行为的外在表现。
二、正式合约
一个服务之所以被称为服务,是因为其给公开的功能以及如何实现提供了正规的描述,即正式合约。这其实就是我们做SOA设计时定义的服务接口,每一个接口都应该有明确的定义以及实现方式。
三、低耦合
在面向对象的软件中,单独的系统组件指的是被设计成无边界效应的独立对象。那些发生在组件之间的相互作用可以被明确地定义和测试。将依赖关系减小到最小程度,也就是低耦合,使修改服务的实现方式时不会带来意想不到的边界效应,从而降低风险。
而对于SOA低耦合的设计原则,主要包括两个方面:
1、服务的实现方式和正式合约应该分离。服务的正式合约只要不变或者修改,那不管实现方式如何变化,这都不影响。就像去麦当劳点了一个套餐,只要套餐内容不变,不管麦当劳内部人员怎么分工,是一个人炸薯条再做汉堡,还是分不同的人分别去炸薯条和做汉堡,对于服务使用方来说结果都是一样的,他并不关心服务是怎么实现。
2、服务的实现过程不要依赖另一个服务的结果。就比如麦当劳A客户点了套餐A,B客户点了套餐B,就算套餐B不能完成,那也不会影响套餐A的制作。
四、可复用性
可复用性其实是SOA设计所期望的设计目标。真正的可复用性是让服务适用于多种不同应用的能力。进行一项服务的设计时,如果没有进行认真地思考,它可能仅能满足某种特定的应用。而经过思考的良好设计,服务可以与具体的实现过程相独立,这就意味着该服务可以在其它的应用中快速地复制。
五、独立性
每一个服务与客户端的状态应该是相互独立的,它应该有它内部既有的工作流程,而不依赖于客户端的状态。这样做的目的是剥离客户端与服务端的状态交互,无论客户端来自哪里,在任何时间、任何地点请求同样的服务,服务端都以同样的方式响应,从而得到相同的结果。就像当顾客点了一个汉堡时,麦当劳门店只要按照既有流程去制作就行,期间不用关心该用户是谁、来自哪里。
六、可组合性
进行服务的设计时,为了低耦合,往往把服务设计得小而精。然而在进行汽车的服务架构开发时,整车的系统往往是复杂的。经常会有不同的用户使用场景,针对不同的使用场景,利用现有的服务,鼓励可以聚合大的服务,以实现更高级别的应用。
比如说汉堡、炸鸡、薯条、可乐这些都是属于小的服务,门店可以自由搭配组合形成新的不同套餐,在价格中形成一定的优惠,以供客户选择。应用到车上,氛围灯、座椅、空调、音乐等属于小的服务,车企可以根据不同的组合,形成不同的用户场景,比如休憩模式关闭所有灯光、音乐,座椅和空调调整到舒适位置。
七、可发现性
可发现性指的是用户可以通过某种特定的方式查询到该服务,而不是通过静态编程的方式。例如在高德地图中输入麦当劳,你就可以得到附近的门店的位置,而不用在手机中把每个门店的地址记下来。
设计案例
以控制空调的开和关为例,比较SOA两种设计方案。
方案一
服务端(空调模块)提供开和关的接口,客户端(娱乐主机)调用这个服务接口来请求空调的开和关,服务端根据自身的控制逻辑执行空调的开和关。
服务接口描述:
HVAC ON/OFF_Req:客户端可通过调用这个接口来打开或者关闭空调。
HVAC ON/OFF_Resp:服务端根据自身的逻辑,收到请求后反馈给客户端执行结果,如果正常执行就反馈“执行成功”。如果不执行,就反馈“执行失败”且会携带失败的原因,比如“执行失败,整车未上高压或者发动机未启动”。
方案二
服务端(娱乐主机)发布空调开和关的请求事件,客户端(空调模块)订阅这个事件接口,当该接口为空调开或者关请求时,用户根据自身的控制逻辑执行空调的开和关。
方案比较
从结果上分析,两种方案对用户的体验实际上是一样的,都能实现对空调的开和关,但细究下来还是有不少差异的。
方案一的优势是服务端提供统一的接口,客户端可以根据自身的需求调用。服务端可以不关注客户端是谁,只要根据自身的内部逻辑进行空调的状态跳转。
方案二其实细看就像上一代面向信号的架构,只是把之前的信号重新包装成服务的形式在以太网上传输,相当于脱裤子放屁。基本可以用现有的软件架构,把接口名字改改就可以上车,开发的代价最小,但是没有任何扩展性可言。如果有其他的APP想控制空调,那就需要重新发布一个接口,客户端(空调模块)需要重新订阅该新的接口,从而实现另一种控制方式。
从短期来看,方案二是实现所谓的SOA代价最小的方案。但是作为要面向下一个十年甚至二十年的架构设计来说,方案一才是主流的方案。虽说在开发过程中还会遇到其他问题,但是总体的架构思路和方向是对的。
SOA功能分配
上面介绍的是SOA某个服务的架构设计方案,进行系统详细设计时,比如说具体到SOA的功能分配,会面临另一个问题。
还是上文空调控制举例,比如在进行空调的控制时,需要判断车辆状态,当整车处于高压或者发动机启动时才能开启空调。
方案一中如果把对车辆状态的判断放在客户端,那么意味着每一个客户在控制空调时,都要先获取车辆的状态,只有满足车辆处于高压或者发动机启动时,才能调用空调的控制接口。服务端收到空调的控制器指令,执行相应的动作。
这么做的好处是服务端提供的接口内部逻辑可以做的很简单,不会随着前置条件的变化而变化,把控制的前置条件判断上移到客户端。坏处是对每个客户端提出了更严苛的调用条件,如果该服务仅仅针对内部软件的团队进行开发,那相对来说是可控的,可以通过设计文档审核以及测试进行把控,不会出现客户端在前置条件不满足时就调用服务端的空调控制接口的情况。
如果这个接口开源,那显然这样的设计方案不合理,因为对于开源的接口,不可能要求每个开发者严格按照规则去做,一旦有某位开发者开发的APP没有判断前置条件就直接调用空调的控制接口,那有极大的概率出现小电池被耗尽电的问题,除非有智能补电功能。
方案一中如果把对车辆的状态判断放在服务端,对于客户端来说,就可以很简单,只要想控制空调,那就调用服务端的接口,至于能不能开启,服务端会根据车辆的状态反馈给客户端。
这仅仅是针对单一车型,对于不同的车型以及应用场景,可能开启空调的前提条件不同,所以在进行服务的接口定义时,先定义和开放适用于大部分应用场景的接口,至于特殊的需求,经过评估后再确认是否需要定义新的接口。
这里阐述一个观点,服务并不是一成不变,不要想着一个服务接口就可以覆盖用户所有的使用场景。
SOA在互联网行业的成熟经验可参考,但也仅供参考,要想把这个概念用到汽车行业,且用得好,无论是使得软件复杂度减少,亦或者为将来的功能拓展带来便利性,这些都需要进行深刻的理论分析以及实践应用。