$30 off During Our Annual Pro Sale. View Details »

Build resilient systems with Spring Cloud Contract and Testcontainers

Build resilient systems with Spring Cloud Contract and Testcontainers

Verifying behaviors of the cloud-native applications and ensuring that all of the services in the system work correctly together is both crucial and challenging. Manually maintaining environments to test the correctness of the entire system is undevops-like and fragile.

Luckily, modern tools can help you to build automated, reliable test pipelines, and in this session, we explore how using Spring Cloud Contract and Testcontainers together can improve your testing and deployment processes.

Spring Cloud Contract is an implementation of Consumer-Driven Contracts, an approach that provides a way to easily describe and verify APIs, at the same time allowing building API backward compatibility verification into the deployment process.

Testcontainers lets developers programmatically build test environments consisting of real services running in lightweight and disposable containers. It turns the process of integration testing into a seamless, unit-test-like experience.

In this presentation, we’ll show how contract and integration tests complement each other and explore one of the most natural and reliable approaches to service evolution with contract testing. We’ll discuss why in Spring Cloud Contract, we’ve decided to switch to using Testcontainers as the solution for Kafka and AMQP messaging verification and demonstrate practical use-cases and code examples of how to set up both types of tests in your applications and deployment pipelines.

Olga Maciaszek-Sharma

May 19, 2023
Tweet

More Decks by Olga Maciaszek-Sharma

Other Decks in Technology

