Why Strict and Loose Contracts Matter in Microservice Architecture
This article explores the role of contracts in software architecture, explaining why architectural-level contract decisions are crucial, comparing strict and loose contracts such as gRPC, REST, and key‑value formats, and introducing consumer‑driven contracts, their trade‑offs, and practical guidelines for reducing coupling and brittleness in microservices.
Architecture‑Level Contract Decisions
In software systems the way services or components connect—referred to as a contract—is a constant factor that influences many architectural decisions. Contracts provide a “unified language” (e.g., REST, gRPC, XML‑RPC) for communication between parts of a system.
What & Why
At the architectural level we must consider how different services exchange information and express dependencies. In a microservice architecture, static coupling describes dependency relationships while dynamic coupling describes the information‑transfer mechanisms. A contract is the integration point that either transmits information or expresses a dependency, and it is expressed through a specific format.
The format used by parts of an architecture to convey information or dependencies.
Choosing a contract format (strict or loose) directly affects static and dynamic coupling, and therefore the overall brittleness of the system.
How to Decide Between Strict and Loose Contracts
The book Software Architecture: The Hard Parts distinguishes Strict and Loose contracts. Strict contracts require exact matching of names, types, order, and other details; RPC is a classic example.
// proto2
service ProfileService {
Profile queryProfile(ProfileRequest request);
}
message ProfileRequest {
required string profileId = 1;
required repeated ProfileConditions conditions = 2;
}
message ProfileConditions {
required string key = 1;
required string sign = 2;
required string value = 3;
}
message Profile {
required string id = 1;
required string name = 2;
required int32 age = 3;
required string addr1 = 4;
required string country = 5;
required string email = 6;
// ...
}Strict contracts can cause fragility: if a required field changes, every consumer that depends on the contract must be updated, leading to cascading failures.
Loose contracts relax these constraints. Using a resource‑oriented REST model, the same services can be described with Go structs:
// Go structs
type ProfileResourceA struct {
id string `json:"id"`
name string `json:"name"`
identify string `json:"identify"`
}
type ProfileResourceB struct {
id string `json:"id"`
name string `json:"name"`
workingYears string `json:"workingYears"`
}Even looser, a simple key‑value JSON payload can be used:
{
"id": 1,
"name": "customerName1",
"identity": "440930X1"
}
{
"id": 1,
"name": "customerName1",
"workingYears": 10
}While loose contracts improve decoupling, they reduce validation and increase uncertainty, requiring contract‑fitness functions to ensure correctness.
Consumer‑Driven Contracts (CDC)
CDC inverts the traditional push model: the consumer defines the contract it needs and provides it to the provider, which must keep the contract tests green.
The concept of a consumer‑driven contract inverses that relationship into a pull model; here, the consumer puts together a contract for the items they need from the provider, and passes the contract to the provider, who includes it in their build and keeps the contract test green at all times.
Providers incorporate these contracts into CI/CD pipelines, allowing each team to choose the appropriate strictness while guaranteeing contract accuracy.
Stamp Coupling and Trade‑Offs
Stamp coupling occurs when services exchange large objects or full entities, increasing bandwidth and brittleness. Reducing the contract to only necessary fields (e.g., just a name) can dramatically lower data transfer costs.
Trade‑off tables (illustrated in the original article) summarize when to prefer strict versus loose contracts, considering factors such as compatibility, bandwidth, and evolution speed.
Summary
When designing contracts at the architectural level, strict contracts (e.g., gRPC/protobuf) provide strong guarantees but can make the system brittle if requirements change. Loose contracts (REST, KV) improve flexibility but need additional validation mechanisms. Consumer‑driven contracts offer a pull‑based approach that aligns provider implementations with consumer needs, helping to balance decoupling, compatibility, and operational costs.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Xiaokun's Architecture Exploration Notes
10 years of backend architecture design | AI engineering infrastructure, storage architecture design, and performance optimization | Former senior developer at NetEase, Douyu, Inke, etc.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
