Sharing Consumer-Driven Contracts with Pact Broker

If you’re already using Pact for Consumer-Driven Contracts (if not, I've explained why you should in a previous blog), you have probably hit the issue of persisting and sharing contracts. The first step is usually to start simple, having a Consumer generating contracts at build time and manually giving them to the team(s) responsible for the Providers so that they can validate the interactions during their own builds. Unfortunately this does not scale well and can be quickly prone to contracts becoming out-of-sync.

This is where the Pact Broker comes in, essentially acting as a registry for the pacts, enabling Consumers and Providers to share contracts in a more automated manner.

Starting the Pact Broker

If you want to quickly try out the Pact Broker, you can do so using the Pact Broker Docker Image. Pact Broker relies on PostgreSQL to store the pacts, so the simplest for now is to start PostgreSQL in Docker as well. You can use the Pact Broker docker-compose to start both of them at once. You should then be able to access the Pact Broker API and UI through http://192.168.99.100 (the IP may vary depending on your Docker Host IP, 192.168.99.100 is the default IP for docker-machine on OSX).

Note that if you are setting up Pact Broker and PostgreSQL in your production environment you will need to put some more thought into persisting volumes etc, but this setup is enough for getting a good view of what Pact Broker does.

Publishing a pact to the Pact Broker

Pact Broker will not change the way you generate your pacts. In our case, we use the pact-jvm-consumer library to write Unit Tests that will record the interactions and produce the relevant pact during the build.

The pact file contains the interactions between a Consumer and a Provider, for example (auth-service-user-service.json):

{
    "provider": {
        "name": "user-service"
    },
    "consumer": {
        "name": "auth-service"
    },
    "interactions": [
        {
            "providerState": "user 123 exists",
            "description": "A request for a user by id",
            "request": {
                "method": "GET",
                "path": "/v1/users/123"
            },
            "response": {
                "status": 200,
                "body": "{ \"id\": 123, \"username\": \"jane\", \"role\": \"trial-user\" }"
            }
        },
        {
            "providerState": "user 456 does not exists",
            "description": "A request for a user by id",
            "request": {
                "method": "GET",
                "path": "/v1/users/456"
            },
            "response": {
                "status": 404,
                "body": "{ \"error\": \"User 456 does not exist\" }"
            }
        }
    ],
    "metadata": {
        "pact-specification": {
            "version": "2.0.0"
        }
    }
}

We can use the RESTful API of the Pact Broker to persist it:

curl -XPUT -H "Content-Type: application/json" -d@auth-service-user-service.json http://192.168.99.100/pacts/provider/user-service/consumer/auth-service/version/1.0.0  

The Pact Broker UI will display the pacts that have been published:

Pact Broker - Pact Lists

With detailed information about the interactions:

Pact Broker - Interaction details

As well as a dependency graph (in our case very simple):

Pact Broker - Dependency graph

Retrieving Pacts for verification

The simplest way to retrieve the pacts for a given Provider is through the RESTful API. The pact that we just published should be available under:

http://192.168.99.100/pacts/provider/user-service/consumer/auth-service/latest  

We can also get the latest pacts by Provider, which will be useful in order to verify all interactions:

http://192.168.99.100/pacts/provider/user-service/latest  

If we want to verify these interactions as part of the Provider’s Unit Tests, instead of loading the pacts from files in a directory, we can use the pact-jvm-provider-junit to load them from the Pact Broker:

val pactLoader = new PactBrokerLoader("192.168.99.100", "80", "http")  
val pacts = pactLoader.load("user-service")  

Or if you prefer to use the PactRunner, you can also use annotations:

@RunWith(PactRunner)
@Provider("user-service")
@PactBroker(host = "192.168.99.100", port = "80")
class PactVerificationsTest {  
    ...

More details as well as more features (e.g. retrieving pacts by tags) are available in the documentation of pact-jvm-provider-junit.