Deploy and then test...
Back in the day, deploying used to be easy for developers. Mostly because 1. we didn't have that many applications to deploy, 2. it didn't happen often, and 3. it wasn't the developers who had to do it. Now we live in a world of microservices, serverless functions, continuous deployment and devops, and developers might be responsible for 10s or 100s of deployments a week.
Ensuring that everything still works after each deployment can seem like a daunting task that only an extensive and exhaustive set of end-to-end tests can achieve. However, the maintenance and execution of such a test suite can slow us down and stop us achieving the goal that we hoped to achieve by using microservices in the first place - shipping code fast.
Pact is a tool that was born out of the experiences that a group of developers had with exactly this situation. Pact allows developers to exchange slow and flakey end-to-end tests for fast and reliable "contract tests". A contract is generated during consumer tests using a simulated provider, and is "verified" during provider tests using a simulated consumer. This verification step checks that the real provider and the simulated provider behave the same way, and gives us confidence that when the two services meet in real life, they'll play nicely together.
... or test and then deploy?
To automate this process, the contract and the verification results can be exchanged using a service called the Pact Broker, which was written specifically to support this workflow. Having all the contracts and verification results in one places allows us to create a "matrix" that shows which versions of the applications have been tested against each other, and whether or not they are compatible with each other. This matrix is the key to avoiding Integration Testing Hell! When we use end-to-end tests to make sure that our applications are working correctly together, we don't find out until after we've deployed whether or not they're compatible. By using contract tests and checking the matrix, we can find out before we deploy whether or not they're compatible. Fast feedback improves flow, and helps us release our code quickly.
Let's see how this works in action. Imagine two applications that have a contract, Foo and Bar. Foo, the consumer, publishes a contract generated by version 1, and that contract is verified by version 5 of Bar. The "matrix" for these applications would look like this.
Let's say Bar makes a change that breaks the contract. Now our matrix looks like this.
What does this matrix tell us? It tells us that we can safely deploy Foo v1 and Bar v5 together, but that we can't deploy Foo v1 and Bar v6 together.
This is how we would ask the Pact Broker if we could deploy two application version together using the Pact Broker Client CLI.
$ pact-broker can-i-deploy --pacticipant Foo --version 1 --pacticipant Bar --version 5
CONSUMER | C.VERSION | PROVIDER | P.VERSION | SUCCESS?
Foo | 1 | Bar | 5 | true
All required verification results are published and successful
Using this form of the
can-i-deploy command, we would need to know which version of Bar was in our given environment at that time. This might be easily determined for a single integration, but if you had to automatedly determine this for every integrated application for every deployment, it would quickly become more work than it was worth. If, however, we let our Pact Broker know when we deploy an application to an environment, it can work out all the dependencies for us.
This is how we would tell the Pact Broker that we had deployed version 5 of Bar to our test environment.
$ pact-broker create-version-tag --pacticipant Bar --version 5 --tag test
Now, to run
can-i-deploy we only need to know the version of the application we're about to deploy.
$ pact-broker can-i-deploy --pacticipant Foo --version 1 --to test
Because the Pact Broker now knows which version of each of the integrated applications is in the test environment, it can return the same matrix that it did in our first example.
Using Pact with
can-i-deploy allows you to stop integration bugs before they even make it into a deployed environment, and allows you to release your microservices quickly and with confidence.