Microservices Contract Testing
Implementing consumer-driven contract testing to eliminate integration failures in a complex microservices architecture with 20+ services.
The Challenge
The microservices ecosystem had grown to 20+ services, each owned by different teams deploying independently. API compatibility issues were causing 3-4 production incidents per month, with each incident requiring emergency rollbacks and costing an average of $50K in downtime and engineering time.
- •20+ microservices with complex interdependencies
- •3-4 production incidents monthly due to API breaking changes
- •Integration tests taking 2+ hours and frequently flaky
- •No visibility into API compatibility until deployment to staging
- •Teams deploying blindly without knowing impact on consumers
The Solution
- Implemented Pact.js for consumer-driven contract testing across all services
- Deployed PactBroker as central contract registry with version management
- Created 'can-i-deploy' checks integrated into GitLab CI pipelines
- Built automated contract generation from OpenAPI specs
- Established contract versioning strategy aligned with semantic versioning
We adopted a consumer-driven approach where frontend teams define their API expectations as contracts. These contracts are published to PactBroker, and backend services verify against them before deployment. The 'can-i-deploy' CLI tool prevents any service from deploying if it would break existing contracts.
Consumer Contract Definition
// Consumer Pact test for User Service
import { PactV3, MatchersV3 } from '@pact-foundation/pact';
const provider = new PactV3({
consumer: 'WebApp',
provider: 'UserService',
});
describe('User API Contract', () => {
it('returns user profile', async () => {
await provider
.given('user exists', { userId: '123' })
.uponReceiving('a request for user profile')
.withRequest({
method: 'GET',
path: '/api/users/123',
headers: { Authorization: like('Bearer token') }
})
.willRespondWith({
status: 200,
body: {
id: string('123'),
email: email(),
createdAt: iso8601DateTime(),
}
});
await provider.executeTest(async (mockServer) => {
const response = await userClient.getProfile('123');
expect(response.id).toBe('123');
});
});
});Key Results
Quantifiable outcomes achieved through this implementation