技能创建器
skill-creator
创建、改进和评估自定义技能,把专业流程转化为可重复使用的 AI 能力。
backend-api-versioning【数据与研究】Use this skill when the user says 'API versioning', 'version strategy', 'URI versioning', 'header versioning', 'content negotiation', 'accept header', 'breaking change', 'API migration', 'deprecate endpoint', 'sunset endpoint'. This skill manages API version transitions using URI, header, or content-negotiation strategies. Applies to any backend stac…
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
Manage API version transitions without breaking existing clients. Support coexistence of multiple API versions and provide clear deprecation signals.
Exact user phrases: "API versioning", "version strategy", "URI versioning", "header versioning", "content negotiation", "accept header", "breaking change", "API migration", "deprecate endpoint", "sunset endpoint", "version path".
Versioning strategy recommendation or configuration. No file unless requested.
Strategy: {URI|Header|Content-negotiation}
Current Version: {v1|v2}
Deprecation Policy: {N versions back|N months}
Do clients control the URL they call?
├── Yes → URI Path Versioning (most common)
├── No → Is content negotiation important?
│ ├── Yes → Content Negotiation (Accept header / media types)
│ └── No → Header Versioning (X-API-Version)
Is the API public or internal?
├── Public → URI path (most explicit for external devs)
│ └── Mobile clients? → URI path (app store can't change headers)
└── Internal → Header versioning (cleaner, version managed by gateway)
How many concurrent versions to support?
├── 2 → URI or Header works well
├── 3+ → URI versioning recommended (explicit routing)
└── 1 (always latest) → Not versioning — additive changes only
Is rapid iteration expected?
├── Yes → No versioning, feature flags, additive changes only
└── No → Formal versioning with deprecation policy (18-month min)
Is the change backward-compatible?
├── Yes → Can ship in current version
├── No → Does it remove a field from response?
│ ├── Yes → Major version bump required
│ └── No → Does it change a field type?
│ ├── Yes → Major version bump required
│ └── No → Does it change behavior/semantics?
│ ├── Yes → Major version bump required
│ └── No → Does it make optional field required?
│ ├── Yes → Major version bump required
│ └── No → Additive change, safe within version
└── Unsure → Assume breaking. Better to version than break clients.
Who are the consumers?
├── External third-party developers
│ ├── Announce deprecation ≥ 12 months before sunset
│ ├── Send email notifications to known contacts
│ └── Post migration guide on developer portal
├── Internal B2B partners
│ ├── Announce deprecation ≥ 6 months before sunset
│ └── Provide dedicated migration support
└── Internal service-to-service
├── Announce deprecation ≥ 3 months before sunset
└── Coordinate deployments with consumers
| Strategy | Mechanism | Pros | Cons | Best For |
|---|---|---|---|---|
| URI | /v1/users | Visible, cache-friendly, simple routing | URL pollution, hard-coded versions | Public APIs, mobile, REST |
| Header | X-API-Version: 2 | Clean URLs, no pollution | Hidden, needs client config | Internal, service-to-service |
| Content-negotiation | Accept: application/vnd.api+json;version=2 | RESTful, clean separation | Complex, opaque to tooling | Hypermedia, REST purists |
| Query param | ?version=2 | Simplest implementation | No cache differentiation, non-standard | Early-stage, internal tools |
GET /v1/users -> list v1 shape
GET /v2/users -> list v2 shape (migrated)
POST /v1/users -> create v1 shape
POST /v2/users -> create v2 shape
Implementation approaches:
Pattern A: Separate Router per Version
// Express.js
const v1Router = express.Router();
v1Router.get('/users', v1UsersController.list);
v1Router.post('/users', v1UsersController.create);
const v2Router = express.Router();
v2Router.get('/users', v2UsersController.list);
v2Router.post('/users', v2UsersController.create);
app.use('/v1', v1Router);
app.use('/v2', v2Router);
# FastAPI
from fastapi import APIRouter, FastAPI
app = FastAPI()
v1 = APIRouter(prefix="/v1")
v2 = APIRouter(prefix="/v2")
@v1.get("/users")
async def list_users_v1(): ...
@v2.get("/users")
async def list_users_v2(): ...
app.include_router(v1)
app.include_router(v2)
Pattern B: Translation Layer — shared core with version adapters:
// Internal canonical model
interface InternalOrder {
id: string;
customer: { id: string; name: string; email: string };
total: number;
currency: string;
items: Array<{ productId: string; quantity: number; unitPrice: number }>;
createdAt: Date;
}
// Version adapters transform to wire format
class OrderTranslator {
toV1(order: InternalOrder): OrderV1 {
return {
id: order.id,
customer_name: order.customer.name,
total: order.total,
items: order.items.map(i => ({ product_id: i.productId, qty: i.quantity })),
};
}
toV2(order: InternalOrder): OrderV2 {
return {
id: order.id,
customer: { id: order.customer.id, name: order.customer.name, email: order.customer.email },
total: order.total,
currency: order.currency,
items: order.items.map(i => ({ productId: i.productId, quantity: i.quantity, unitPrice: i.unitPrice })),
created_at: order.createdAt.toISOString(),
};
}
}
// Router uses translator
router.get('/v1/orders/:id', async (req, res) => {
const order = await orderService.findById(req.params.id);
res.json(translator.toV1(order));
});
router.get('/v2/orders/:id', async (req, res) => {
const order = await orderService.findById(req.params.id);
res.json(translator.toV2(order));
});
Directory structure:
src/
├── core/
│ ├── services/ # Shared business logic
│ ├── models/ # Internal canonical model
│ └── repositories/ # Shared data access
├── api/
│ ├── v1/
│ │ ├── controllers/
│ │ ├── schemas/ # v1 request/response schemas
│ │ └── adapters/ # v1 ↔ core model adapters
│ ├── v2/
│ │ ├── controllers/
│ │ ├── schemas/
│ │ └── adapters/
│ └── middleware/
│ └── version-router.ts
// Express header version middleware
function headerVersion(versionMap: Record<string, express.Router>) {
return (req: express.Request, res: express.Response, next: express.NextFunction) => {
const version = req.headers['x-api-version'] as string || '1';
const handler = versionMap[version];
if (!handler) {
return res.status(400).json({ error: `Unsupported version: ${version}` });
}
handler(req, res, next);
};
}
app.get('/users', headerVersion({
'1': v1UsersController.list,
'2': v2UsersController.list,
}));
HTTP/1.1 200 OK
Deprecation: true
Sunset: Sat, 23 May 2027 00:00:00 GMT
Link: </v2/users>; rel="successor-version"
Warning: 299 - "This API version is deprecated. Migrate to the latest version."
Implementation:
function deprecationMiddleware(sunsetDate: string, migrationUrl: string) {
return (req: Request, res: Response, next: NextFunction) => {
res.setHeader('Deprecation', 'true');
res.setHeader('Sunset', sunsetDate);
res.setHeader('Link', `<${migrationUrl}>; rel="successor-version"`);
res.setHeader('Warning', '299 - "This API version is deprecated. Migrate to the latest version."');
next();
};
}
router.get('/v1/users', deprecationMiddleware('2026-12-31T23:59:59Z', '/v2/users'), v1Handler);
Breaking changes require a new version:
Non-breaking changes (additive within a version):
Announce deprecation with minimum notice period:
Set explicit sunset date from day one
Add deprecation headers to all responses from deprecated versions
Keep old version running for full deprecation period
Monitor version adoption metrics
Send deprecation notices to known consumers
After sunset date, return 410 Gone with migration docs
HTTP/1.1 410 Gone
Content-Type: application/json
{
"error": {
"code": "GONE",
"message": "API version 1 has been sunset. Please migrate to v2.",
"migrationUrl": "https://docs.example.com/migration-v1-to-v2",
"sunsetDate": "2026-12-31T23:59:59Z"
}
}
class VersionRouter {
private versions = new Map<string, Router>();
register(version: string, router: Router): void {
this.versions.set(version, router);
}
getRouter(): Router {
const router = Router();
// URI versioning
router.use('/v:version(\\d+)', (req, res, next) => {
const version = `v${req.params.version}`;
const versionRouter = this.versions.get(version);
if (!versionRouter) {
return res.status(404).json({ error: { code: 'UNKNOWN_VERSION', message: `API version ${version} is not supported` } });
}
req.url = req.url.replace(`/v${req.params.version}`, '');
versionRouter(req, res, next);
});
return router;
}
}
const versionRouter = new VersionRouter();
versionRouter.register('v1', v1Router);
versionRouter.register('v2', v2Router);
app.use('/api', versionRouter.getRouter());
| Versions | Maintenance Cost | Recommendation |
|---|---|---|
| 1 | Low | Default, additive changes only |
| 2 | Moderate | Standard — support current + previous |
| 3+ | High | Exponential overhead. Seek to retire oldest. |
-- Canonical table with versioned projections
CREATE TABLE orders_canonical (
id UUID PRIMARY KEY,
data JSONB NOT NULL, -- canonical format
version INT NOT NULL DEFAULT 1,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- v1 view
CREATE VIEW orders_v1 AS
SELECT id, data->>'customer_name' AS customer_name,
(data->>'total')::DECIMAL AS total
FROM orders_canonical;
-- v2 view
CREATE VIEW orders_v2 AS
SELECT id, data->>'customer_id' AS customer_id,
(data->>'total')::DECIMAL AS total,
data->>'currency' AS currency
FROM orders_canonical;
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-ingress
spec:
rules:
- host: api.example.com
http:
paths:
- path: /v1
pathType: Prefix
backend:
service:
name: api-v1-service
port: number: 80
- path: /v2
pathType: Prefix
backend:
service:
name: api-v2-service
port: number: 80
Using /2025-01-01/users without semantic meaning. Combine with semver or release numbering.
3+ concurrent versions creates exponential maintenance burden. Enforce max 2 active versions.
Failing to inform clients leads to surprise breakage. Always include Deprecation, Sunset, Link headers.
Treating breaking changes as "minor fixes" breaks clients silently. Define breaking changes clearly.
Internal services change contracts without notice. Apply versioning with shorter deprecation (1-3 months).
Releasing new version without migration guide leaves clients stranded. Provide before/after examples.
Copy-pasting entire controllers for each version. Use shared core + version-specific adapters.
Assuming API will never break. Every API eventually needs to change.
Forcing all clients to latest version breaks those that can't upgrade. Always support previous version.
Versioning every single endpoint independently. Version at API level, not endpoint level.
| Aspect | URI Path | Header | Content Negotiation |
|---|---|---|---|
| Explicitness | Highest | Medium | Low |
| Client effort | None | Medium | High |
| Cache friendliness | High | Low (Vary header) | Medium |
| RESTfulness | Medium | Medium | Highest |
| Browser testability | High | Low | Low |
| API gateway routing | Native | Requires config | Requires config |
| OpenAPI support | Native | Partial | Manual |
| Mobile client support | Native | Complex | Complex |
| Aspect | Semver (v1.2.3) | Date-based (2025-01) |
|---|---|---|
| Change magnitude | Communicates | Doesn't communicate |
| Ordering | Numerical | Chronological |
| Client comprehension | Requires semver knowledge | Intuitive |
| Tooling | npm, package managers | Manual |
| API contracts | Well-suited | Less clear |
references/breaking-change-mgmt.md — Identifying, documenting, and communicating breaking changesreferences/version-compatibility-testing.md — Testing strategies for multi-version API compatibilityreferences/version-discovery.md — How clients discover available versions and their capabilitiesreferences/version-lifecycle.md — Full version lifecycle from proposal to sunsetreferences/version-migration-automation.md — Automating client migration between API versionsreferences/versioning-strategies.md — Detailed comparison of URI, header, content-negotiation approachesreferences/api-versioning-strategies.md — Comprehensive strategy selection guide with implementation detailsreferences/api-migration-deprecation.md — Migration patterns and deprecation workflows in depthreferences/api-versioning-fundamentals.md — API Versioning Fundamentalsreferences/api-versioning-advanced.md — API Versioning Advanced Patternsreferences/api-versioning-multi-protocol.md — Versioning Across REST, GraphQL, gRPC, and EventsNo artifact produced unless requested. Next skill: scheduling-cron — schedule background jobs for version migration tasks. Carry forward: version strategy, active versions, sunset dates.