So you have adopted the microservice way of software development. You have your pipelines ready, monitoring is watching, load balancers are making sure your product is available 24/7. You have unit tests, integration tests, maybe even some acceptance tests. Everything is green! But suddenly a recent release makes FooService no longer aligned with BarService, which makes some part of your product broken. What happened? Someone removed a field in a response that he or she thought was not being used any more.

Can we make sure this will not happen again? Yes, we can. We can forbid any breaking changes to the API. Problem solved — right?

Imagine that FooService retrieves a field that is very expensive to calculate. Let’s say it requires running dedicated SQL query that uses a lot of resources. We would like to remove it from response.

We want to be able to modify our API.

Consumer-Driven Contracts

There are many solutions to this problem. I’ll concentrate on a specific one called Consumer-Driven Contracts. As far as I’m aware this pattern was introduced by Ian Robinson and you can read his article here. CDC allows to:

Express a business requirement in a form of a specification that is being consumed. Understand how a change done to an API will influence other applications that are using it.

In this pattern there is a provider of an API and a consumer. Consumer specifies a contract that describes how it will use API. Provider then can check if this contract is being fulfilled by his API. If the check will fail provider will know which contract is broken and which with consumer.

I’m currently working with a customer whose development teams are building microservices. They had similar problems of broken products caused by breaking change in the API. Some time ago they have adopted Consumer-Driven Contracts to specify every API interaction. Now problems can be detected during development phase. Let me show you a tool they use and hopefully you will investigate on your own how to use it with your own build pipeline.

Our task

Let’s see how this pattern will help us avoid the problem of removing a field from a response in an API. But before we start we will need a sample problem to tackle. Let’s assume we are writing an application to manage a company library. We know we want to present a list of books and that they will have: a title, author, release year and an id. We are going to use a client-server architecture. Our client will show books and we can say it will consume books that will be provided by some microservice. Let’s assume that both consumer and provider are being developed by different teams (CDC approach, however, is useful even if one team is working on both the consumer and the provider side of the service). Oh, and I’ll use Java and Spring Boot.

From the consumer perspective we want to express a business need of showing books. We will create a consumer-contract that will specify what we require from API provider. We also know that it should be consumable. A provider should be able to verify if the latest API satisfies contracts of all consumers. If at least one of the consumers’ contracts is broken, we have to be able to tell which part of API breaks which consumers. So how can we accomplish that?

I’m going to use PACT library. It allows you to create tests on the consumer side that can be verified with the provider. I’ll be using JVM flavor of this library but you can get it also for Ruby and .Net. Let’s skip the project configuration, as you can read about it here. You will need a dependency on PACT library in both consumer and provider projects. I’ll be using Spring Boot to create consumer and provider applications.

Creating consumer contract

We’ll start by writing a special test on a consumer side. Why it is special? We have to run it to see, but first things first. We will start with creating a JUnit test that will use SpringRunner to execute.

In the consumer project we have BooksReader class. This is a service that will execute HTTP request to read all books from a provider. Let’s write a test for scenario of reading all books:

This test is annotated by @PactVerification("books_provider") which defines this interaction as a contract with books_provider . In this test we are calling bookReader.all() and expect it to return some books. We then can verify the books’ attributes to have specific values. This will require to setup the data later, when we’re writing verification part in the consumer project. If you do not care for the data, but just want JSON to be correctly deserialized into your object, then you have to remove the specific assertions and use more generic ones (for example just assert amount of books returned). Now, how can we setup this HTTP interaction? PACT JVM has a mock HTTP provider which we’ll use to create our mock provider:

You can see that this provider has a name books_provider . PACT will allow us to define interactions with several providers. In our example we have only one.

We have defined the provider, also we have declared what consumer is expecting to get while reading all books. What is left is to setup the mock provider to return some data:

Method annotated with @Pact(provider = “books_provider”, consumer = “books_consumer”) defines a contract between books_provider and books_consumer for interaction of “Request to get all books” when we know that “there are books to read”.

The given(“there are books to read”) allows us to ask framework to put application into a specific state, and we will use this later when configuring provider.

The uponReceiving(“Request to get all books”) names the interaction. Then path and method tells HTTP mock to respond on specific HTTP method on a specific URL when consumer executes request. This ends the “when” phase of a test (where you describe what is being executed).

The part willRespondWith() defines the response from the provider. The status specifies HTTP response code, and body will allow us to tell what our consumer expects to get:

What we have defined here, is a response that will contain array, named “books”, with 2 elements (books) inside. Each book will have 3 string attributes: id, title, author and one numeric attribute year. Values of those attributes match exactly the values you have seen in our initial test. With PACT you can specify exact values that must be returned by the mock, or it can generate sample values. You can also provide a regular expression which will be used to generate a matching value. PACT documentation provides examples for all typical usages. Just remember to adjust assertions accordingly (for example if you do not care about the data you can just verify type of an attribute).

So we have created a test. But there are chances you have written similar tests before, so what is so special about this one? When we execute it a JSON file will be generated with a name books_consumer-books_provider.json :

The file name represents which consumer and provider interactions are described here. All your tests for interactions between same consumer and producer will be appended in one file. This is also why naming an interaction is important — you can’t have duplicate names.

This JSON represents our unit test. It names the provider and the consumer (1 and 2). It declares a list of interactions (3). Interaction (3.1) has a description, a request and a response just like the unit test we just wrote. There is also information about a state (4) and some metadata used by PACT (5).

Verify contract by provider

Now that we have our contract in the JSON file, you are probably assuming that this should be consumed by a provider. And you are right. We will start with a test in the provider project:

This test is using PactRunner to execute. We can name the provider that will be defined in this test with @Provider(“books_provider” . We are also defining a folder in which JSON contracts are stored ( @PactFolder ). This will make PACT runner to read all contracts and execute those that are specified for the provider specified in this test. Next we need to define a target used for our test. Our provider is a REST endpoint. We need to define something that will execute requests against it and verify responses. This is achieved with a Target interface and there is a target prepared for HTTP communication:

This target will be used by PACT to execute requests defined in contract. We will need to run our application on the same host, port and protocol as specified in the target.

We have specified a state in our contract so we need to provide it (the given(“there are books to read”) part in the consumer test):

We have also defined a BooksRepository implementation just for this contract verification:

All that is left is to check if this test passes:

Verifying a pact between books_consumer and books_provider Given there are books to read Request to get all books

returns a response which has status code 200 (OK) has a matching body (OK)

Now let’s remove the year field from response of getting all books and run verification test again:

has a matching body (FAILED) Failures: 0) Request to get all books returns a response which has a matching body $.body.books.0 -> Expected year=2007 but was missing

And the test failed! The body that was expected by a consumer was not delivered by a provider. I’ve omitted a part of the log so it is easier to read. The test response clearly states that a book is missing a year field. If we had this verification used as a part of the test phase of our build process we would have an immediate feedback that we are breaking a contract.

So what should happen when a test fails? I’m assuming that there are two different teams involved. One is working on the provider application and the other one on the consumer. Those teams should now have a conversation about what could be done. And after agreeing on a solution the consumer should adjust the contract and then the provider will implement it. If there is only one team then at least they know early that there is a change that will have an impact on the product.

Summary

We have created a test in the consumer project that defines a contract between it and some provider. Next we have created a test on a provider side that will get all contracts and verify if they are fulfilled. This verifies JSON structure that is required by a consumer to deliver some business functionality. Provider can have multiple contracts with one or more consumers. If at any point API change will break any contract, provider will be notified about that during build process.

In the end we can change API and know who will be impacted by our changes.