xiaohongshu
xiaohongshu
Comprehensive guide to backward compatibility rules for APIs, databases, and data contracts with migration strategies and testing approaches
Full skill instructions
Definition: New version works with old clients without requiring changes.
API v1: GET /users → { id, name }
API v2: GET /users → { id, name, email } (added field)
Old client (expects v1) calls v2 API:
→ Receives { id, name, email }
→ Ignores 'email' field
→ Works! ✅ Backward compatible
Version 1.0 → 1.1 → 1.2 → 2.0
↓ ↓ ↓ ↓
Compatible Compatible Breaking
Without Backward Compatibility:
Deploy new API version
→ All clients break immediately
→ Emergency rollback
→ Coordinate with all clients to update
→ Big bang migration (risky!)
With Backward Compatibility:
Deploy new API version
→ Old clients continue working
→ Clients migrate gradually
→ No coordination needed
Timeline:
Month 0: Deploy v2 (backward compatible)
Month 1: 20% of clients migrated
Month 3: 50% of clients migrated
Month 6: 80% of clients migrated
Month 12: 100% migrated, deprecate v1
Without:
With:
Trust:
Definition: Old client works with new server
Example:
Old client expects: { id, name }
New server returns: { id, name, email }
Client ignores 'email' → Works ✅
Definition: New client works with old server
Example:
New client expects: { id, name, email? }
Old server returns: { id, name }
Client handles missing 'email' → Works ✅
Definition: Works both ways
Example:
v1: { id, name }
v2: { id, name, email? } (optional)
Old client + new server: Ignores 'email' ✅
New client + old server: Handles missing 'email' ✅
Definition: Doesn't work
Example:
Old client expects: { id, name }
New server returns: { id, firstName, lastName }
Client looks for 'name' → Not found → Breaks ❌
Before:
GET /users
After:
GET /users
GET /projects (new)
Compatible: ✅ Old clients don't call new endpoint
Before:
POST /users
{ "name": "John" }
After:
POST /users
{ "name": "John", "email": "[email protected]" } (email optional)
Compatible: ✅ Old clients don't send 'email', server handles missing field
Before:
GET /users → { "id": "123", "name": "John" }
After:
GET /users → { "id": "123", "name": "John", "email": "[email protected]" }
Compatible: ✅ Old clients ignore 'email'
Before:
status: "ACTIVE" | "INACTIVE"
After:
status: "ACTIVE" | "INACTIVE" | "PENDING"
Compatible: ✅ Old clients may not handle "PENDING", but schema is valid
Before:
name: minLength 5
After:
name: minLength 1 (less strict)
Compatible: ✅ Old clients sending 5+ chars still work
Before:
GET /users
After:
(endpoint removed)
Breaking: ❌ Old clients calling endpoint get 404
Before:
GET /users → { "id": "123", "name": "John" }
After:
GET /users → { "id": "123" } ('name' removed)
Breaking: ❌ Old clients expect 'name' field
Before:
POST /users
{ "name": "John" }
After:
POST /users
{ "name": "John", "email": "[email protected]" } (email required)
Breaking: ❌ Old clients don't send 'email', get 400 error
Before:
age: integer
After:
age: string
Breaking: ❌ Old clients send integer, server expects string
Before:
{ "name": "John" }
After:
{ "fullName": "John" }
Breaking: ❌ Old clients look for 'name', not found
Before:
amount: 100 (dollars)
After:
amount: 10000 (cents, changed meaning!)
Breaking: ❌ Same field, different meaning
-- Safe: Existing rows get NULL
ALTER TABLE users ADD COLUMN email VARCHAR(255);
-- Safe: Doesn't affect existing tables
CREATE TABLE projects (
id UUID PRIMARY KEY,
name VARCHAR(255)
);
-- Safe: Improves performance, doesn't change data
CREATE INDEX idx_users_email ON users(email);
-- Breaking: Existing queries fail
ALTER TABLE users DROP COLUMN name;
-- Multi-step migration:
-- Step 1: Stop writing to column
-- Step 2: Deploy code that doesn't read column
-- Step 3: Drop column
-- Breaking: Existing queries fail
ALTER TABLE users RENAME COLUMN name TO full_name;
-- Multi-step migration:
-- Step 1: Add new column
ALTER TABLE users ADD COLUMN full_name VARCHAR(255);
-- Step 2: Copy data
UPDATE users SET full_name = name;
-- Step 3: Create view with old name
CREATE VIEW users_v1 AS
SELECT id, full_name AS name FROM users;
-- Step 4: Migrate clients to new column
-- Step 5: Drop view and old column
-- Breaking: Data may not fit
ALTER TABLE users ALTER COLUMN age TYPE VARCHAR(10);
-- Multi-step migration:
-- Step 1: Add new column
ALTER TABLE users ADD COLUMN age_str VARCHAR(10);
-- Step 2: Copy and convert data
UPDATE users SET age_str = age::VARCHAR;
-- Step 3: Migrate clients
-- Step 4: Drop old column, rename new column
Before:
{ "id": "123", "name": "John" }
After:
{ "id": "123", "name": "John", "email": "[email protected]" }
Compatible: ✅ Old consumers ignore 'email'
Before:
user.created
After:
user.created
user.deleted (new)
Compatible: ✅ Old consumers don't subscribe to new event
Before:
{ "id": "123", "name": "John" }
After:
{ "id": "123" } ('name' removed)
Breaking: ❌ Old consumers expect 'name'
Before:
{ "age": 30 } (integer)
After:
{ "age": "30" } (string)
Breaking: ❌ Old consumers expect integer
Before:
{ "name": "John" }
After:
{ "fullName": "John" }
Breaking: ❌ Old consumers look for 'name'
Before:
type User {
id: ID!
name: String!
}
After:
type User {
id: ID!
name: String!
email: String # New field
}
Compatible: ✅ Old queries don't request 'email'
After:
type Project {
id: ID!
name: String!
}
Compatible: ✅ Old queries don't use new type
After:
type Query {
users: [User!]!
projects: [Project!]! # New query
}
Compatible: ✅ Old clients don't call new query
Before:
type User {
id: ID!
name: String!
}
After:
type User {
id: ID!
# 'name' removed
}
Breaking: ❌ Old queries requesting 'name' fail
Before:
type User {
age: Int!
}
After:
type User {
age: String! # Changed type
}
Breaking: ❌ Old queries expect Int
Before:
users(limit: Int): [User!]!
After:
users(limit: Int!, offset: Int!): [User!]! # 'offset' required
Breaking: ❌ Old queries don't provide 'offset'
In Docs:
## GET /users/:id/profile
**Deprecated:** Use GET /users/:id instead.
**Sunset Date:** 2024-12-31
In Code:
/**
* @deprecated Use getUser() instead. Will be removed in v2.0.0.
*/
function getUserProfile(id) {
// ...
}
In Headers:
HTTP/1.1 200 OK
Deprecation: true
Sunset: Sat, 31 Dec 2024 23:59:59 GMT
Link: <https://docs.example.com/migration>; rel="deprecation"
Example:
# Migration Guide: /users/:id/profile → /users/:id
## What's Changing
The `/users/:id/profile` endpoint is deprecated. Use `/users/:id` instead.
## Why
Consolidating user endpoints for simplicity.
## How to Migrate
### Before
```javascript
const profile = await fetch('/api/users/123/profile');
const user = await fetch('/api/users/123');
### Set Sunset Date (6-12 Months)
**Timeline:**
Month 0: Announce deprecation Month 3: Warn users (emails, headers) Month 6: Final warning Month 12: Sunset (stop working)
### Warn Clients (in Response Headers)
HTTP/1.1 200 OK Deprecation: true Sunset: Sat, 31 Dec 2024 23:59:59 GMT Warning: 299 - "This endpoint is deprecated. Use /v2/users instead. See https://docs.example.com/migration"
### Monitor Usage
**Track Deprecated Endpoint Usage:**
```javascript
app.get('/users/:id/profile', (req, res) => {
// Log usage
metrics.increment('deprecated.users_profile.calls');
// Return response with deprecation headers
res.set('Deprecation', 'true');
res.set('Sunset', 'Sat, 31 Dec 2024 23:59:59 GMT');
res.json(profile);
});
Dashboard:
Deprecated Endpoint Usage:
/users/:id/profile
- Calls/day: 1,000 (down from 5,000 last month)
- Top clients: mobile-app (500), web-app (300), partner-api (200)
- Sunset date: 2024-12-31 (6 months remaining)
Process:
1. Sunset date reached
2. Return 410 Gone
3. Monitor for errors
4. After 1 month, remove code
410 Gone Response:
HTTP/1.1 410 Gone
Content-Type: application/json
{
"error": "This endpoint has been removed. Use /v2/users instead.",
"migrationGuide": "https://docs.example.com/migration"
}
Step 1: Add New Field, Both Fields Populated
// API returns both fields
{
"name": "John Doe", // Old field (deprecated)
"fullName": "John Doe" // New field
}
Step 2: Clients Migrate to New Field
Month 1-3: Clients update to use 'fullName'
Step 3: Stop Populating Old Field (Still Present)
// API returns both fields, but old field is null
{
"name": null, // Old field (deprecated, not populated)
"fullName": "John Doe" // New field
}
Step 4: Remove Old Field
// API returns only new field
{
"fullName": "John Doe" // New field
}
Format:
/api/v1/users
/api/v2/users
Pros:
Cons:
Format:
GET /api/users
Accept-Version: v1
Pros:
Cons:
Strategy:
Pros:
Cons:
See Contract Testing skill for details
Example:
describe('Backward compatibility', () => {
test('v1 client works with v2 API', async () => {
// v1 client expects { id, name }
const response = await v1Client.get('/users/123');
expect(response).toHaveProperty('id');
expect(response).toHaveProperty('name');
// v2 API may return additional fields (email), but v1 client ignores them
});
});
OpenAPI:
oasdiff breaking old.yaml new.yaml
JSON Schema:
const isCompatible = checkBackwardCompatibility(oldSchema, newSchema);
if (!isCompatible) {
throw new Error('Breaking change detected');
}
Process:
1. Deploy new version to 5% of traffic
2. Monitor errors
3. If no errors, increase to 25%
4. Continue until 100%
See OpenAPI Governance skill
Install:
npm install -g @graphql-inspector/cli
Check:
graphql-inspector diff old.graphql new.graphql
Output:
✖ Field 'name' was removed from object type 'User' (BREAKING)
✔ Field 'email' was added to object type 'User'
Install:
brew install bufbuild/buf/buf
Check:
buf breaking --against .git#branch=main
Example:
function checkBackwardCompatibility(oldSchema, newSchema) {
// Check if all required fields in old schema are still required
for (const field of oldSchema.required) {
if (!newSchema.required.includes(field)) {
return false; // Breaking: required field removed
}
}
// Check if field types changed
for (const [field, oldType] of Object.entries(oldSchema.properties)) {
const newType = newSchema.properties[field];
if (newType && newType.type !== oldType.type) {
return false; // Breaking: type changed
}
}
return true; // Compatible
}
Example:
# Changelog
## v2.0.0 (2024-06-01) - BREAKING CHANGES
### Breaking Changes
- **Removed:** `GET /users/:id/profile` endpoint. Use `GET /users/:id` instead.
- **Changed:** `POST /users` now requires `email` field.
- **Renamed:** Response field `name` renamed to `fullName`.
### Migration Guide
See https://docs.example.com/migration/v1-to-v2
## v1.2.0 (2024-05-01)
### Added
- New field `avatar` in user response (optional, backward compatible)
See "Provide Migration Guide" section above
See "Deprecation Strategy" section above
Email Template:
Subject: Action Required: API v1 Deprecation
Hi [Client],
We're writing to inform you that API v1 will be deprecated on December 31, 2024.
What's changing:
- Endpoint /users/:id/profile will be removed
- Use /users/:id instead
Action required:
- Update your integration by December 31, 2024
- See migration guide: https://docs.example.com/migration
Need help?
- Reply to this email
- Schedule a call: https://calendly.com/api-team
Thank you,
API Team
Process:
1. Create v2 API (breaking changes)
2. Maintain v1 API (for transition)
3. Announce deprecation of v1
4. Give 6-12 months notice
5. Sunset v1
Timeline:
Month 0: Launch v2
Month 0-12: Maintain both v1 and v2
Month 12: Sunset v1
Provide:
Example:
# Migration tool
./migrate-to-v2.sh
# Scans code for v1 API calls
# Suggests v2 equivalents
# Optionally auto-updates code
Versioning:
Stripe-Version: 2024-01-15Deprecation:
Policy:
Best Practices:
@deprecated directive- [ ] All changes reviewed for compatibility
- [ ] Automated compatibility checks in CI
- [ ] Deprecation process defined (6-12 months)
- [ ] Migration guides for breaking changes
- [ ] Versioning strategy documented
- [ ] Monitor deprecated endpoint usage
- [ ] Direct outreach to major clients
- [ ] Test with old clients
- [ ] Canary deployments for risky changes
See "Compatibility Checking Tools" section
GitHub Actions:
- name: Check backward compatibility
run: |
# OpenAPI
oasdiff breaking origin/main:openapi.yaml HEAD:openapi.yaml
# GraphQL
graphql-inspector diff origin/main:schema.graphql HEAD:schema.graphql
# Fail if breaking changes
if [ $? -ne 0 ]; then
echo "Breaking changes detected"
exit 1
fi
Express:
function deprecationMiddleware(sunsetDate, migrationUrl) {
return (req, res, next) => {
res.set('Deprecation', 'true');
res.set('Sunset', sunsetDate);
res.set('Link', `<${migrationUrl}>; rel="deprecation"`);
res.set('Warning', `299 - "This endpoint is deprecated. See ${migrationUrl}"`);
// Log usage
metrics.increment('deprecated.endpoint.calls', {
path: req.path,
client: req.headers['user-agent']
});
next();
};
}
// Usage
app.get('/users/:id/profile',
deprecationMiddleware('Sat, 31 Dec 2024 23:59:59 GMT', 'https://docs.example.com/migration'),
(req, res) => {
// Handle request
}
);
Backward Compatibility: New version works with old clients
Why:
Safe Changes (REST API):
Breaking Changes:
Database:
Deprecation:
Multi-Step Migration:
Versioning:
/v1/, /v2/Accept-Version: v1Testing:
Tools:
Communicating:
Use semantic versioning (SemVer)
Tag all releases with version numbers
[ ] Document version compatibility matrix
[ ] Use @deprecated directive for breaking changes
Communicate changes early
Provide migration guides
Hold breaking change meetings
[ ] Create data contracts with consumers
[ ] Document breaking changes clearly
[ ]
Test backward compatibility
Test with production-like data
[ ] Test migration scripts thoroughly
[ ]
Use zero-downtime migration pattern
Use multi-step migrations for breaking changes
[ ] Backfill data before removing old columns
[ ]
[ ] Document all breaking changes with dates
[ ] Document breaking changes clearly
[ ] Provide migration guides for consumers
[ ] Document version compatibility
[ ]
[ ] Monitor breaking change impacts
[ ] Track consumer adoption of new schema
[ ] Set up dashboards for schema health
[ ] [ ]
[ ] Use data contracts for all shared data
[ ] Enforce schema validation at source
[ ] [ ] Implement CI/CD schema checks
[ ]
[ ] Use semantic versioning
[ ] Implement zero-downtime migrations
[ ] Backfill data before removing old columns
[ ] Test backward compatibility
[ ] Have rollback procedures ready
[ ] Monitor schema drift metrics
[ ] Track migration success rates
[ ] Document all breaking changes
[ ] Set up change notifications
[ ] Test with production-like data
[ ] Monitor test coverage
[ ] Optimize schema validation overhead
[ ] Cache schema definitions
[ ] Use efficient validation libraries
[ ] Monitor schema performance impact
[ ] Establish schema ownership
[ ] Create schema review process
[ ] Define schema lifecycle
[ ] Plan schema deprecation strategy
[ ] Set up incident response for violations
[ ] Test schema validation logic
[ ] Train team on backward compatibility
[ ]
xiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience