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
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
- Consumer creates contract with Pact DSL in Consumer codebase
- Consumer creates a client corresponding to the contract
- Consumer verifies the client against contract within integration test. Pact automatically generates Provider Mock from the contract
- Consumer publishes contract to Pact Broker
-
Provider verifies the client against contract within integration test. Pact automatically
- downloads the contract
- generates Consumer client from the contract
Takeways from CDC
- Consumer can create contract test even if there is no Provider implementation
-
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 -
-
because the version of the client we have may not always be in sync with the deployed API
-
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


- ./gradlew clean test
- ./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

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
- Download already generated Stub from Maven repo (Producer side publishes the stub to the repo)
- 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.
- First, we need to verify contracts against producers (1), and
- In case of success publish an artifact with stubs to Artifactory (2).
- 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).
- Then, the newest version contract is verifying against consumer (4).
- 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
- 332