These instructions will walk you through a simple demo of Spring Cloud Contract. As you go through the demo, take the time to understand the scripts for each step and the changes they make to the code.
We will create two Spring Boot apps, simple-producer
and simple-consumer
.
We will then create an API contract to define the interaction between the apps, and show how the contract can be used programmatically by each.
- Create a new home directory for this demo and clone this setup repo into it, as follows:
mkdir contract-demo
cd contract-demo
export CONTRACT_DEMO_HOME=`echo $PWD`
git clone https://github.com/ciberkleid/contract-demo-setup.git
- Create the consumer and producer projects by running the following script. The script uses Spring Initializr to generate the projects.
source contract-demo-setup/scripts/00-create-projects.sh
- What happened? You should see two new directories created, each with a new Spring Boot project.
Notice that the producer has the
cloud-contract-verifier
dependency in itspom.xml
and that the consumer has thecloud-contract-stub-runner
dependency in itspom.xml
.
- To simulate an API contract being suggested ("driven") by the consumer and accepted by the producer, we will copy a contract to the producer project (in a more formal scenario, this might entail a pull request by a consumer that is evaluated and accepted by a producer). To accomplish this, run the following script:
source contract-demo-setup/scripts/02-create-contract.sh
- What happened? Notice that a new file has been added to the simple-producer project in a location that is compliant with Spring Cloud Contract convention.
- The simple-producer can now use the contract to auto-generate API tests and to produce a stub jar.
To do so, it must complete the minimal requirements for the
spring-cloud-contract-maven-plugin
to be able to generate the tests and stubs. To accomplish this, run the following script:
source contract-demo-setup/scripts/03-use-contract--producer.sh
- What happened? Three new files were added to
simple-producer
, and a new maven profile configuration was added to thesimple-producer
pom.xml
. These are all used by thespring-cloud-contract-maven-plugin
to auto-generate contract tests and a stub jar file. Notice that the maven build creates a stub jar, in addition to the usual jar. Maven also executes the tests that the plugin generates. The resulting stub jar contains the contract and the auto-generated tests. Maven saves the stub to the local maven repository;simple-consumer
will use the stub in the next step.
- The simple-consumer can now use the stub to test against, rather than having to manually mock out the producer. We will configure simple-consumer to retrieve the stub from the local maven repository (~/.m2/repository). Spring Cloud Contract WireMock modules automatically configure WireMock to load the stub on a specific port in the same process. We will then add a test to simple-consumer that runs against the loaded stub. To accomplish this, run the following script:
source contract-demo-setup/scripts/04-use-contract--consumer.sh
- What happened? We added a simple implementation of a controller and service on the consumer side, as well as a test class that the consumer can use to validate the implementation. The test class uses
@AutoConfigureStubRunner
to load the stub, and then tests the implementation against the stub. There is no need for the consumer to manually implement stub out the producer, and the consumer has the confidence that the stub that is being used is reliable , since it was auto-generated by the producer based on the accepted contract.
- Let's assume that the consumer wants to update the contract. They decide that instead of using the base path "http://<PRODUCER_URL>/" to get fortunes, they want to have a more specific API: "http://<PRODUCER_URL>/fortune", so that in the future, they can add "/fact", "/forecast", and other types of calls to the API. They issue a pull request, which is accepted by the producer. By accepting the pull request, the producer updates the copy of the contract in the simple-producer code base. We will simulate that by updating the copy of the contract in the producer code base. To accomplish this, run the following script:
source contract-demo-setup/scripts/05-update-contract.sh
- What happened? The contract was updated, but no other code changes were made. New tests are generated based on the updated contract, but the old implementation does not pass the tests. Therefore, the tests fail, as we would expect.
- In order to update the producer implementation to comply with the updated contract, we update our controller to respond to calls at "/fortune" rather than "/". To accomplish this, run the following script:
source contract-demo-setup/scripts/06-update-producer.sh
- What happened? The updated implementation complies with the new contract, so the auto-generated tests pass.
- The producer could go ahead and publish the new code. However, changing the API from "/" to "/fortune" is a breaking change (in contrast to a non-breaking additive change, like adding "/fact", "/forecast", etc). This means that as soon as the updated producer code is deployed to production, all consumers will need to update their code as well. Assuming this coordination is achieved, the producer and consumer will still need to coordinate rollbacks if one is required. This also means the producer cannot have both version running simultaneously in production with requests being load-balanced in a random or round-robbin fashion across both versions (aka they cannot safely do a zero-downtime "blue/green" deployment). Thus, if the producer wants to do a zero-downtime deployment, deploy independently, and roll back independently, it will need to ensure that its implementation of the updated contract is also back-compatible with the previous contract. In order to update the producer implementation to comply with both contracts, we update our controller to respond to calls at {"/", "/fortune"} rather than just "/fortune". We also add a maven profile to do an api-compatibility check using the older contract tests, which we can obtain from the prior version stub jar. To accomplish this, run the following script:
source contract-demo-setup/scripts/07-back-compatibility--producer.sh
- The consumer can now test against the updated stub, which complies with the new contract. We update the consumer implementation to call the producer at "/fortune" instead of "/". We then re-run our tests, this time using the new stub (new stub was published to the local maven repository by the producer as version "build-2" in the last step). Once again, Spring Cloud Contract WireMock modules automatically configure WireMock to load the stub on a specific port in the same process. To accomplish this, run the following script:
source contract-demo-setup/scripts/08-update-consumer.sh
- What happened? We updated the implementation of the consumer's
FortuneService
class and re-ran our tests, this time specifying thebuild-2
stubs in the maven command.
- The consumer could go ahead and promote the new code to production. However, if the producer has not yet promoted their new code to production, the consumer will have to wait and coordinate the deployment with the producer. Assuming this coordination is achieved, the producer and consumer will still need to coordinate rollbacks if one is required. In order to ensure the consumer is compatible with both contract versions, we test the updated consumer against both build-1 and build-2 stubs, making the necessary code changes to ensure that back-compatibility is maintained. To accomplish this, run the following script:
source contract-demo-setup/scripts/09-back-compatibility--consumer.sh