- Added request/response flow diagrams to api-design and api-gateway-advanced skills for better visualization of processes. - Introduced configuration loading flow in configuration-management skill to clarify the configuration process. - Included error propagation flow in error-handling-patterns skill to illustrate error handling across layers. - Enhanced various skills with additional diagrams to improve understanding of complex concepts. These updates aim to provide clearer guidance and improve the overall documentation experience for developers.
405 lines
12 KiB
Markdown
405 lines
12 KiB
Markdown
---
|
|
name: api-versioning-strategy
|
|
description: API versioning strategies for GoodGo microservices including semantic versioning, backward compatibility patterns, API deprecation, version negotiation, and breaking changes handling.
|
|
---
|
|
|
|
# API Versioning Strategy
|
|
|
|
## When to Use This Skill
|
|
|
|
Use this skill when:
|
|
- Versioning APIs
|
|
- Handling breaking changes
|
|
- Implementing API deprecation
|
|
- Maintaining backward compatibility
|
|
- Implementing version negotiation
|
|
- Managing multiple API versions
|
|
- Planning API evolution
|
|
- Communicating API changes to consumers
|
|
|
|
## Core Concepts
|
|
|
|
### Versioning Strategies
|
|
|
|
1. **URL Path Versioning**: `/api/v1/users`, `/api/v2/users`
|
|
2. **Header Versioning**: `Accept: application/vnd.goodgo.v1+json`
|
|
3. **Query Parameter**: `/api/users?version=1`
|
|
4. **Semantic Versioning**: Major.Minor.Patch (e.g., 1.2.3)
|
|
|
|
### Compatibility Types
|
|
|
|
- **Backward Compatible**: New version works with old clients
|
|
- **Forward Compatible**: Old version works with new clients
|
|
- **Breaking Changes**: Incompatible changes requiring new version
|
|
|
|
## Version Negotiation
|
|
|
|
Version negotiation allows clients to request a specific API version through headers while maintaining clean URLs. The middleware extracts the version from the `Accept` header and routes to the appropriate handler.
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant Client
|
|
participant Middleware as Version Negotiation<br/>Middleware
|
|
participant Controller as Version-Aware<br/>Controller
|
|
participant Service
|
|
|
|
Client->>Middleware: Request with Accept header<br/>Accept: application/vnd.goodgo.v1+json
|
|
Middleware->>Middleware: Extract version from header
|
|
alt Version specified
|
|
Middleware->>Middleware: Parse version number
|
|
alt Version supported
|
|
Middleware->>Controller: Set req.apiVersion = 1
|
|
Controller->>Controller: Check version
|
|
Controller->>Service: Call service method
|
|
Service-->>Controller: Return data
|
|
Controller->>Controller: Format response for v1
|
|
Controller-->>Client: v1 response format
|
|
else Version not supported
|
|
Middleware-->>Client: 400 Unsupported Version
|
|
end
|
|
else No version specified
|
|
Middleware->>Controller: Set req.apiVersion = latest (2)
|
|
Controller->>Service: Call service method
|
|
Service-->>Controller: Return data
|
|
Controller->>Controller: Format response for v2
|
|
Controller-->>Client: v2 response format (default)
|
|
end
|
|
```
|
|
|
|
### Implementation
|
|
|
|
```typescript
|
|
// src/middlewares/version-negotiation.middleware.ts
|
|
import { Request, Response, NextFunction } from 'express';
|
|
import { logger } from '@goodgo/logger';
|
|
|
|
export function versionNegotiation(
|
|
req: Request,
|
|
res: Response,
|
|
next: NextFunction
|
|
): void {
|
|
const acceptHeader = req.headers.accept || '';
|
|
const versionMatch = acceptHeader.match(/application\/vnd\.goodgo\.v(\d+)\+json/);
|
|
|
|
if (versionMatch) {
|
|
const requestedVersion = parseInt(versionMatch[1], 10);
|
|
req.apiVersion = requestedVersion;
|
|
|
|
const supportedVersions = [1, 2];
|
|
if (!supportedVersions.includes(requestedVersion)) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: {
|
|
code: 'UNSUPPORTED_VERSION',
|
|
message: `API version ${requestedVersion} is not supported. Supported versions: ${supportedVersions.join(', ')}`,
|
|
},
|
|
});
|
|
}
|
|
} else {
|
|
req.apiVersion = 2; // Default to latest
|
|
}
|
|
|
|
next();
|
|
}
|
|
```
|
|
|
|
## API Deprecation Timeline
|
|
|
|
API deprecation follows a structured timeline to give consumers adequate time to migrate. The lifecycle progresses through active, deprecated, sunset, and removed phases.
|
|
|
|
```mermaid
|
|
gantt
|
|
title API Version Lifecycle Timeline
|
|
dateFormat YYYY-MM-DD
|
|
section Version 1
|
|
Active (v1 only) :active, v1-active, 2024-01-01, 2024-06-01
|
|
Deprecated (v1 + v2) :crit, v1-deprecated, 2024-06-01, 2024-12-31
|
|
Sunset Period :v1-sunset, 2024-12-31, 2025-01-31
|
|
Removed :v1-removed, 2025-01-31, 1d
|
|
section Version 2
|
|
Development :v2-dev, 2024-03-01, 2024-06-01
|
|
Active (v1 + v2) :active, v2-active, 2024-06-01, 2025-12-31
|
|
```
|
|
|
|
### Deprecation Phases
|
|
|
|
```mermaid
|
|
stateDiagram-v2
|
|
[*] --> Active: Version Released
|
|
Active --> Deprecated: New Version Released<br/>Add Deprecation Headers
|
|
Deprecated --> Sunset: Sunset Date Reached<br/>Stop Accepting New Requests
|
|
Sunset --> Removed: Grace Period Ended<br/>Remove Routes
|
|
Removed --> [*]
|
|
|
|
note right of Active
|
|
- Version fully supported
|
|
- No warnings
|
|
- All features available
|
|
end note
|
|
|
|
note right of Deprecated
|
|
- Deprecation header set
|
|
- Warning headers added
|
|
- Migration guide provided
|
|
- Still functional
|
|
end note
|
|
|
|
note right of Sunset
|
|
- Read-only mode
|
|
- No new requests accepted
|
|
- Existing requests honored
|
|
end note
|
|
```
|
|
|
|
### Deprecation Headers
|
|
|
|
```typescript
|
|
// src/middlewares/deprecation.middleware.ts
|
|
export function deprecationMiddleware(version: string, sunsetDate: string) {
|
|
return (req: Request, res: Response, next: NextFunction): void => {
|
|
if (req.apiVersion && parseInt(req.apiVersion.toString()) < parseInt(version)) {
|
|
res.setHeader('Deprecation', 'true');
|
|
res.setHeader('Sunset', sunsetDate);
|
|
res.setHeader('Link', `<${req.url.replace(/\/v\d+/, `/v${version}`)}>; rel="successor-version"`);
|
|
res.setHeader('Warning', `299 - "API version ${req.apiVersion} is deprecated. Please migrate to version ${version} by ${sunsetDate}"`);
|
|
}
|
|
|
|
next();
|
|
};
|
|
}
|
|
```
|
|
|
|
## Migration Flow
|
|
|
|
Breaking changes require a careful 3-phase migration strategy to ensure zero downtime and smooth client transitions.
|
|
|
|
```mermaid
|
|
flowchart TD
|
|
Start([Breaking Change Identified]) --> Phase1[Phase 1: Support Both Versions]
|
|
|
|
Phase1 --> DeployV2[Deploy v2 alongside v1]
|
|
DeployV2 --> Monitor1[Monitor v1 and v2 usage]
|
|
Monitor1 --> Wait1[Wait for client adoption]
|
|
Wait1 --> Phase2{Sufficient<br/>v2 adoption?}
|
|
|
|
Phase2 -->|No| Wait1
|
|
Phase2 -->|Yes| Phase2Start[Phase 2: Deprecate v1]
|
|
|
|
Phase2Start --> AddHeaders[Add deprecation headers to v1]
|
|
AddHeaders --> NotifyClients[Notify clients via<br/>deprecation warnings]
|
|
NotifyClients --> ProvideGuide[Provide migration guide]
|
|
ProvideGuide --> Monitor2[Monitor migration progress]
|
|
Monitor2 --> Wait2[Wait until sunset date]
|
|
Wait2 --> Phase3{Sunset date<br/>reached?}
|
|
|
|
Phase3 -->|No| Monitor2
|
|
Phase3 -->|Yes| Phase3Start[Phase 3: Remove v1]
|
|
|
|
Phase3Start --> StopAccepting[Stop accepting new v1 requests]
|
|
StopAccepting --> GracePeriod[Grace period for<br/>existing requests]
|
|
GracePeriod --> RemoveRoutes[Remove v1 routes]
|
|
RemoveRoutes --> End([Migration Complete])
|
|
|
|
style Phase1 fill:#e1f5ff
|
|
style Phase2Start fill:#fff4e1
|
|
style Phase3Start fill:#ffe1e1
|
|
style End fill:#e1ffe1
|
|
```
|
|
|
|
### Implementation Strategy
|
|
|
|
```typescript
|
|
// src/core/api/migration.strategy.ts
|
|
export class MigrationStrategy {
|
|
/**
|
|
* Phase 1: Support both versions
|
|
*/
|
|
phase1SupportBoth(): void {
|
|
router.use('/v1', v1Router);
|
|
router.use('/v2', v2Router);
|
|
}
|
|
|
|
/**
|
|
* Phase 2: Deprecate v1
|
|
*/
|
|
phase2DeprecateV1(): void {
|
|
router.use('/v1', deprecationMiddleware('2', '2024-12-31'), v1Router);
|
|
router.use('/v2', v2Router);
|
|
}
|
|
|
|
/**
|
|
* Phase 3: Remove v1
|
|
*/
|
|
phase3RemoveV1(): void {
|
|
router.use('/v2', v2Router);
|
|
// v1 routes removed after sunset date
|
|
}
|
|
}
|
|
```
|
|
|
|
## URL Path Versioning
|
|
|
|
### Implementation
|
|
|
|
```typescript
|
|
// src/routes/index.ts
|
|
import { Router } from 'express';
|
|
import v1Router from './v1';
|
|
import v2Router from './v2';
|
|
|
|
const router = Router();
|
|
|
|
router.use('/v1', v1Router);
|
|
router.use('/v2', v2Router);
|
|
|
|
export default router;
|
|
```
|
|
|
|
## Semantic Versioning
|
|
|
|
### Version Structure
|
|
|
|
```
|
|
MAJOR.MINOR.PATCH
|
|
|
|
MAJOR: Breaking changes
|
|
MINOR: Backward-compatible additions
|
|
PATCH: Backward-compatible bug fixes
|
|
```
|
|
|
|
### Version Response
|
|
|
|
```typescript
|
|
// src/core/api/version.middleware.ts
|
|
export function versionMiddleware(req: Request, res: Response, next: NextFunction): void {
|
|
const originalJson = res.json.bind(res);
|
|
|
|
res.json = (data: any) => {
|
|
const response = {
|
|
...data,
|
|
metadata: {
|
|
...data.metadata,
|
|
apiVersion: req.apiVersion || '2.0.0',
|
|
serviceVersion: process.env.SERVICE_VERSION || '1.0.0',
|
|
},
|
|
};
|
|
|
|
return originalJson(response);
|
|
};
|
|
|
|
next();
|
|
}
|
|
```
|
|
|
|
## Backward Compatibility
|
|
|
|
### Compatibility Layer
|
|
|
|
```typescript
|
|
// src/core/api/compatibility.adapter.ts
|
|
export class CompatibilityAdapter {
|
|
adaptV1ToV2(v1Data: any): any {
|
|
return {
|
|
success: true,
|
|
data: {
|
|
user: {
|
|
...v1Data,
|
|
profile: null, // Add default for new field
|
|
},
|
|
},
|
|
metadata: {
|
|
version: '2.0.0',
|
|
adapted: true,
|
|
},
|
|
};
|
|
}
|
|
|
|
adaptV2RequestToV1(v2Request: any): any {
|
|
return {
|
|
email: v2Request.email,
|
|
name: v2Request.name,
|
|
// Ignore new fields
|
|
};
|
|
}
|
|
}
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
1. **Versioning Strategy**: Choose URL path or header, be consistent
|
|
2. **Semantic Versioning**: Use MAJOR.MINOR.PATCH
|
|
3. **Deprecation**: Always deprecate before removing
|
|
4. **Migration Guide**: Provide clear migration documentation
|
|
5. **Backward Compatibility**: Maintain compatibility when possible
|
|
6. **Communication**: Clearly communicate version changes
|
|
|
|
## Common Mistakes
|
|
|
|
1. **No Deprecation Period**: Breaking clients suddenly
|
|
```typescript
|
|
// ❌ BAD: Remove v1 immediately
|
|
router.use('/v2', v2Router);
|
|
|
|
// ✅ GOOD: Deprecate with sunset date
|
|
router.use('/v1', deprecationMiddleware('2', '2024-12-31'), v1Router);
|
|
router.use('/v2', v2Router);
|
|
```
|
|
|
|
2. **Breaking Changes Without Major Version**: Client confusion
|
|
```
|
|
# ❌ BAD: Breaking change in minor version
|
|
v1.1.0 → Changed response format
|
|
|
|
# ✅ GOOD: Breaking change = new major version
|
|
v1.x.x → v2.0.0 (new response format)
|
|
```
|
|
|
|
3. **Inconsistent Versioning Strategy**: Mixed approaches
|
|
```typescript
|
|
// ❌ BAD: Mix URL and header versioning
|
|
/api/v1/users + Accept: application/vnd.v2+json
|
|
|
|
// ✅ GOOD: Choose one approach
|
|
/api/v1/users OR Accept: application/vnd.goodgo.v1+json
|
|
```
|
|
|
|
## Quick Reference
|
|
|
|
| Strategy | Pros | Cons | Use When |
|
|
|----------|------|------|----------|
|
|
| **URL Path** | Clear, cacheable | URL changes | Public APIs |
|
|
| **Header** | Clean URLs | Less visible | Internal APIs |
|
|
| **Query Param** | Simple | Not RESTful | Quick prototypes |
|
|
|
|
**Semantic Versioning:**
|
|
```
|
|
MAJOR.MINOR.PATCH
|
|
│ │ └── Bug fixes (backward compatible)
|
|
│ └──────── New features (backward compatible)
|
|
└────────────── Breaking changes
|
|
```
|
|
|
|
**Version Lifecycle:**
|
|
```
|
|
v1 Active → v2 Released → v1 Deprecated → v1 Sunset → v1 Removed
|
|
│ │ │ │ │
|
|
│ │ Add headers Remove from Delete
|
|
│ Support + warnings docs routes
|
|
Solo both
|
|
```
|
|
|
|
**Deprecation Headers:**
|
|
```http
|
|
Deprecation: true
|
|
Sunset: Sat, 31 Dec 2024 23:59:59 GMT
|
|
Warning: 299 - "API v1 is deprecated. Migrate to v2 by 2024-12-31"
|
|
Link: </api/v2/users>; rel="successor-version"
|
|
```
|
|
|
|
## Resources
|
|
|
|
- [API Design](./api-design.md) - API design patterns
|
|
- [Middleware Patterns](./middleware-patterns.md) - Middleware patterns
|
|
- [Project Rules](./project-rules.md) - GoodGo standards
|
|
- Skill Source: `.cursor/skills/api-versioning-strategy/SKILL.md`
|