@lsmn
2017-04-26T06:41:39.000000Z
字数 4112
阅读 2190
Spring
Java
微服务
Marcin Grzejszczak是Pivotal的一名软件工程师。目前,他在从事Spring Cloud Contract的开发,这是一个消费者驱动的、面向Java的契约框架。为了了解该框架的一些好处,特别是消费者驱动契约对微服务测试的帮助,InfoQ对Marcin进行了采访。
Marcin Grzejszczak是Pivotal的一名软件工程师。目前,他在从事Spring Cloud Contract的开发,这是一个消费者驱动的、面向Java的契约框架。为了了解该框架的一些好处,特别是消费者驱动契约对微服务测试的帮助,InfoQ对Marcin进行了采访。
要点:
InfoQ:您能简要介绍一下消费者驱动契约模式以及它解决的是微服务开发中的哪类问题吗?
Marcin Grzejszczak:消费者驱动契约是一个流程,旨在帮助客户测试他们的软件,创建更好的架构设计。在架构层面,我们可以称之为测试驱动开发。现如今,软件需要能够非常快速地响应客户需求、法律法规变化及业务需求,等等。另一方面,我们IT行业希望尽可能快地交付可靠的软件,经过测试,不含任何Bug。这就是为什么我们创建了部署管道——自动化发布和测试过程。至于微服务,由于架构风格本身的性质,问题被放大了。我们不可能在不影响测试流程的情况下将单体应用拆分成微服务。
单体结构中包含的复杂性被推至架构层面。消费者驱动契约试图在团队之间定义一些明确的沟通界限。CDC的总体流程是,消费者定义他们期望API/消息是什么样子。这种期望就称为契约。从这些契约可以生成存根,稍后,消费者团队可以在构建过程中重复使用它们。在生产者一端也需要验证契约。那就导致,不管是测试生产者一端,还是测试消费者一端,都需要引入一种快速失败方法。对于快速失败,我们指的是软件构建失败以及通过产品调试发现问题(例如,我们在REST/AMQP消息中犯了一个错误)。
消费者驱动契约将API设计转移给了使用它的人。典型地,服务器端的团队只需要宣布API会是个什么样子。通常,消费者的数量导致那是唯一可能的方式。但情况并不总是如此。我见过,有的公司没有公开暴露他们的API,团队也不希望在API应该是个什么样子这个问题上进行协作。CDC就是设法将那种方法变成一种由消费者驱动API变化的方法。细想一下,这很有道理。不是服务器端消费API,是消费者消费API,那就是为什么创建的API应该尽可能地适应消费者。
InfoQ:你们创建Spring Cloud Contract的动机是什么?你们为什么用它代替其他可选的消费者驱动的契约框架?
Grzejszczak:和来自Devskiller公司的Jakub Kubrynski一起,我们对CDC框架进行了分析。我们得出的结论是,它们的学习曲线很高,而且非常啰嗦。这就是为什么在2014年12月诞生了Spring Cloud Contract的前身Accurest。我们已经决定引入静态类型Groovy DSL来定义契约。这里要说到Spring Cloud Contract和其他CDC框架的主要区别了。Spring Cloud Contract不仅可以从契约生成存根,还可以生成测试。那意味着开发人员只需要定义契约,而其他的东西都会为生产者自动生成。这曾是我们希望做出的一个非常重要的决策,因为CDC的本质就是,假定生成的存根是可信的。如果有人在契约中定义了一个可以通过
GET
方法访问的端点/foo
,那么在生产者一端,我们就可以通过向/foo
端点发送GET
请求来生成一个测试。如果没有这样的端点,那么测试就会中断,存根也不会生成。显而易见,Spring Cloud Contract与Spring环境集成得很好。我们已经支持使用Spring Integration、Spring Cloud Stream、Spring AMQP和Apache Camel进行消息传递。但是,是一个叫做Stub Runner的组件让Spring Cloud Contract成为一个有吸引力的选项。我已经提到过,存根是从契约生成的。在默认情况下,我们希望用户以JAR文件的形式将生产者存根和契约发布到Maven库。假如存根的组ID为“org.springframework”,工件ID为“spring-boot-application”。为了运行存根,消费者需要像下面这样给测试加上注解:
@AutoConfigureStubRunner (ids={'org.springframework:spring-boot-application:+'}
实际情况是,框架会自动下载包含存根的“org.springframework:spring-boot-application” JAR文件的最新版本,然后启动一个内存内HTTP服务器,并在一个随机端口上提供存根。也就是说,只需一条注解,你就可以为构建生成整个环境的存根!此外,真正有趣的是,Spring Cloud Contract可以完全消除服务发现工具。那意味着,存根注册在一个服务注册中心的内存版本中。那样,你可以像使用服务发现那样,向一个真正的HTTP服务器发送一个真正的HTTP请求。
如果你想要在测试环境中对打包好的应用程序执行一些冒烟测试,Spring Cloud Contract的Stub Runner也非常方便。下载好的存根可以注册到真正的服务发现工具中(例如Eureka),在契约中定义的真正的消息可以发送给真正的队列(例如RabbitMQ)。那样,你的应用程序甚至都不知道它在同存根交互。
InfoQ:消费者驱动契约模式,如果有的话,对端到端测试有什么影响吗?
Grzejszczak:这是一个很好的问题。就像我提到的那样,使用微服务增加了测试和部署应用程序的复杂度。尤其是,部署和端到端测试的组合很有趣。假如我们的系统由50个微服务组成,让我们提几个问题:
- 对于每个构建的微服务,每个团队都应该有它的部署环境吗?
- 谁来承担那50个环境的费用?
- 如果我们决定,不是每个微服务一个环境,那我们该如何处理部署队列?如果端到端测试需要运行很长时间,我们就不得不在轮到我们之前等待很长时间……
- 那些环境应该包含其他49个微服务的生产版本还是开发版本?
- 或者,生产版本和开发版本都应该测试?谁来配置那些环境,谁又负责维护那些环境?
- 有时候,企业无法在测试环境中使用生产数据——他们会对数据进行模糊处理,并满足完整性需求。
此外,还有一点需要考虑,就是端到端测试相当脆弱。有许多和代码Bug无关的原因可以导致它们失败。我不是说端到端测试没有带来任何价值——恰恰相反。当复杂度达到一定程度时,必须计算成本和收益。消费者驱动契约可以解决问题。如果消息违反了契约,那么执行契约测试可以提早终止构建。换句话说,如果你的消息中有错误,那么最好在构建的第一分钟就失败,而不是在2个小时的端到端测试的最后一分钟。
这可能会引起争议,但在我个人而言,我认为,只要设置恰当,端到端测试就是多余的,完全可以省略。这需要满足三个条件:(1)进行契约测试;(2)监控关键性能指标并报警;(3)可以进行回滚测试,并构建到部署管道中。作为这种部署管道的例子,你可以检出Spring Cloud Pipelines项目。
InfoQ:给我介绍下新的Spring Rest Docs集成吧,它是否可以改变传统的消费者驱动契约工作流?
Grzejszczak:那不新了,不过,它确实获得了更多的关注。有些用户不喜欢编写Groovy DSL,不希望生成测试。他们希望完全属于自己的测试流程。还有一些其他的用户已经在使用Spring Rest Docs测试他们的代码。Dave Syer就是其中的一位用户,向Spring Rest Docs添加Spring Cloud Contract集成就是他的主意。多亏了这个,你才能编写测试来测试你的Web应用程序以及自动生成存根。使用Spring Cloud Contract的最新版本,你还可以从Spring Rest Docs生成Groovy DSL契约。
至于工作流,我可以想象得到,消费者和开发者结对满足消费者需要的测试和存根。那样,流程得以保留。另一方面,有些事情告诉我,Spring Rest Docs方法将更多地应用在“生产者契约”方法中。生产者定义契约是什么样子也是如此。在Spring,我们喜欢用自己的产品来进行开发,Spring Initilizr(start.spring.io背后的代码)已经使用了Spring Cloud Contract,而且,Spring Initilizr存根发布到了Spring的Maven库。
InfoQ:最后一个问题,在你们的路线图上,有什么有趣的东西可以介绍一下吗?
Grzejszczak:我们刚刚发布了Spring Cloud Contract的最新版本“1.1.0.RELEASE”,带来了一些有趣的特性,其中包括完全模块化(现在,你可以自定义Spring Cloud Contract的部件,比如,生成PHP测试),所以,在接下来的几个周里,我们最可能做的是修复Bug,让API更稳定。就长远规划而言,我们在考虑简化非Java用户的工作。不过显然,对我们而言,最重要的是用户的反馈,我们会调整规划满足他们的需求。
如果你想进一步了解这个项目,可以查看项目主页。如果你有什么问题,可以通过Gitter或者Twitter(@mgrzejszczak)联系我。