At SoundCloud, we’ve found that teams move faster when we’ve moved away from a monolith architecture to one based around microservices. Unfortunately, this new type of architecture has been prone to cascading failures when breaking changes go unnoticed in one of our services’ API’s. These failures have had a devastating impact on our system’s uptime, but we’ve found that we can mitigate some of this risk by introducing consumer driven contract tests.
Consumer driven contract tests allow each consumer service and client to define their expectations and interactions with each provider service upstream, and for provider services to verify all of these contracts as part of their build pipeline. Breakage avoided.
In this talk we’ll go through SoundCloud’s process of breaking the monolith into microservices, then see how PACT-based contract tests were introduced, and discuss some of the challenges of adopting such tests in an already-established microservices culture.
6. ● One service, one client
● API changes are easy
● One client team to sync with for migrations
● API deprecation is easy
Testing, monolith style
The good ol’ days
7. ● Different requirements per client
=> Code complexity increases
● Harder to deploy without breaking at least one client
Testing, monolith style
Mo clients, mo problems
8. ● More manual QA
expensive, slow, prone to human error,
doesn’t scale
● More end-to-end tests
Maintenance nightmare, flaky, slow,
creates bottlenecks
Testing, monolith style
Mo clients, mo problems
24. Introducing contract tests
How this works?
➢ Requesting “/cake”.
➢ Expecting a JSON response
with a key “current_state”.
➢ Asserting deserialization
of the response succeeds.
import JsonLibFoo
GET “/cake”:
return toJson(current_state)
// { “current_state” : “lie” }
Consumer
Provider
26. What’s Pact?
Pactifying our services
● A family of frameworks designed for consumer driven
contract testing.
● Supports JSON over HTTP.
● Impl. for Java, Scala, Go, Ruby, .Net, Swift, JS etc.
● Tests run locally, no external dependencies.
54. Pactifying our services
Provider side verification
● Collect pact files from consumers and verify them all.
● Keep your provider environment isolated.
○ Dockerized databases.
○ Test doubles for upstream services.
59. Pactifying our services
Provider states
INSERT
likes
Set state:
“User 1000 has liked 2 tracks”
HTTP Request: GET /likes/1000
Likes
App
Likes
DB
60. Pactifying our services
Provider states
HTTP Request: GET /likes/1000
HTTP Response: 200 OK
INSERT
likes
Set state:
“User 1000 has liked 2 tracks”
Likes
App
Likes
DB
61. Pactifying our services
Provider states
Likes
App
Likes
DB
HTTP Response: 200 OK
INSERT
likes
Set state:
“User 1000 has liked 2 tracks”
HTTP Request: GET /likes/2000
HTTP Response: 404 Not Found
Set state:
“User 2000 has liked no tracks”
HTTP Request: GET /likes/1000
DELETE
likes
62. Pactifying our services
Pact broker
● Share pact files between projects.
● API for pact frameworks to fetch all pacts by provider.
● Support for versioning and tagging.
● Useful UI and visualization of the network.
70. ● Communication is key.
● New moving parts.
● Learning curve per pact framework.
Caveats
71. ● Communication is key.
● New moving parts.
● Learning curve per pact framework.
● Frameworks are WIP.
Caveats
72. ● Communication is key.
● New moving parts.
● Learning curve per pact framework.
● Frameworks are WIP.
● Provider side setup is time consuming.
Caveats
73. ● Communication is key.
● New moving parts.
● Learning curve per pact framework.
● Frameworks are WIP.
● Provider side setup is time consuming.
● Automating consumer-triggered provider verification.
Caveats
74. ● Communication is key.
● New moving parts.
● Learning curve per pact framework.
● Frameworks are WIP.
● Provider side setup is time consuming.
● Automating consumer-triggered provider verification.
● Adoption is essential.
Caveats