An Intro to Spring Cloud Contract – Spring云合同的介绍

最后修改: 2018年 3月 3日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

Spring Cloud Contract is a project that, simply put, helps us write Consumer-Driven Contracts (CDC).

Spring Cloud Contract是一个项目,简单来说就是帮助我们编写Consumer-Driven Contracts(CDC)

This ensures the contract between a Producer and a Consumer, in a distributed system – for both HTTP-based and message-based interactions.

这确保了分布式系统中生产者消费者之间的契约–基于HTTP和基于消息的互动。

In this quick article, we’ll explore writing producer and consumer side test cases for Spring Cloud Contract through an HTTP interaction.

在这篇快速文章中,我们将探讨通过HTTP交互为Spring Cloud Contract编写生产者和消费者侧的测试案例。

2. Producer – Server Side

2.制作人–服务器端

We’re going to write a producer side CDC, in the form of an EvenOddController – which just tells whether the number parameter is even or odd:

我们要写一个生产者端CDC,以EvenOddController的形式–它只是告诉number参数是偶数还是奇数。

@RestController
public class EvenOddController {

    @GetMapping("/validate/prime-number")
    public String isNumberPrime(@RequestParam("number") Integer number) {
        return Integer.parseInt(number) % 2 == 0 ? "Even" : "Odd";
    }
}

2.1. Maven Dependencies

2.1.Maven的依赖性

For our producer side, we’ll need the spring-cloud-starter-contract-verifier dependency:

对于我们的生产者一方,我们将需要spring-cloud-starter-contract-verifier依赖性。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-contract-verifier</artifactId>
    <version>2.1.1.RELEASE</version>
    <scope>test</scope>
</dependency>

And we’ll need to configure spring-cloud-contract-maven-plugin with the name of our base test class, which we’ll describe in the next section:

我们需要将spring-cloud-contract-maven-plugin配置为基础测试类的名称,我们将在下一节介绍。

<plugin>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-contract-maven-plugin</artifactId>
    <version>2.1.1.RELEASE</version>
    <extensions>true</extensions>
    <configuration>
        <baseClassForTests>
            com.baeldung.spring.cloud.springcloudcontractproducer.BaseTestClass
        </baseClassForTests>
    </configuration>
</plugin>

2.2. Producer Side Setup

2.2.生产者一方的设置

We need to add a base class in the test package that loads our Spring context:

我们需要在测试包中添加一个基类,加载我们的Spring上下文。

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
@DirtiesContext
@AutoConfigureMessageVerifier
public class BaseTestClass {

    @Autowired
    private EvenOddController evenOddController;

    @Before
    public void setup() {
        StandaloneMockMvcBuilder standaloneMockMvcBuilder 
          = MockMvcBuilders.standaloneSetup(evenOddController);
        RestAssuredMockMvc.standaloneSetup(standaloneMockMvcBuilder);
    }
}

In the /src/test/resources/contracts/ package, we’ll add the test stubs, such as this one in the file shouldReturnEvenWhenRequestParamIsEven.groovy:

/src/test/resources/contracts/包中,我们将添加测试存根,例如文件shouldReturnEvenWhenRequestParamIsEven.groovy中的这个。

import org.springframework.cloud.contract.spec.Contract
Contract.make {
    description "should return even when number input is even"
    request{
        method GET()
        url("/validate/prime-number") {
            queryParameters {
                parameter("number", "2")
            }
        }
    }
    response {
        body("Even")
        status 200
    }
}

When we run the build, the plugin automatically generates a test class named ContractVerifierTest that extends our BaseTestClass and puts it in /target/generated-test-sources/contracts/.

当我们运行构建时,该插件会自动生成一个名为ContractVerifierTest的测试类,它扩展了我们的BaseTestClass并将其放在/target/generated-test-sources/contracts/中。

The names of the test methods are derived from the prefix “validate_” concatenated with the names of our Groovy test stubs. For the above Groovy file, the generated method name will be “validate_shouldReturnEvenWhenRequestParamIsEven”.

测试方法的名称是由前缀”validate_”与我们的Groovy测试存根的名称连接而成的。对于上述Groovy文件,生成的方法名称将是“validate_shouldReturnEvenWhenRequestParamIsEven”

Let’s have a look at this auto-generated test class:

让我们看一下这个自动生成的测试类。

