The Problem

Local Out-Of-Process Plugin (LOOPP) will make our setup look a lot more like microservices system

What is gRPC

  • RPC - function calls that execute on a different machine

 

  • gRPC - is a free and open-source fw developed by Google

 

  • Language independent 

  • Efficient Data Compaction 

  • Efficient serialization and deserialization (uses HTTP2) 

  • Simple to use 

 

  • gRPC - is a free and open-source fw developed by Google

Protocol Buffers

  • Google’s language-neutral, platform-neutral, extensible mechanism for serializing structured data
  •  support generated code in Java, Python, Objective-C, and C++, Kotlin, Dart, Go, Ruby, PHP, and C#
  • Defined in .proto file

But how to ensure contract will be respected between plugins/services and across different plugin/service versions?

 Contract Testing To The Rescue

Contract Changed!

How to Identify that Contract has changed?

1. E2E Test

  • Problem - tests not isolated, results are not easy to interpret, execution time is bigger, less stability, harder to maintain

How to Identify that Contract has changed?

2. Mocking

  • Problem - the implementor of the service creates stubs thus they might have nothing to do with the reality; you can go to production with passing tests and failing production

How to Identify that Contract has changed?

3. Contract Testing

  • The main idea is to give you very fast feedback, without the need to set up the whole world of microservices. If you work on stubs, then the only applications you need are those that your application directly uses

Types Of Contract Testing

  • Consumer-Driven Contract Testing
     
  • Provider-Driven Contract Testing

Why Contract Testing is needed in gRPC

  • When using gRPC (and Protobuf) we can rely on the parser and code generation to maintain backward/forward compatibility between different versions of the same API

 

  • With Protobuf v3 all fields are optional, introducing a new field on the provider, outdated clients and server can still communicate but the common understanding or business logic may be definitely broken

 

  • Contract Testing is not only about API compatibility and detecting breaking changes, it is also about independent but coordinated integration testing for both consumers and provider.

 

Why Contract Testing is needed in gRPC

  • Protocol-level breaking changes
  • Handling changes to message semantics
  • Coordinating changes (forward-compatibility) and dependency management
  • Providing transport layer safety (less of an issue when Protobuf is paired with gRPC)
  • Ensuring narrow type safety (strict encodings)
  • Loss of visibility into real-world client usage
  • Optionals and defaults: a race to incomprehensible APIs
  • Great resource 

An integration test is a test between an API provider and an API consumer that asserts that the provider returns expected responses for a set of pre-defined requests by the consumer. The set of pre-defined requests and expected responses is called a contract."

Thus, with an integration test, we want to assert that consumer and provider are speaking the same language and that they understand each other syntactically by checking that they both follow the rules of a mutual contract.

Strategies for Integration Testing

  • E2E - real servers or containers are set up so that provider and consumer are available in a production-like runtime environment
  • Mocking - consumer and provider each test against a mock that is fed with requests and responses from a common API contract
  • Consumer-Driven Contract tests - work just like mock tests with the specialty that the interface contract is driven by the consumer and not, as one would expect naturally, by the provider.

The Problem

  • E2E 
    • tests not isolated, results are not easy to interpret, execution time is bigger, less stability, harder to maintain
  • Mocking
    • the implementor of the service creates stubs thus they might have nothing to do with the reality

    • you can go to production with passing tests and failing production

Consumer-Driven Contracts

Consumer-Driven Contracts

Consumer-Driven Contracts

  • To ensure that HTTP and Messaging stubs (used when developing the client) do exactly what the actual server-side implementation does.

  • To promote the ATDD (acceptance test-driven developement) method and the microservices architectural style. 

  • TDD for services

  • To provide a way to publish changes in contracts that are immediately visible on both sides.

  • To generate boilerplate test code to be used on the server side

Purposes

Tools for CDC

  • Pact
  • Spring Cloud Contract
  • Postman 
  • ...

CDC in Pact

  1. Consumer creates contract with Pact DSL in Consumer codebase
  2. Consumer creates a client corresponding to the contract
  3. Consumer verifies the client against contract within integration test. Pact automatically generates Provider Mock from the contract
  4. Consumer publishes contract to Pact Broker
  5. Provider verifies the client against contract within integration test. Pact automatically 
    • downloads the contract
    • generates Consumer client from the contract

Takeways from CDC 

  1. Consumer can create contract test even if there is no Provider implementation
  2.  Even if the API client had been been graciously provided for us by our Provider Team, it doesn't mean that we shouldn't write contract tests -

    1. because the version of the client we have may not always be in sync with the deployed API

    2. because we will write tests on the output appropriate to our specific needs.

Pact Broker

  • Helps managing, distributing and versioning contracts
  • Great tool to visualize contracts and system relatioships (dependency graph)
  • Contracts as a form of Live Documentation

Pact Broker

Network Diagram

DEMO

Pact Test Setup

On Consumer Side

  1. ./gradlew clean test
  2. ./gradlew pactPublish

Gradle

Pact Test Setup

On Provider Side

After Test execution Pact will automatically send test results to Pact Broker

Pact Broker

Pact In CI/CD

Pact In CI/CD

Pact In CI/CD

Consumer Pipeline

Pact In CI/CD

Provider Pipeline

Pact Pros

  • support for multiple languages - JVM, Go, JS, Python, PHP, etc
  • Pact Broker

Pact Cons

  • Hard to debug - verifying generated stubs
  • Steep learning curve and not so intuitive
  • Not everything is documented, or documentation is not well structured 
  • No support for Spring AMPQ out of the box
    (https://github.com/DiUS/pact-jvm/issues/422)

CDC in Spring Cloud Contract

CDC in Spring Cloud Contract

1. CONSUMER
SIDE

2. PRODUCER
SIDE

CDC in Spring Cloud Contract

3. CONSUMER SIDE

Spring Cloud Contract

 

  • On Provider side:
    • Consumer creates a contract and make PR to Provider repo
    • SCC automatically creates the mock (Wiremock) according to the contract, the mock (Stub Runner) is then generated as a Maven artefact and can be distributed to Consumer
  • On your (Consumer) side:
    • setup Provider URL and Port and run the test
      • verify that client code works exactly as the contract specifies, verify response, check that DTO is successfully deserialized
    • SCC will automatically download the Stub Runner artifact at test runtime

Process

CDC in Spring Cloud Contract

CDC in Spring Cloud Contract

  • Contracts can be defined using Groovy, YAML, Java and Kotlin notation.
  • After building on the producer side Spring Cloud Contract generates a special JAR file with stubs suffix, that contains all defined contracts and JSON mappings.
  • Such a JAR file can be built on Jenkins and then published on Artifactory.
  • Contract consumers also use the same Artifactory server, so they can use the latest version of stubs file.
  • Because every application expects different response from person-service, we have to define three different contracts between person-service and a target consumer

Spring Cloud Contract

As a Provider and following CDC approach, you need to

  • Consumer creates a contract in Groovy/YML/Kotlin/Java file and provides it to Provider
  • Add SCC dependencies to your provider codebase
  • Add Base Test class for the test setup of Contract Test
  • Add Gradle config and build project
  • Stub Runner (Provider Mock) will be created for Consumer as a separate artefact
  • Publish Stub Runner to Maven Repo

Routine on Producer Side

Spring Cloud Contract

  • add Gradle plugin id("org.springframework.cloud.contract") - creates a Stub Runner
  • add Gradle dependency testImplementation("org.springframework.cloud:spring-cloud-starter-contract-verifier:<version>")

Routine on Producer Side

Spring Cloud Contract

  • Get a contract from Consumer in Groovy/YML/Java/Kotlin
  • Each contract defines a single request / response pair
  • Contract file is expected to be filed under contractsDslDir property. By default, it is $rootDir/src/test/resources/contracts

Routine on Producer Side

Spring Cloud Contract

  • Add Base Test Class

     

     

Routine on Producer Side

Spring Cloud Contract

Routine on Producer Side

Adjust Gradle config in order to map contracts to Base test classes

Spring Cloud Contract

Generate and Run the tests

  • ./gradlew generateContractTests - will only generate tests in build/generated-test-sources folder

  • ./gradlew clean build - generate, run tests and create a Stub Runner based on a contract

Routine on Producer Side

Spring Cloud Contract

  • After build is executed, Spring Cloud Contract Verifier Gradle plugin creates a Stub Runner based on a contract (a Mock copy of Provider)

Routine on Producer Side

Publish Contracts

  • Add maven-publish plugin to Provider side in order to publish the stub into a Maven repository. Hence, making it available for Consumer

Spring Cloud Contract

As a Consumer and following CDC approach, you need to 

  • On Provider side:
    • Create a contract and make PR to Provider repo
    • When Contract is written, SCC creates the mock (Wiremock) according to the contract, the mock (Stub Runner) is then generated as a Maven artefact and can be distributed to Consumer
  • On your (Consumer) side:
    • Add SCC dependencies to your consumer codebase
    • Add a Junit test where Provider side is invoked and setup Provider URL and Port and run the test
      • verify that client code works exactly as the contract specifies, verify response, check that DTO is successfully deserialized
    • SCC will automatically download the Stub Runner artifact at test runtime
    • You can verify Stub Runner downloaded from local or remote Maven repo

Routine on Consumer Side

Spring Cloud Contract

Add SCC dependencies to your consumer codebase

 

testImplementation("org.springframework.cloud:spring-cloud-starter-contract-stub-runner:<version>") - downloads Stub runner and configures it

Routine on Consumer Side

Spring Cloud Contract

Add a Junit test where Provider side is invoked and setup Provider URL and Port and run the test

Routine on Consumer Side

Spring Cloud Contract

Routine on Consumer Side

Download Provider Stub (Provider Mock) from

  • Local Maven Repo
    • set stubsMode = StubRunnerProperties.StubsMode.LOCAL
  • Remote Maven Repo
    • specify stubrunner.repositoryRoot as from where to download the artefact
    • stubsMode = StubRunnerProperties.StubsMode.REMOTE
  • Pick the stubs from classpath.
    stubsMode = CLASSPATH

Spring Cloud Contract

Routine on Consumer Side

Ways to generate a Stub

  1. Download already generated Stub from Maven repo (Producer side publishes the stub to the repo)
  2. Generate Stub at runtime from found contract folder
    • set generateStubs = true (contracts should be on the classpath)
    • Or If you do not want to use Stub Runner, you need to copy the contracts stored in src/test/resources/contracts and generate WireMock JSON stubs by using the following command:
      ./gradlew generateClientStubs

Spring Cloud Contract

Complimentary Info

  • ids -

    The ids of the stubs to run in "ivy" notation([groupId]:artifactId[:version][:classifier][:port]). version, classifier (default is "stubs") and port can be optional

stubsPerConsumer  -

On the producer side the consumers can have a folder that contains contracts related only to them. By setting the flag to true we no longer register all stubs but only those that correspond to the consumer application's name. In other words we'll scan the path of every stub and if it contains the name of the consumer in the path only then will it get registered

Spring Cloud Contract

Integrations

 

  • Apache Camel

 

  • Spring Integration

 

  • Spring Cloud Stream

 

  • Spring AMQP

 

Spring Cloud Contract

Integration with Spring AMQP

 

Basically, the same routine as for REST

  • Configure AMQP Stub Runner instead of REST
  • Behind the scenes, this stub runner will essentially mock the RabbitMQ broker

Spring Cloud Contract

Integration with Spring AMQP

 

Different schema for the contract

Spring Cloud Contract

Integration with Spring AMQP

 

On Producer side: provide Base Test class which sends the Message

Spring Cloud Contract

Integration with Spring AMQP

 

On Producer side: which will generate following Test 

Spring Cloud Contract

Integration with Spring AMQP

 

On Consumer side:

Spring Cloud Contract in CI/CD

Spring Cloud Contract in CI/CD

Spring Cloud Contract in CI/CD

Assuming we have performed some changes in the API exposed by person-service and we have modified contracts on the producer side, we would like to publish them on a shared server.

  1. First, we need to verify contracts against producers (1), and
  2. In case of success publish an artifact with stubs to Artifactory (2).
  3. All the pipelines defined for applications that use this contract are able to trigger the build on a new version of JAR file with stubs (3).
  4. Then, the newest version contract is verifying against consumer (4).
  5. If contract testing fails, pipeline is able to notify the responsible team about this failure

Pact

  • consumer owns the contract and autogenerates integration tests
  • producer downloads the contract from Pact Broker

SCC

  • producer owns the contract and autogenerates integration tests.
  • consumer downloads the contract from remote Maven repo.

Pact

  • Stores contracts in Pact Broker, from where you can easily see full picture

SCC

  • In case of Maven - contracts are stored only on Producer Side codebase
  • In case of Git - separate Git repo

Pact

  • Contracts formed directly in Test from Pact DSL

SCC

  • Contracts stored as a separate file

Pact

  • Contract tests are triggered when CONSUMER changes the Contract

SCC

  • Contract tests are triggered when PROVIDER changes the Contract

CI/CD

Lessons learnt

  • Contracts are not a replacement for good communication between or within teams

Questions?

Consumer Driven Contract Testing

By Ilja Pavlovs

Consumer Driven Contract Testing

  • 372