一.理想的集成应该是什么样的?
1.避免破坏性修改
如果在一个微服务的响应中添加一个字段,服务的消费方不应该受到影响。
2.保证API的技术无关性
微服务之间的通信应该是与技术无关的。
3.使服务的消费方易于使用
如果消费方使用该服务比登天还难,那么无论该微服务多漂亮都没用任何意义。但同时,易于使用的服务可能内部封装了很多细节,这会增加耦合。
4.隐藏内部实现细节
消费方与服务方的内部细节应该是分开的,如果与细节绑定,则意味着改变服务内部的一些变化,消费方也要跟着修改,这会增加修改的成本。
二.数据库是否应该共享
共享数据库是最快的集成方式,但这种方式应该避免。
如果是这样,那么外部的服务则能查看内部的实现细节,并与其绑定在一起,存储在数据库中的数据结构对所有人来说都是平等的,数据库是一个很大的共享API,那么为了不影响其他服务,必须非常小心地避免修改与其他服务相关的表结构。这样的情况下,需要做大量的回归测试来保证功能的正确性。
如果是这样,消费方与服务方的特定技术绑定在了一起,如果消费方要从关系型数据库换成非关系型数据库,那么这样是不容易实现的。这不符合低耦合的原则。
如果是这样,那么原本由服务方提供的修改,现在可以由各个消费方直接操作数据库来完成,而且每个消费方可能都会有一套自己的修改方法。这不符合高内聚的原则。
三.服务的协作:同步or异步?
对于同步方式而言,我们可以用请求/响应来概括整个过程,发起方发起一个远程调用后,发起方会阻塞自己并等待整个操作的完成,同步对于响应的低延迟有着高要求,因而在现在,这也是不太实际的。我们通常选用方式。
异步的通信模型有两种。
一种是请求/响应方式,这与同步的不同,它是在发起一个请求时,同时注册一个回调,当服务端操作结束之后,会调用该回调。
一种是基于事件的方式,服务提供方不发起请求,而是发布一个事件,然后期待调用方接收消息,并知道该怎么做,服务提供方不需要知道该或者什么会对此做出响应,这也意味着,你可以在不影响服务提供方的情况下对该事件添加新的订阅。
四.服务的编排与协同
如果你注册一家网站,它在注册时做了下面3件事:
(1)在客户的积分账户上创建一条记录
(2)通过EMS系统发送一个欢迎礼包
(3)向客户发送欢迎电子邮件
当考虑实现时,编排是一种架构风格,它由一个中心大脑来指导并驱动整个流程。它可由客户管理这个服务来承担,在创建客户时会跟积分账户服务、电子邮件服务及EMS服务通过请求/响应的方式进行通信。客户管理服务可以对当前进行到了哪一步进行跟踪,它会检查积分账户是否创建成功、电子邮件是否发送出去、EMS包裹是否寄出。这种方式的缺点是:客户管理服务承担了太多职责,它会成为网关结构的中心和很多逻辑的起点。这样会导致少量的“上帝”服务,而与其打资产的那些服务通常会沦为贫血的CRUD服务。
另一种实现的风格则是协同,它仅仅告知各个系统各自的职责,具体的实现留给他们自己。就上例而言,客户管理服务创建一个事件,邮件服务、积分服务、EMS服务会订阅这些事件并做相应的处理,如果其他的服务也关心客户创建这件事情,它们只需要简单的订阅该事件即可。这种方式能显著地消除耦合,但这需要额外做一些监控工作,以保证其正确进行。我们可以建一个跟业务流程相匹配的跟服务的监控服务,分别监控每个服务。
五.服务的版本管理
1.尽可能推迟修改
比如我们可以使用XPATH来读取XML,它可以忽略XML的一些修改,Martin Fowler称之为容错性读取器(http://martinfowler.com/bliki/TolerantReader.html)。
接收消息的一方应尽可能灵活地消费服务,这就是鲁棒性原则(https://tolls.ietf.org/html/rfc761),它认为每个模块都应该宽进严出。
2.及早发现破坏性修改
尽量对修改的影响进行全面的回归。
3.使用主义化的版本管理
每个服务的版本号支持:Major.Minor.Patch的格式,其中Major的改变意味着其中包含着向后不兼容的修改;Minor的改变意味着有新功能的加入但应该是向后兼容;Patch的改变代表对已有功能的缺陷修复。
4.不同版本的接口共存
当不得不这么做时,我们的生产环境可以同时存在接口的新老版本。
假如一个接口存在着V1、V2、V3三种版本,我们可以在所有对V1的请求转换给V2,然后V2转换给V3,这样是一种平滑的过度,首先扩张服务的能力,对新老两种都支持,然后等老的消费者都采用了新的方式,再通过收缩API去掉旧的功能。
我们也可以在URI中存放版本信息,但同时我们需要一套方法来对不同的请求进行路由。
5.不同版本的服务共存
短期内同时使用两个版本的服务是合理的,尤其是当你做蓝绿部署或者金丝雀发布时,在这些情况下,不同版本的服务可能只会存在几分钟或者几个小时,而且一般只会有两个版本。升级消费者到新版本的时间赵长,就越应该考虑在同一个微服务中暴露两套API的做法。
六.服务的UI管理
PC端的程序我们需要关注浏览器和分辨率;移动端的程序我们需要关注带宽和手机的电量,甚至是地区发展的水平(也许使用短信登录更符合当地的实际);在平板上,我们很难使用右键操作。我们通过把服务的功能进行不同的组合,可以为Web、移动端、可穿戴设备提供不同的体验。那么,如何很好的组织起来呢?这就是UI管理面临的问题。
如果组织一个移动端的页面,需要调用20个服务,那么对于移动设备来说会有些吃力,我们可以使用API Getway来缓解这一问题,这种模式下多个底层的调用会被聚合成一个调用。
另外一种方式是让服务暴露出一部分UI,然后只需要简单地把这些片段组合在一起就可以创建出整体UI。也许音乐商店的订单管理团队可以对所有与订单管理相关的页面负责。但在最后,我们仍然需要将这些UI集成在一起,我们可以使用类似服务器模板的技术来实现。这种方式的优势是可以完成快速修改,但很难保证用户体验的一致性,因为各个服务的维护,它们给出的UI风格可能迥异,同时如果你给PC、Web、平板、移动端都提供HTML方式的UI,那么你需要进行一系列的组件嵌入处理,而原生的应用有可能提供更大好的体验。
七.与外系统集成
可以在外系统上再包装出一些服务,对调用者隐藏细节,或者捕获所有的请求,并决定是否路由到遗留代码还是导向新的代码中,这种方式可以帮助我们从老系统进行替换,从而避免影响过大的重写。
参考
《微服务设计》(Sam Newman 著 / 崔力强 张骏 译)