Spring Cloud Contract 1.0.0.M1 Released

On behalf of the Spring Cloud team it is my pleasure to announce the 1.0.0.M1 release of the new Spring Cloud project called Spring Cloud Contract. You can grab it from the Spring’s milestone repository or even better - go to start.spring.io and pick it from there.

Spring Cloud Contract

The microservice approach has plenty of benefits but also introduces complexity. This is an inevitable result of working with distributed systems: with increasing complexity inevitably more questions are posed. In this article we show how to test microservices and create a better API by using the Consumer Driven Contracts approach. In order to make testing microservices easier we are more than happy to introduce a new project in the family of Spring Cloud projects - Spring Cloud Contract. This project provides support for Consumer Driven Contracts and service schemas in Spring applications, covering a range of options for writing tests, publishing them as assets, asserting that a contract is kept by producers and consumers, for HTTP and message-based interactions.

Service providers and consumers

Before we jump into the details let’s go though some theory. One of the biggest challenges related to the distributed systems is the agreement on the structure of messages that pass between nodes (by "message" we mean any well-formed, non-streaming data, so that applies to traditional HTTP APIs and also event-based microservices). Here are a few questions that arise when we think about message structure:

How can the consumer know that the producer has changed its API?

How can the producer side know if it’s going to break the consumers?

If the consumer is using stubs for testing, who should create those stubs?

How can you ensure the quality of stubs?

One of the ways to solve these problems is to introduce the notion of a contract. A contract is an agreement between a provider and a consumer in terms of what their communication should look like. The questions remains on who should drive the change of the API, where the contract should be stored and what the contract should contain.

Meanwhile in a company…​

Let’s imagine a following scenario:

The producer side team finishes its sprint and changes their application by introducing a new version of the API. Since there is not much time due to tight schedules there were no consultations with the consumers of that API. The consumers had all the producer side integrations mocked in their tests and, since nobody has informed them about any changes, they didn’t update those mocks. That’s why all the unit and integration tests were still green but end to end tests failed miserably. When the consumers noticed that the producer side API has changed they had to invest a lot of time to adjust their production and test code in order to send and receive the new, required data. The consumer team decided that it’s close to impossible (due to tight schedule and complexity of the changes) so they started filing issues to the producer side team to adjust their API. The producer side team replies that "there is no time" and that they can "talk to their product owner so he puts that requirement into their backlog".

If you took a look at the retrospectives of both teams you could see the following.

For the consumer side:

a breaking change was introduced and nobody informed us

the producer side team hasn’t consulted on their API changes - the new API is unusable

our integration tests didn’t catch the change of the producer side API

we got completely ignored by the producer side team in terms of adjusting their API

For the producer side:

everybody is angry with us but we have to deliver business value

we can not update every single consumer team’s tests when we change our API

Rings a bell? Don’t worry, there are ways to change this approach to make everybody less annoyed.

What is Consumer Driven Contracts?

There a few problems presented in the aforementioned scenario:

the API change was not made in consultation with the consumers

the stubbing process is owned by the consumers thus no changes of the producer side are reflected

Let’s focus on the first problem. Do you remember the Test Driven Development (TDD) approach? You start with an expectation in a form of a test, then you write an implementation to make the test pass and finally you refactor to make the code look nicer. "Red, green, refactor" - failing test, passing test, refactored code. TDD is about making mistakes. Mistakes related to the assumption how your code API should look like. It’s an iterative process that allows you to improve the quality of your code. The developer is the driver of the change of the code’s API. He is its user, he knows what he wants to achieve so he alters the API until he is happy with the results. Now, let’s imagine that we move this approach to the level of API design…​

Since the consumers are those who use the API they should be the drivers of the API change. The main difference between that and TDD is that here you have 2 teams taking part in the process - the consumer and the prodcuer. This is where you can profit from the Consumer Driven Contract (CDC) approach. A couple of its characteristics are:

the producer API is designed by the consumers together with the producer team (communication is crucial!)

the contracts are written down and have to suit both parties

the work can be decoupled - once the contracts have been noted down both teams can work independently

the producer side owns the contracts (this is a strictly defined responsibility of concrete people)

The Spring Cloud team wanted to have a tool that would allow us to:

define contracts in a readable but also a flexible manner

make the contracts show some use-cases and not only present structure of the messages

generate tests to automatically verify the contracts against the producer side

automatically produce stubs from the contracts so that the consumers can reuse it

make this approach possible for HTTP and messaging based communication

Spring Cloud Contract Verifier solves this problem by providing automated solutions to ensure the quality and reliability of the created contracts and their stubs. It consists of the following main features:

Let’s look at the following step-by-step example how to use the tool in case of the HTTP communication.

CDC with Spring Cloud Contract Verifier

Let’s take an example of Fraud Detection and Loan Issuance process. The business scenario is such that we want to issue loans to people but don’t want them to steal the money from us. The current implementation of our system grants loans to everybody.

Let’s assume that the Loan Issuance is a client to the
Fraud Detection server. In the current sprint we are required to develop a new feature - if a client wants to borrow too much money then
we mark him as fraud.

Technical remark - Fraud Detection will have artifact id http-server, Loan Issuance http-client and both have group id com.example.

Social remark - both consumer and producer development teams need to communicate directly and discuss changes while
going through the process. CDC is all about communication.

We’ve just written a test of our new feature. If a loan application for a big amount is received we should reject that loan application with some description.