Transcript

  1. Olga Maciaszek-Sharma
    Oleg Šelajev
    May 2023
    Build resilient systems with Spring
    Cloud Contract and Testcontainers
    Copyright © 2023 VMware, Inc. or its affiliates.

    View Slide

  2. What is going to happen?

    View Slide

  3. About Olga
    Senior Software Engineer in Spring Cloud Team
    ● Spring Cloud LoadBalancer (Spring Cloud Commons)
    ● Spring Interface Clients
    ● Spring Cloud Contract
    ● Spring Cloud OpenFeign
    ● Spring Cloud Netflix
    ● Spring Native support for Spring Cloud projects
    ● RSocket HTTP Bridge
    https://github.com/OlgaMaciaszek
    https://twitter.com/olga_maciaszek

    View Slide

  4. Developer advocate at AtomicJar
    ● Testcontainers for Java
    https://github.com/shelajev
    https://twitter.com/shelajev
    About Oleg

    View Slide

  5. Our project

    View Slide

  6. A new message
    topic: servings
    body:
    { "beverages": [
    {
    "uuid": "16b439a4-6ace-4901-8fb6-728588c12da8",
    "coffee": {
    "name": "Latte",
    "coffeeContent": 60,
    "steamedMilkContent": 180,
    "milkFoamContent": 5
    }
    }
    // …
    ]
    }
    headers:
    “contentType”: “application/json”
    “testKey1”: “testValue1”

    View Slide

  7. A new message

    View Slide

  8. We go to production!
    Production

    View Slide

  9. A new message
    topic: servings
    body:
    { "beverages": [
    {
    "uuid": "16b439a4-6ace-4901-8fb6-728588c12da8",
    "coffee": {
    "name": "Latte",
    "coffeeContent": 60,
    "steamedMilkContent": 180,
    "milkFoamContent": 5
    }
    }
    // …
    ]
    }
    headers:
    “contentType”: “application/json”
    “testKey1”: “testValue1”

    View Slide

  10. A new message
    topic: serving
    body:
    { "beverages": [
    {
    "uuid": "16b439a4-6ace-4901-8fb6-728588c12da8",
    "coffee": {
    "name": "Latte",
    "coffeeContent": 60,
    "steamedMilkContent": 180,
    "milkFoamContent": 5
    }
    }
    // …
    ]
    }
    headers:
    “contentType”: “application/json”
    “testKey1”: “testValue1”

    View Slide

  11. A new message
    topic: servings
    body:
    { "beverages": [
    {
    "uuid": "16b439a4-6ace-4901-8fb6-728588c12da8",
    "coffee": {
    "name": "Latte",
    "coffeeContent": 60,
    "steamedMilkContent": 180,
    "milkFoamContent": 5
    }
    }
    // …
    ]
    }
    headers:
    “contentType”: “application/json”
    “testKey1”: “testValue1”

    View Slide

  12. A new message
    topic: servings
    body:
    { "beverages": [
    {
    "id": "16b439a4-6ace-4901-8fb6-728588c12da8",
    "coffee": {
    "name": "Latte",
    "coffeeContent": 60,
    "steamedMilkContent": 180,
    "milkFoamContent": 5
    }
    }
    // …
    ]
    }
    headers:
    “contentType”: “application/json”
    “testKey1”: “testValue1”

    View Slide

  13. A new message
    topic: servings
    body:
    { "beverages": [
    {
    "uuid": "16b439a4-6ace-4901-8fb6-728588c12da8",
    "coffee": {
    "name": "Latte",
    "coffeeContent": 60,
    "steamedMilkContent": 180,
    "milkFoamContent": 5
    }
    }
    // …
    ]
    }
    headers:
    “contentType”: “application/json”
    “testKey1”: “testValue1”

    View Slide

  14. A new message
    topic: servings
    body:
    { "beverages": [
    {
    "uuid": "16b439a4-6ace-4901-8fb6-728588c12da8",
    "coffee": {
    "name": "Latte",
    "coffeeContent": 60,
    "steamedMilkContent": 180,
    "milkFoamContent": 5
    }
    }
    // …
    ]
    }
    headers:
    “contentType”: “application/json”
    “testKey1”: “testValue1”

    View Slide

  15. Stubs alone don’t guarantee
    ANYTHING

    View Slide

  16. The Answer:
    Consumer-Driven Contracts

    View Slide

  17. Spring Cloud Contract

    View Slide

  18. No free stubs!
    Build tool
    plugin
    Contract
    definitions
    Generated
    tests
    Stubs
    Test setup
    configuration

    View Slide

  19. Producer Build Setup



    org.springframework.cloud
    spring-cloud-contract-maven-plugin
    4.0.3-SNAPSHOT
    true

    JUNIT5
    io.xxx.BaseTestClass



    org.springframework.boot
    spring-boot-maven-plugin



    View Slide

  20. ● Setup the environment
    ● Create inputs
    ● Produce the outputs (with production code)
    Producer test setup

    View Slide

  21. View Slide

  22. ● TC slide

    View Slide

  23. ● TC slide

    View Slide

  24. No free stubs!
    Build tool
    plugin
    Contract
    definitions
    Generated
    tests
    Stubs
    Test setup
    configuration

    View Slide

  25. Contract
    Contract.make {
    label("serving")
    input {
    triggeredBy("triggerServing()")
    }
    / ...
    }

    View Slide

  26. Contract
    Contract.make {
    / ...
    outputMessage {
    sentTo("servings")
    body([beverages:
    [[uuid : $(anyUuid()),
    coffee: [
    name : "Latte",
    coffeeContent : "60",
    steamedMilkContent: "180",
    milkFoamContent : "5"
    ]]]])
    / ...
    }

    View Slide

  27. Contract
    Contract.make {
    / ...
    headers {
    messagingContentType(applicationJson())
    header 'testKey1', 'testValue1'
    }
    }

    View Slide

  28. No free stubs!
    Build tool
    plugin
    Contract
    definitions
    Generated
    tests
    Stubs
    Test setup
    configuration

    View Slide

  29. Generated Contract Test
    @Test
    public void validate_shouldSendServing() throws Exception {
    // when:
    triggerServing();
    // then:
    ContractVerifierMessage response = contractVerifierMessaging.receive("servings",
    contract(this, "shouldSendServing.yml"));
    assertThat(response).isNotNull();
    // and:
    assertThat(response.getHeader("contentType")).isNotNull();
    assertThat(response.getHeader("contentType").toString()).isEqualTo("application/json");
    assertThat(response.getHeader("testKey1")).isNotNull();
    assertThat(response.getHeader("testKey1").toString()).isEqualTo("testValue1");
    // ...

    View Slide

  30. Generated Contract Test
    @Test
    public void validate_shouldSendServing() throws Exception {
    // ...
    DocumentContext parsedJson =
    JsonPath.parse(contractVerifierObjectMapper.writeValueAsString(response.getPayload()));
    assertThatJson(parsedJson).array("['beverages']").contains("['uuid']")
    .matches("[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}")
    assertThatJson(parsedJson).array("['beverages']").field("['coffee']").field("['name']")
    .isEqualTo("Latte");
    assertThatJson(parsedJson).array("['beverages']").field("['coffee']").field("['coffeeContent']
    ")
    .isEqualTo("60");
    assertThatJson(parsedJson).array("['beverages']").field("['coffee']").field("['steamedMilkCont
    ent']")
    .isEqualTo("180");
    assertThatJson(parsedJson).array("['beverages']").field("['coffee']").field("['milkFoamContent
    ']")
    .isEqualTo("5");

    View Slide

  31. No free stubs!
    Build tool
    plugin
    Contract
    definitions
    Generated
    tests
    Stubs
    Test setup
    configuration

    View Slide

  32. Contracts vs Schemas

    View Slide

  33. Consumer integration tests
    using stubs from Contract and
    TestContainers

    View Slide

  34. View Slide

  35. Local Development Environment

    View Slide

  36. Teamwork - different approaches
    Provider Contracts
    ● Contracts with the producer
    ● The producer has no knowledge
    of how consumers use their API
    ● The producer can impose contract
    changes on consumers
    Consumer Contracts
    ● Contracts with the consumers
    ● Possible issues with conflict
    resolution in case of multiple
    consumers
    ● In some implementations, the
    consumers can impose contract
    changes on the producer
    Consumer-Driven Contracts
    ● Contracts with the producer
    ○ If security issues, an
    external repo
    ● Consumers as key participants in
    the creation of the contracts
    ● Convenient handling of multiple
    consumers

    View Slide

  37. Spring Cloud Contract - Consumer-Driven Contract Flow
    Consumer
    ● Checks out Producer code repo and works
    locally on a contract
    ● Creates a pull request with the new Contract
    ● Collaborates with the Producer on the Contract
    PR
    ● Uses stubs published by the producer along the
    app for integration tests
    Producer
    ● Reviews the Contract PR
    ● Runs build on the PR branch (FAILURE)
    ● Adds required endpoints
    ● Runs build on the PR branch (PASSING)
    ● Code Deployed along with generated stubs jar

    View Slide

  38. Spring Cloud Contract - Consumer-Driven Contract Flow
    Consumer
    ● Checks out Producer code repo and works
    locally on a contract
    ● Creates a pull request with the new Contract
    ● Collaborates with the Producer on the Contract
    PR
    ● Uses stubs published by the producer along the
    app for integration tests
    Producer
    ● Reviews the Contract PR
    ● Runs build on the PR branch (FAILURE)
    ● Adds required endpoints
    ● Runs build on the PR branch (PASSING)
    ● Code Deployed along with generated stubs jar

    View Slide

  39. Spring Cloud Contract - Consumer-Driven Contract Flow
    Consumer
    ● Checks out Producer code repo and works
    locally on a contract
    ● Creates a pull request with the new Contract
    ● Collaborates with the Producer on the Contract
    PR
    ● Uses stubs published by the producer along the
    app for integration tests
    Producer
    ● Reviews the Contract PR
    ● Runs build on the PR branch (FAILURE)
    ● Adds required endpoints
    ● Runs build on the PR branch (PASSING)
    ● Code Deployed along with generated stubs jar

    View Slide

  40. Spring Cloud Contract - Consumer-Driven Contract Flow
    Consumer
    ● Checks out Producer code repo and works
    locally on a contract
    ● Creates a pull request with the new Contract
    ● Collaborates with the Producer on the Contract
    PR
    ● Uses stubs published by the producer along the
    app for integration tests
    Producer
    ● Reviews the Contract PR
    ● Runs build on the PR branch (FAILURE)
    ● Adds required endpoints
    ● Runs build on the PR branch (PASSING)
    ● Code Deployed along with generated stubs jar

    View Slide

  41. Spring Cloud Contract - Consumer-Driven Contract Flow
    Consumer
    ● Checks out Producer code repo and works
    locally on a contract
    ● Creates a pull request with the new Contract
    ● Collaborates with the Producer on the Contract
    PR
    ● Uses stubs published by the producer along the
    app for integration tests
    Producer
    ● Reviews the Contract PR
    ● Runs build on the PR branch (FAILURE)
    ● Adds required endpoints
    ● Runs build on the PR branch (PASSING)
    ● Code Deployed along with generated stubs jar

    View Slide

  42. Spring Cloud Contract - Consumer-Driven Contract Flow
    Consumer
    ● Checks out Producer code repo and works
    locally on a contract
    ● Creates a pull request with the new Contract
    ● Collaborates with the Producer on the Contract
    PR
    ● Uses stubs published by the producer along the
    app for integration tests
    Producer
    ● Reviews the Contract PR
    ● Runs build on the PR branch (FAILURE)
    ● Adds required endpoints
    ● Runs build on the PR branch (PASSING)
    ● Code Deployed along with generated stubs jar

    View Slide

  43. Spring Cloud Contract - Consumer-Driven Contract Flow
    Consumer
    ● Checks out Producer code repo and works
    locally on a contract
    ● Creates a pull request with the new Contract
    ● Collaborates with the Producer on the Contract
    PR
    ● Uses stubs published by the producer along the
    app for integration tests
    Producer
    ● Reviews the Contract PR
    ● Runs build on the PR branch (FAILURE)
    ● Adds required endpoints
    ● Runs build on the PR branch (PASSING)
    ● Code Deployed along with generated stubs jar

    View Slide

  44. Spring Cloud Contract - Consumer-Driven Contract Flow
    Consumer
    ● Checks out Producer code repo and works
    locally on a contract
    ● Creates a pull request with the new Contract
    ● Collaborates with the Producer on the Contract
    PR
    ● Uses stubs published by the producer along the
    app for integration tests
    Producer
    ● Reviews the Contract PR
    ● Runs build on the PR branch (FAILURE)
    ● Adds required endpoints
    ● Runs build on the PR branch (PASSING)
    ● Code Deployed along with generated stubs jar

    View Slide

  45. View Slide

  46. Backward Compatibility Checks in Pipelines
    New API version
    Test with PROD
    contracts
    Deploy
    Test with new
    contracts

    View Slide

  47. Producer Build Setup - Compatibility Tests with Profiles

    apicompatibility



    org.springframework.cloud
    spring-cloud-contract-maven-plugin
    4.0.3-SNAPSHOT

    ${repo.with.binaries}
    REMOTE

    ${project.groupId}
    ${project.artifactId}
    stubs
    ${latest.production.version}






    View Slide

  48. Evolving the system safely
    Deployment Contract fields Producer fields
    0
    {
    "uuid": "xxx"
    }
    {
    "uuid": "xxx"
    }
    1
    {
    "uuid": "xxx",
    "id": 5
    }
    {
    "uuid": “xxx”,
    "id": 5
    }
    2
    {
    "id": 5
    }
    {
    "uuid": “xxx”,
    "id": 5
    }
    3
    {
    "id": 5
    }
    {
    "id": 5
    }

    View Slide

  49. What’s next?
    github.com/OlgaMaciaszek/coffee
    testcontainers.com
    https://docs.spring.io/spring-cloud-contract/docs/current/reference/html/

    View Slide