public class ContractVerifierTest extends BaseTestClass {

@Test
public void validate_shouldReturnEvenWhenRequestParamIsEven() throws Exception {
    // given:
    MockMvcRequestSpecification request = given();

    // when:
    ResponseOptions response = given().spec(request)
      .queryParam("number","2")
      .get("/validate/prime-number");

    // then:
    assertThat(response.statusCode()).isEqualTo(200);
    
    // and:
    String responseBody = response.getBody().asString();
    assertThat(responseBody).isEqualTo("Even");
}

The build will also add the stub jar in our local Maven repository so that it can be used by our consumer.

该构建还将在我们的本地Maven资源库中添加存根jar,以便我们的消费者可以使用它。

Stubs will be present in the output folder under stubs/mapping/.

存根将出现在输出文件夹中的stubs/mapping/

3. Consumer – Client Side

3.消费者 – 客户端

The consumer side of our CDC will consume stubs generated by the producer side through HTTP interaction to maintain the contract, so any changes on the producer side would break the contract.

我们的CDC的消费者端将通过HTTP交互消耗生产者端生成的存根,以维持合约,所以生产者端的任何变化都会破坏合约

We’ll add BasicMathController, which will make an HTTP request to get the response from the generated stubs:

我们将添加BasicMathController,它将提出一个HTTP请求,从生成的存根中获得响应。

@RestController
public class BasicMathController {

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/calculate")
    public String checkOddAndEven(@RequestParam("number") Integer number) {
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.add("Content-Type", "application/json");

        ResponseEntity<String> responseEntity = restTemplate.exchange(
          "http://localhost:8090/validate/prime-number?number=" + number,
          HttpMethod.GET,
          new HttpEntity<>(httpHeaders),
          String.class);

        return responseEntity.getBody();
    }
}

3.1. The Maven Dependencies

3.1.Maven的依赖性

For our consumer, we’ll need to add the spring-cloud-contract-wiremock and spring-cloud-contract-stub-runner dependencies:

对于我们的消费者,我们需要添加spring-cloud-contract-wiremockspring-cloud-contract-stub-runner依赖项。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-contract-wiremock</artifactId>
    <version>2.1.1.RELEASE</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-contract-stub-runner</artifactId>
    <version>2.1.1.RELEASE</version>
    <scope>test</scope>
</dependency>

3.2. Consumer Side Setup

3.2.消费者方面的设置

Now it’s time to configure our stub runner, which will inform our consumer of the available stubs in our local Maven repository:

现在是时候配置我们的存根运行器了,它将告知我们的消费者本地Maven资源库中的可用存根。

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
@AutoConfigureMockMvc
@AutoConfigureJsonTesters
@AutoConfigureStubRunner(
  stubsMode = StubRunnerProperties.StubsMode.LOCAL,
  ids = "com.baeldung.spring.cloud:spring-cloud-contract-producer:+:stubs:8090")
public class BasicMathControllerIntegrationTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void given_WhenPassEvenNumberInQueryParam_ThenReturnEven()
      throws Exception {
 
        mockMvc.perform(MockMvcRequestBuilders.get("/calculate?number=2")
          .contentType(MediaType.APPLICATION_JSON))
          .andExpect(status().isOk())
          .andExpect(content().string("Even"));
    }
}

Note that the ids property of the @AutoConfigureStubRunner annotation specifies:

注意,@AutoConfigureStubRunner注解的ids属性指定。

  • com.baeldung.spring.cloud — the groupId of our artifact
  • spring-cloud-contract-producer — the artifactId of the producer stub jar
  • 8090 — the port on which the generated stubs will run

4. When the Contract Is Broken

4.当合同被破坏时

If we make any changes on the producer side that directly impact the contract without updating the consumer side, this can result in contract failure.

如果我们在生产者一方做出任何直接影响合同的改变,而不更新消费者一方,这可能导致合同失败。

For example, suppose we’re to change the EvenOddController request URI to /validate/change/prime-number on our producer side.

例如,假设我们要把EvenOddController的请求URI改为/validate/change/prime-number,在生产者一方。

If we fail to inform our consumer of this change, the consumer will still send its request to the /validate/prime-number URI, and the consumer side test cases will throw org.springframework.web.client.HttpClientErrorException: 404 Not Found.

如果我们没有告知消费者这一变化,消费者仍然会向/validate/prim-number URI发送请求,消费者端测试案例会抛出org.springframework.web.client.HttpClientErrorException:404 Not Found

5. Summary

5.总结

We’ve seen how Spring Cloud Contract can help us maintain contracts between a service consumer and producer so that we can push out new code without any worry of breaking the contracts.

我们已经看到了Spring Cloud Contract是如何帮助我们维护服务消费者和生产者之间的合约的,这样我们就可以推送新的代码而不用担心破坏合约。

And, as always, the full implementation of this tutorial can be found over on GitHub.

而且,像往常一样,本教程的完整实现可以在GitHub上找到over