write the missing implementation

At some point in time you need to send a request to the Fraud Detection service. Let’s assume that we’d like to send the request containing the id of the client and the amount he wants to borrow from us. We’d like to send it to the /fraudcheck url via the PUT method.

As consumers we need to define what exactly we want to achieve. We need to formulate our expectations. That’s why we write the following contract.

package contracts
org.springframework.cloud.contract.spec.Contract.make {
request { // (1)
method 'PUT' // (2)
url '/fraudcheck' // (3)
body([ // (4)
clientId: value(consumer(regex('[0-9]{10}'))),
loanAmount: 99999
])
headers { // (5)
header('Content-Type', 'application/vnd.fraud.v1+json')
}
}
response { // (6)
status 200 // (7)
body([ // (8)
fraudCheckStatus: "FRAUD",
rejectionReason: "Amount too high"
])
headers { // (9)
header('Content-Type': value(
producer(regex('application/vnd.fraud.v1.json.*')),
consumer('application/vnd.fraud.v1+json'))
)
}
}
}
/*
Since we don't want to force on the user to hardcode values of fields that are dynamic
(timestamps, database ids etc.), one can provide parametrize those entries by using the
`value(consumer(...), producer(...))` method. That way what's present in the `consumer`
section will end up in the produced stub. What's there in the `producer` will end up in the
autogenerated test. If you provide only the regular expression side without the concrete
value then Spring Cloud Contract will generate one for you.
From the Consumer perspective, when shooting a request in the integration test:
(1) - If the consumer sends a request
(2) - With the "PUT" method
(3) - to the URL "/fraudcheck"
(4) - with the JSON body that
* has a field `clientId` that matches a regular expression `[0-9]{10}`
* has a field `loanAmount` that is equal to `99999`
(5) - with header `Content-Type` equal to `application/vnd.fraud.v1+json`
(6) - then the response will be sent with
(7) - status equal `200`
(8) - and JSON body equal to
{ "fraudCheckStatus": "FRAUD", "rejectionReason": "Amount too high" }
(9) - with header `Content-Type` equal to `application/vnd.fraud.v1+json`
From the Producer perspective, in the autogenerated producer-side test:
(1) - A request will be sent to the producer
(2) - With the "PUT" method
(3) - to the URL "/fraudcheck"
(4) - with the JSON body that
* has a field `clientId` that will have a generated value that matches a regular expression `[0-9]{10}`
* has a field `loanAmount` that is equal to `99999`
(5) - with header `Content-Type` equal to `application/vnd.fraud.v1+json`
(6) - then the test will assert if the response has been sent with
(7) - status equal `200`
(8) - and JSON body equal to
{ "fraudCheckStatus": "FRAUD", "rejectionReason": "Amount too high" }
(9) - with header `Content-Type` matching `application/vnd.fraud.v1+json.*`
*/

The Contract is written using a statically typed Groovy DSL. You might be wondering what are those value(consumer(…​), producer(…​)) parts. By using this notation Spring Cloud Contract allows you to define parts of a JSON / URL / etc. which are dynamic. In case of an identifier or a timestamp you don’t want to hardcode a value. You want to allow some different ranges of values. That’s why for the consumer side you can set regular expressions matching those values. You can provide the body either by means of a map notation or String with interpolations. Consult the docs for more information. We highly recommend using the map notation!

Provide the group id and artifact id for the Stub Runner to download stubs of your collaborators. Also provide the offline work switch since you’re playing with the collaborators offline (optional step).

Which means that Stub Runner has found your stubs and started a server for app with group id com.example, artifact id http-server with version 0.0.1-SNAPSHOT of the stubs and with stubs classifier on port 8080.

file a PR

What we did until now is an iterative process. We can play around with the contract, install it locally and work on the consumer side until we’re happy with the contract.

Once we’re satisfied with the results and the test passes publish a PR to the producer side. At this point the consumer side work is done.

Producer side (Fraud Detection server)

As a developer of the Fraud Detection server (a producer of messages consumed by the Loan Issuance service):

That’s because all the generated tests will extend that class. Over there you can set up your Spring Context or whatever is necessary. In our case we’re using Rest Assured MVC to start the producer side FraudDetectionController.

As you can see all the producer() parts of the Contract that were present in the value(consumer(…​), producer(…​)) blocks got injected into the test.

What’s important here to note is that on the producer side we also are doing TDD. We have expectations in form of a test. This test is shooting a request to our own application to an URL, headers and body defined in the contract. It also is expecting very precisely defined values in the response. In other words you have is your red part of red, green and refactor. Time to convert the red into the green.

write the missing implementation

Now since we now what is the expected input and expected output let’s write the missing implementation.

If we execute ./mvnw clean install again the tests will pass. Since the Spring Cloud Contract Verifier plugin adds the tests to the generated-test-sources you can actually run those tests from your IDE.

deploy your app

Once you’ve finished your work it’s time to deploy your change. First merge the branch

Then we assume that your CI would run sth like ./mvnw clean deploy which would publish both the application and the stub artifcats.

Consumer side (Loan Issuance) final step

As a developer of the Loan Issuance service (a consumer of the Fraud Detection service):

merge branch to master

git checkout master
git merge --no-ff contract-change-pr

work online

Now you can disable the offline work for Spring Cloud Contract Stub Runner ad provide where the repository with your stubs is placed. At this moment the stubs of the producer side will be automatically downloaded from Nexus / Artifactory.