Contract Testing Between Services You Don't Own
When service A calls service B, an end-to-end test that spins up both and exercises the real call path will catch a breaking change to B's response shape, but it's slow, requires standing up B's full dependency graph, and tends to flake for reasons unrelated to the thing being tested. Contract testing instead has the consumer (A) define, in code, the exact requests it makes and the responses it expects — a contract. The provider (B) runs that contract against its own implementation in CI, independently of A's test suite, and fails its own build if a change breaks the contract. This catches the same breaking-change class of bug without either service needing the other running, and it fails in the right place: the team making the breaking change gets the failure, not the downstream consumer discovering it in staging or production. The tradeoff is that contract tests only catch what's explicitly encoded in the contract — a change B makes that's outside what A actually asserts on won't be caught, so contracts need updating as consumers start depending on more of a response, which requires some discipline that a full end-to-end test doesn't. Most teams that add contract testing don't remove end-to-end tests entirely; they narrow end-to-end coverage to a handful of critical user journeys and let contract tests cover per-service correctness.
Team size and deploy friction are better predictors of when to split a service than technical elegance. Splitting too early adds distributed-systems cost before the org is big enough to need it.
An N+1 query pattern looks completely reasonable in a diff — one clean loop over a list, one query inside it. The problem only shows up under realistic data volume, which most review environments don't have.
GraphQL solves a real problem — overfetching and underfetching across many client shapes. Most internal, single-client APIs don't have that problem, and adopt the complexity without the benefit.