# 08|需求提炼(二):微服务集群要测什么? 你好,我是柳胜。 随着互联网发展和软件场景普及,单体应用逐渐暴露出致命缺陷,比如过于庞大,大大增加系统的复杂度、交付速度变慢,协作困难、错误难以隔离、维护成本巨大等等。 同时,软件技术也在发展,出现了VMware、Docker和Kubernetes等轻量化部署方式,这使得拆分的困难变小,部署的成本降低。微服务架构诞生后,一个系统拆分成多个独立开发和运行的服务,这个服务不管大小,业界都管它叫微服务。它们也有一套服务治理的技术规范,用来保证部署和运行的可靠性和扩展性。 微服务集群的开发方式确实方便了用户需求快速实现和交付,但今天我们的关注点是,从测试角度看,微服务相比单体应用有什么不一样?有没有新的测试点? 这一讲我们将继续延续FoodCome的例子,看看它从单体应用变成微服务架构之后,给测试工作带来的变化和挑战。 ## 微服务架构下的FoodCome 在上一讲,单体应用的FoodCome是这样的:![图片](https://static001.geekbang.org/resource/image/bd/a3/bddb6ed5729850bb7340033b437775a3.jpg?wh=1920x1369 "FoodCome单体架构图") 随着业务规模的扩大,开发人手增加,FoodCome被拆分成5个微服务,具体如下: * 订单服务:处理用户下的订单; * 物流服务:Foodcome内部的物流管理,与外部物流对接; * 餐馆服务:管理餐馆的信息,参与订单的工作流; * 账户服务:管理订单里的顾客信息,和外部的支付系统对接。 * 通知服务:产生消息通知用户,和外部的邮件系统对接。 由此组成的FoodCome的微服务架构如下图: ![图片](https://static001.geekbang.org/resource/image/b1/21/b1e740b327ea837f1278f1a44e754321.jpg?wh=1920x1030 "FoodCome微服务架构图") 在这个架构下,原先单体应用的对外接口保持不变,但是单体应用内部被5个独立的微服务取代。用户的订单请求先通过API网关到达订单服务,完成支付后,餐馆接单,再通过物流系统交付订单。 每个微服务实现自治,独立开发和发布部署,加快发布速度。而且增加新功能也很方便,比如登录鉴权,在这个图中再增加一个认证服务就可以,这是给客户带来的好处。 现在的问题是,这给测试带来哪些变化呢?分拆后,FoodCome系统变成了微服务集群,就像一部巨大机器,由多个零件组成,互相咬合,一起工作。作为测试人员,不但要验证每个零件是合格的,还要有办法预测它们组装起来的机器也能正常工作。 这里的测试难点是,微服务的数量增加,服务间的交互量也会剧增,相比单体系统,集成测试在微服务集群架构下更加关键。 要做集成测试,我们就先搞明白微服务间是怎么交互的。在微服务架构下,交互可以有多种风格,比如RPC远程过程调用、REST风格、Message Queue消息队列等等。根据交互的方法和风格,我把它们整理出一个表格,方便你理解。 ![](https://static001.geekbang.org/resource/image/6c/02/6cd53f0b8988bc03e77a996106f13302.jpg?wh=3323x1052) 在FoodCome采用了两种交互方式,RestAPI和Message Queue。 RestAPI用来处理实时性强的服务间交互,比如前端通过API网关调用订单服务来下订单。 ![图片](https://static001.geekbang.org/resource/image/d2/57/d231ac53f77f707c09f15d872af69d57.jpg?wh=1920x711) Message Queue用来处理异步的交互,订单服务和通知服务之间通过Message Queue来交换信息. ![图片](https://static001.geekbang.org/resource/image/2d/98/2d4c9f4fa823ab4cb0b877161a6f8f98.jpg?wh=1920x855) 下面我们来看一下这两种交互方式的具体实现,然后找出测试点。 ## REST 我们需要先知道Rest接口是怎么设计的,才能找出后面都要测什么。 ### 什么是REST REST是Representational State Transfer的缩写,叫做表现层状态转换。听起来挺拗口,但我一说你就能懂,它其实是一组松散的规范,不是严格的协议,也不是强制的标准。这个规范的目的就是让API的设计更加简单易懂。 它包含以下几个基本原则: 1.REST是基于HTTP协议的; 2.通过HTTP的URL暴露Resource资源; 3.通过HTTP的操作原语,提供对Resource的操作,GET、 POST、PUT、DELETE对应着增删改查的操作。 只要开发人员懂HTTP协议,按照上面的规则用REST风格表达他的API是很容易的。同样,另外一个开发人员看到REST API,也很快就能知道这些API是干什么用的,几乎不用看难懂的文档。 这是REST的优点,REST风格下设计的AP,学习成本非常低,所以互联网上有很多服务都是通过REST方式对外提供API,比如亚马逊的AWS云服务、Google的Document服务等等。 当然,现实中的REST,从2000年概念诞生到现在发展了20年,在上面的基本规范上又增加了很多内容,让REST接口具备自解释、可发现等优势,有兴趣你可以看RichardSon提出的 [REST四级成熟度模型](https://martinfowler.com/articles/richardsonMaturityModel.html)。 ### Order Service的REST API设计 遵循REST规范,Order Service的接口设计可以按照“名词-动词”的思路来捋清。 首先寻找名词,Order,它对应REST上的一个Resource资源: ```plain http://api.foodcome.com/api/v1/orders ``` 再找到动词“下单”,它对应HTTP协议上的POST原语,对Orders资源发送POST请求就是下单: ```plain POST http://api.foodcome.com/api/v1/orders ``` 之后将“查询订单”这个动词,转成HTTP协议上的GET原语,查询条件orderID以参数形式加在URL里: ```plain GET http://api.foodcome.com/api/v1/orders?orderID=123456 ``` 同样,修改订单使用PUT原语,删除订单使用DELETE原语。 我们再用同样的方法来把其他名词“顾客”和“餐馆”,转成Resource和操作: ```plain http://api.foodcome.com/api/v1/customers http://api.foodcome.com/api/v1/restaurants ``` ### Order service的RestAPI规格定义 不成熟的开发团队,经常是一边写代码,一边设计API,这样做的结果不难推测,一千个开发人员会写出一千个Order Service API,虽然他们都声称遵循了REST规范。 所以,好的实践是,开发团队需要先设计RestAPI,并把它表达出来,然后团队就可以进行评审,达成理解一致。 那表达的载体是什么呢?这里就要提到Interface Definition Language这个概念了,顾名思义,**IDL是接口定义语言,它通过一种独立于编程语言的语法规则来描述API。**不同类型的API,它的IDL是不一样的。 我们用REST主流的IDL,也就是OpenAPI的语法规范,来描述下订单的这个接口的参数,把请求和响应写在一个YAML文件里。 ```yaml "/api/v1/orders":     post:       consumes:       - application/json       produces:       - application/json       parameters:       - in: body         name: body         description: order placed for Food         required: true         properties: foodId: type: integer shipDate: type: Date status: type: String enum: - placed - accepted - delivered       responses:         '200':           description: successful operation         '400': description: invalid order ``` 到这里,FoodCome服务间的REST接口规格说明书就生成了! 这个规格说明书定义了客户端和服务端之间的契约,顾客要下单的话,客户端应该向服务端"api/v1/orders"发送一个请求,里面包含了食品的代码、日期等等,而服务端成功则返回一个HTTP 200的响应,失败返回一个HTTP 400的响应。 ## 异步消息 说完了同步常用的REST,我们再分析一下异步消息。 什么是异步消息呢?消息就是客户端和服务端交换的数据,而异步指的是调用的方式,客户端不用等到服务端处理完消息,就可以返回。等服务端处理完,再通知客户端。 异步消息在微服务集群的架构里,能够起到削峰、解耦的作用。比如FoodCome在订餐高峰时段,先把订单收下来,放到消息队列,排好队,等待餐馆一个个处理。所以异步消息是现在业界很常用的一种服务交互方式,它的技术原理是消息队列,技术实现是消息代理,有Kafka、RabbitMQ等等。 而开发人员在设计微服务时,首先要设计异步消息接口,定义好我的微服务什么时候往消息队列里放消息,放什么样的消息。同样,也要定义好取消息的时机和方法。 ### 异步消息接口设计 好,那我们就来看一下订单服务是怎么设计它的异步消息接口的。 首先,要定义消息体,订单服务会向外发出三种消息OrderCreated、OrderUpdated、OrderCancelled。消息里包含了order ID、order items、order Status这些字段。 其次,还要说明这个消息发送到哪个channel里。Channel就是消息的队列,一个消息代理里可以有多个channel,每个channel有不同的功能。 因为order的消息有严格的时序,比如,OrderCancelled和OrderCreated这两个消息的顺序反了的话,会引起程序处理的混乱。所以,我们把这三种消息都发送到一个叫order的channel里。 如下图: ![图片](https://static001.geekbang.org/resource/image/f3/d9/f3f7b68948f2f64acc82c53ed833bfd9.jpg?wh=1920x1021) ### 异步消息接口规格说明书 好,下面就到关键环节了,对于测试人员来说,我们最关心的就是**接口规格说明书**,跟REST一样,消息队列也需要找到IDL来描述接口上的信息。 RestAPI的主流IDL是OpenAPI,相对应地,MessageAPI的IDL是 AsyncAPI。上面的Order消息接口,用AsyncAPI规范来定义,会是下面这个样子: ```yaml asyncapi: 2.2.0 info:   title: 订单服务   version: 0.1.0 channels:   order:     subscribe:       message:         description: Order created.         payload:           type: object           properties:             orderID:               type: Integer             orderStatus:               type: string ``` 这段代码描述的是,订单服务在运行时会向Order channel输出OrderCreated消息,这个OrderCreated消息包含了2个字段,order的ID和order的状态。 ## 在设计阶段测试要做什么? 刚刚我们花了不少篇幅分析API设计,如果你之前一直只做测试,也许会疑惑:“这些看起来是开发领域的知识啊,是不是跑题了?”其实我想说的是,API领域是开发、测试共同关注的。测试应该主动参与到这些领域的活动,才能让测试更加有效。 我曾经看到过两个微服务团队各自开发都很快,但是微服务一上线,就发现问题了,有的是接口就对不上,有的是数据类型不一致等等千奇百怪的问题,这些问题花了大把诊断时间不说,甚至会给客户带来损失。 单体应用基本没这些问题,它们是微服务集群的典型问题,那微服务集群的测试,该怎么避免这些问题呢? 有两个比较好的实践,推荐你尝试一下。 第一,测试设计先行原则。 测试设计先行,需要的是开发设计先行。开发不做设计,测试干着急也没法设计。怎么督促开发设计先行呢?一个关键指标是,它在设计阶段是否输出了接口规格说明书。**对于开发工作来说,是需要去代码实现的开发需求。对于测试工作来说,它就是测试需求,需要根据它写测试案例。** 第二,找到合适的IDL来表达接口设计。 一份周密、高质量的测试需求,会是成功测试的开始。所以这个接口规格说明书不仅要有,还得规范,能指导我们生成测试案例。 怎么做到呢?让开发人员写一份Word文档?一千个开发人员能写出一千个规格说明。这时,IDL的价值就显现出来了,它提供一套规范和语法,像一门专用语言,能精准描述接口。而且它与编程语言无关,可以根据IDL做Java的实现,也可以是C++, JavaScript,Python等等。 OpenAPI和AsyncAPI是IDL族群里的2种。我这里列出一个常见的IDL列表,你可以看看你领域里的IDL是什么。 ![图片](https://static001.geekbang.org/resource/image/7f/4d/7ffae5be80f21c28d878156fd5e3664d.jpg?wh=1920x623) 找到了IDL,你和团队就可以一起商量怎么践行设计先行原则,使用IDL设计接口了。有了规格说明书之后,之后我们还会讲到,在测试阶段怎么运用它们设计、开发测试案例,敬请期待。 ## 小结 这一讲,我们先分析了FoodCome升级成为微服务集群架构后,发生了哪些变化。其中最主要就是服务间的交互量大幅增加。Foodcome采用了2种交互方式,一是RestAPI同步接口,用来接收用户的订单;二是Message Queue的异步接口,用来处理用户的订单。 这两种API在设计中用到了不同的IDL:OpenAPI和AsyncAPI,产生出来的接口规格说明书是**YAML文件**。这个YAML文件对开发很重要,可以保证他们开发出来的微服务在集成时,能咬合在一起;对于测试来说也很重要,这是后续测试的测试需求。在后面的测试阶段,我们需要去验证,服务是不是遵循了接口。 当然微服务架构还有一些其他变化,比如服务的治理模式,分布式事务,可靠性的实现等等。我们本专栏关注和自动化相关的测试需求,其他变化可能需要开一个新专栏才能详细讨论。 ![](https://static001.geekbang.org/resource/image/08/61/0853ba4b25a66d1e21ebd2556a62bd61.jpg?wh=1990x1520) ## 思考题 在你的项目组里,能不能推行设计先行,你预想会遇到什么困难,要怎么应对呢? 欢迎你在留言区和我交流互动,也推荐你把这一讲分享给更多同事、朋友。