API Versioning Stratejileri: İlk Release'den Sunset'a Kadar
Pratik bir API versioning rehberi: URL ve header yaklaşımları, breaking change'ler, Sunset header'larıyla deprecation, AWS API Gateway, GraphQL ve contract testing.
API versioning bir URL kuralı değil, bir sözleşme yönetimi problemidir. Bir sürüm, belirli bir istemci kümesine belirli bir kırıcı değişiklik kümesi hakkında verilen sözdür. Bu sözün arkasındaki strateji (URL yolu, header, content negotiation ya da sürümlenmiş kaynak grafı) her istemci migrasyonunun maliyetini, kullanımdan kaldırılan yüzeyin ne kadar süre deploy’da kalacağını ve geçiş boyunca altyapının ne kadar ikiye katlanacağını belirler. Çoğu versioning yeniden yazımı, ekibin sözleşmeyi örtük olarak ilettiğini fark ettiği anda başlar; asıl iş, o sözleşmeyi açık hale getirmektir.
Başlangıç için varsayılan şu: dış tüketicisi olan her API’de sürümü URL yoluna koy, bir major sürüm içinde değişiklikleri additive tut ve sunset tarihini aylar öncesinden duyur. Header versioning, GraphQL schema evolution ve adapter katmanları bu varsayılanın bilinçli istisnaları; her biri getirdiği ek karmaşıklığı ancak dar bir koşul kümesinde hak eder.
API Evriminin Yarattığı Dört Problem#
Dördü birlikte gelir ve bir versioning stratejisinin hepsine cevap vermesi gerekir:
Breaking Change Management: Bir field’ı name’den fullName’e rename ettiğinde, name bekleyen mevcut client’lar fail olur. Uzun ömürlü bir API’de breaking change kaçınılmazdır; asıl mühendislik işi, bunu production incident’ı çıkarmadan yapmaktır.
Version Proliferation: Sunset policy olmayan takımlar altı veya daha fazla concurrent API version’ı desteklemek zorunda kalıyor. Her version testing matrix’ini, security patch burden’ını ve infrastructure maliyetlerini katlar. Engineering time hızla birikiyor.
Migration Coordination: 50 farklı client API’nize bağlıyken zero-downtime migration’ları koordine etmek zorlaşır. Bazı client’lar hemen güncellenir, bazıları aylar alır. İkisini birden kaldıran bir stratejiye ihtiyacın var.
Documentation Synchronization: Birden fazla API version’ında OpenAPI spec’leri, SDK version’larını ve documentation’ı maintain etmek, birçok versioning stratejisinin başarısız olduğu nokta. Doc’lar gerçeklikten uzaklaşıyor, integration confusion’a neden oluyor.
Strateji Seçimi#
Üç yaklaşım öne çıkıyor ve her birinin kendi trade-off’ları var: sürümü URL yoluna koymak, request header’ına koymak ya da (GraphQL ile) hiç sürümlemeyip şemayı sürekli evolve etmek.
URL Path Versioning#
// Version URL path'ine gömülü
app.get('/api/v1/users/:id', async (req, res) => {
const user = await db.users.findById(req.params.id);
res.json({
id: user.id,
name: user.name,
email: user.email
});
});
app.get('/api/v2/users/:id', async (req, res) => {
const user = await db.users.findById(req.params.id);
res.json({
id: user.id,
fullName: user.name, // Field rename edildi
contactInfo: {
email: user.email,
phone: user.phone
}
});
});
Avantajlar: URL’lerde explicit versioning, browser’da test etmesi straightforward, excellent CDN cache efficiency (farklı URL’ler = ayrı cache key’ler).
Dezavantajlar: URL change’leri bookmark’ları ve hardcoded client’ları bozar, her version için routing configuration gerekir.
Ne zaman kullanılır: Clarity’nin URL aesthetic’ten daha önemli olduğu, multiple major version’lı public API’ler için. Twitter, Stripe ve GitHub (historically) bu yaklaşımı kullanır.
Header-Based Versioning#
// Version request header'ından belirlenir
app.use((req, res, next) => {
const apiVersion = req.headers['api-version'] ||
req.headers['accept-version'] ||
'1'; // default version
req.apiVersion = apiVersion;
res.setHeader('API-Version', apiVersion);
next();
});
app.get('/api/users/:id', async (req, res) => {
const user = await db.users.findById(req.params.id);
if (req.apiVersion === '2') {
return res.json(transformToV2(user));
}
res.json(transformToV1(user));
});
Avantajlar: Değişmeyen clean URL’ler, granular version control, content negotiation pattern’lerini destekler.
Dezavantajlar: API client’lar olmadan test etmesi daha zor, log’larda header’ları specifically log etmediğin sürece version invisible, cache configuration Vary header setup gerektirir.
Ne zaman kullanılır: Internal API’ler, frequent minor update’li API’ler, URL stability’nin önemli olduğu sistemler için. GitHub (current approach) ve Microsoft Graph API bu pattern’i kullanır.
Decision Framework#
Tüketiciler kurumun dışındaysa varsayılan URL path versioning’dir; ek routing konfigürasyonu, sağladığı netliğe değer. Header versioning asıl yerini internal microservice’lerde bulur: orada URL, zaten kontrol ettiğin servisler arasındaki sabit sözleşmenin kendisidir. Consumer ve provider’ı aynı takım sahipleniyorsa GraphQL’in evolution modeli versioning sorusunu tamamen masadan kaldırır.
Breaking vs Non-Breaking Change’ler#
Breaking change’in ne olduğunu anlamak accidental production incident’ları önler:
Non-Breaking (Güvenli) Change’ler:
- Yeni endpoint’ler eklemek
- Optional request parameter’lar eklemek
- Response’lara yeni field’lar eklemek (existing client’lar ignore eder)
- Mevcut code’ları valid tutarken yeni response status code’ları eklemek
- Validation rule’larını relax etmek (daha fazla input format kabul etmek)
Breaking Change’ler (Yeni Version Gerektirir):
- Endpoint’leri remove veya rename etmek
- Request/response field’larını remove veya rename etmek
- Field data type’larını değiştirmek (string’den number’a)
- Required request parameter eklemek
- Authentication mechanism’larını değiştirmek
- Error response structure’larını modify etmek
- HTTP method’ları değiştirmek (GET’ten POST’a)
Practice’te breaking olmadan evolution şöyle görünür:
// Original API (v1) - değişmeden kalır
interface UserV1 {
id: string;
name: string;
email: string;
}
// Evolved API (v2) - sadece additive change'ler
interface UserV2 {
id: string;
name: string; // compatibility için tutuldu
email: string; // compatibility için tutuldu
// Yeni optional field'lar
phoneNumber?: string;
avatar?: string;
preferences?: UserPreferences;
}
// V2 data'yı gerektiğinde v1 format'a transform et
function toV1Format(user: UserV2): UserV1 {
return {
id: user.id,
name: user.name,
email: user.email
};
}
Automated detection hataları önler. CI/CD pipeline’larda:
import { diff } from 'openapi-diff';
async function detectBreakingChanges(
oldSpecPath: string,
newSpecPath: string
): Promise<void> {
const result = await diff(oldSpecPath, newSpecPath);
if (result.breakingDifferencesFound) {
console.error('Breaking change tespit edildi:');
result.breakingDifferences.forEach(change => {
console.error(`- ${change.type}: ${change.action}`);
console.error(` Path: ${change.path}`);
});
process.exit(1); // Build'i fail et
}
}
Deprecation Uygulaması#
Deprecation aylara yayılan bir süreçtir. Gerçekçi bir takvim şöyle görünür:
Bu takvimi programatik olarak iletmek için Sunset header’ını (RFC 8594) ve Deprecation header’ını (RFC 9745) kullan:
interface DeprecationConfig {
version: string;
deprecationDate: Date;
sunsetDate: Date;
migrationGuideUrl: string;
}
const v1Config: DeprecationConfig = {
version: '1',
deprecationDate: new Date('2025-07-01'),
sunsetDate: new Date('2026-01-01'),
migrationGuideUrl: 'https://docs.example.com/api/v1-to-v2-migration'
};
function addDeprecationHeaders(
res: Response,
config: DeprecationConfig
): void {
const now = new Date();
// RFC 9745 Deprecation header
if (now >= config.deprecationDate) {
res.setHeader('Deprecation', '@' + Math.floor(config.deprecationDate.getTime() / 1000));
}
// RFC 8594 Sunset header
res.setHeader('Sunset', config.sunsetDate.toUTCString());
// Migration documentation'a link
res.setHeader('Link',
`<${config.migrationGuideUrl}>; rel="deprecation"; type="text/html"`
);
// Kalan günlerle warning header
const daysUntilSunset = Math.floor(
(config.sunsetDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)
);
res.setHeader('Warning',
`299 - "API version ${config.version} ${daysUntilSunset} gün içinde sunset olacak. ` +
`Lütfen v2'ye migrate edin. Bakın: ${config.migrationGuideUrl}"`
);
}
app.use('/api/v1/*', (req, res, next) => {
addDeprecationHeaders(res, v1Config);
next();
});
Snippet’te bir uyarı var: RFC 9111 Warning alanını kullanımdan kaldırdı. Makinenin okuyacağı sinyal olarak Sunset ve Link’e güven, Warning’i de hâlâ parse eden client’lar için nezaketen bırak.
Client SDK’lar deprecation’ı detect edip warn etmeli:
class ApiClient {
private checkDeprecationHeaders(response: Response): void {
const deprecation = response.headers.get('Deprecation');
const sunset = response.headers.get('Sunset');
if (deprecation) {
const sunsetDate = sunset ? new Date(sunset) : null;
const daysUntilSunset = sunsetDate
? Math.floor((sunsetDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24))
: null;
console.warn(
`[API Deprecation Uyarısı] Bu endpoint deprecated.`,
sunsetDate ? `Sunset: ${sunsetDate.toISOString()}` : '',
daysUntilSunset !== null ? `Kalan gün: ${daysUntilSunset}` : ''
);
// Monitoring'e report et
this.reportDeprecationMetric({
endpoint: response.url,
daysUntilSunset
});
}
}
}
Hard shutdown yerine gradual throttling migration panic’ini azaltır:
function getVersionThrottleLimit(version: string, sunsetDate: Date): number {
const daysUntilSunset = Math.floor(
(sunsetDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24)
);
if (daysUntilSunset > 30) {
return 10000; // Normal rate limit
} else if (daysUntilSunset > 7) {
return 1000; // Reduced rate
} else if (daysUntilSunset > 0) {
return 100; // Severe throttling
} else {
return 0; // Sunset geçti
}
}
AWS API Gateway Versioning Pattern’leri#
AWS API Gateway birkaç versioning yaklaşımı sunar. Production’da çalışanlar:
Custom Domain ile Base Path Mapping#
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as acm from 'aws-cdk-lib/aws-certificatemanager';
import * as cdk from 'aws-cdk-lib';
export class ApiVersioningStack extends cdk.Stack {
constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// Versioning'li Lambda function
const userServiceFn = new lambda.Function(this, 'UserService', {
runtime: lambda.Runtime.NODEJS_20_X,
handler: 'index.handler',
code: lambda.Code.fromAsset('lambda'),
});
// Version 1'e point eden v1 alias
const v1Alias = new lambda.Alias(this, 'UserServiceV1', {
aliasName: 'v1',
version: userServiceFn.currentVersion,
});
// Version 2'ye point eden v2 alias
const v2Alias = new lambda.Alias(this, 'UserServiceV2', {
aliasName: 'v2',
version: userServiceFn.currentVersion,
});
// V1 API Gateway
const apiV1 = new apigateway.RestApi(this, 'UserApiV1', {
restApiName: 'User Service V1',
deployOptions: { stageName: 'prod' },
});
const v1Users = apiV1.root.addResource('users');
const v1User = v1Users.addResource('{id}');
v1User.addMethod('GET', new apigateway.LambdaIntegration(v1Alias));
// V2 API Gateway
const apiV2 = new apigateway.RestApi(this, 'UserApiV2', {
restApiName: 'User Service V2',
deployOptions: { stageName: 'prod' },
});
const v2Users = apiV2.root.addResource('users');
const v2User = v2Users.addResource('{id}');
v2User.addMethod('GET', new apigateway.LambdaIntegration(v2Alias));
// Path mapping'li custom domain
const domain = new apigateway.DomainName(this, 'CustomDomain', {
domainName: 'api.example.com',
certificate: acm.Certificate.fromCertificateArn(
this,
'Certificate',
'arn:aws:acm:us-east-1:123456789012:certificate/abc123'
),
});
// /v1/* V1 API'ye, /v2/* V2 API'ye map et
new apigateway.BasePathMapping(this, 'V1Mapping', {
domainName: domain,
restApi: apiV1,
basePath: 'v1',
});
new apigateway.BasePathMapping(this, 'V2Mapping', {
domainName: domain,
restApi: apiV2,
basePath: 'v2',
});
}
}
Bu, version’lar arasında complete isolation ile https://api.example.com/v1/users/123 ve https://api.example.com/v2/users/123 gibi clean URL’ler oluşturur. Snippet kısa kalsın diye iki alias da currentVersion’ı gösteriyor; gerçek bir deploy’da v1, v1 sözleşmesini yayınlayan sürüme sabitlenir, v2 ilerlemeye devam eder.
CloudFront ile Header-Based Routing#
Header versioning için Lambda@Edge request’leri route eder:
import { CloudFrontRequestEvent } from 'aws-lambda';
export const handler = async (event: CloudFrontRequestEvent) => {
const request = event.Records[0].cf.request;
const headers = request.headers;
const apiVersion = headers['api-version']?.[0]?.value || '1';
// Version'a göre uygun origin'e route et
if (apiVersion === '2') {
request.origin = {
custom: {
domainName: 'api-v2.internal.example.com',
port: 443,
protocol: 'https',
path: '',
sslProtocols: ['TLSv1.2'],
readTimeout: 30,
keepaliveTimeout: 5,
customHeaders: {}
}
};
} else {
request.origin = {
custom: {
domainName: 'api-v1.internal.example.com',
port: 443,
protocol: 'https',
path: '',
sslProtocols: ['TLSv1.2'],
readTimeout: 30,
keepaliveTimeout: 5,
customHeaders: {}
}
};
}
return request;
};
URL’ler temiz kalır, sürüm seçimi de request origin’e ulaşmadan edge’de yapılır.
GraphQL Schema Evolution#
GraphQL’in felsefesi REST versioning’den farklı. Tüm API’yi version’lamak yerine, field deprecation kullanarak schema’yı continuously evolve ediyorsun:
type User {
id: ID!
# Original field - asla remove edilmez, ama deprecated
name: String! @deprecated(reason: "Bunun yerine firstName ve lastName kullan")
email: String!
# Mevcut query'leri bozmadan eklenen yeni field'lar
firstName: String
lastName: String
# Clear migration path'li deprecated field
phone: String @deprecated(reason: "Bunun yerine contactInfo.phoneNumber kullan")
# Yeni structured contact information
contactInfo: ContactInfo
}
type ContactInfo {
email: String!
phoneNumber: String
address: Address
}
name ve phone query eden client’lar çalışmaya devam eder. Yeni client’lar firstName, lastName ve contactInfo query eder. GraphQL introspection API deprecation warning’lerini gösterir.
Field-level version tracking için custom directive’ler yardımcı olur:
directive @version(
added: String!
deprecated: String
removed: String
) on FIELD_DEFINITION
type User {
id: ID!
name: String! @version(added: "1.0")
email: String! @version(added: "1.0")
phoneNumber: String @version(added: "2.0")
# 3.0'da deprecated, 4.0'da removed
legacyAddress: String @version(
added: "1.0"
deprecated: "3.0"
removed: "4.0"
)
address: Address @version(added: "3.0")
}
Resolver’lar deprecated field kullanımını track edebilir:
const resolvers = {
User: {
legacyAddress: (parent, args, context) => {
const clientVersion = context.apiVersion || '1.0';
if (semver.gte(clientVersion, '3.0')) {
context.metrics.incrementDeprecatedFieldUsage('User.legacyAddress');
}
return parent.address?.fullAddress || '';
}
}
};
Consumer-Driven Contract Testing#
Contract testing consumer’lar ve provider’lar arasında version compatibility’yi ensure eder. Pact en established tool:
// Consumer test (Frontend team)
// Not: Pact V2 API kullanılıyor. V3+ için `PactV3` ve farklı lifecycle method'ları kullan.
const { Pact } = require('@pact-foundation/pact');
describe('User Service V2', () => {
const provider = new Pact({
consumer: 'UserWebApp',
provider: 'UserServiceV2',
port: 8080
});
beforeAll(() => provider.setup());
afterEach(() => provider.verify());
afterAll(() => provider.finalize());
it('id ile user alabilmeli', async () => {
await provider.addInteraction({
state: 'user mevcut',
uponReceiving: 'id 123 olan user için request',
withRequest: {
method: 'GET',
path: '/api/v2/users/123',
headers: { 'Accept': 'application/json' }
},
willRespondWith: {
status: 200,
headers: {
'Content-Type': 'application/json',
'API-Version': '2'
},
body: {
id: 123,
fullName: 'John Doe',
contactInfo: {
email: 'john@example.com',
phone: '+1234567890'
}
}
}
});
const user = await getUserById(123);
expect(user.fullName).toBe('John Doe');
});
});
Backend team tüm consumer contract’ları verify eder:
const { Verifier } = require('@pact-foundation/pact');
describe('User Service Provider', () => {
it('tüm consumer contract'ları validate etmeli', async () => {
await new Verifier({
provider: 'UserServiceV2',
providerBaseUrl: 'http://localhost:3000',
pactBrokerUrl: 'https://pact-broker.example.com',
publishVerificationResult: true,
providerVersion: '2.0.0',
providerVersionTags: ['prod']
}).verifyProvider();
});
});
Bu, breaking change’leri production’a ulaşmadan yakalar. Frontend fullName beklediğinde ama backend name döndürdüğünde, contract test provider verification’da fail olur.
Migration Pattern’leri#
Traffic Splitting ile Parallel Run#
Gradual rollout risk’i azaltır:
Feature flag’lerle implement et:
import LaunchDarkly from 'launchdarkly-node-server-sdk';
const ldClient = LaunchDarkly.init(process.env.LAUNCHDARKLY_SDK_KEY);
app.get('/api/users/:id', async (req, res) => {
const user = {
key: req.user?.id || 'anonymous',
email: req.user?.email,
custom: {
apiClient: req.headers['user-agent']
}
};
const useV2 = await ldClient.variation('api-v2-rollout', user, false);
if (useV2) {
return handleGetUserV2(req, res);
} else {
return handleGetUserV1(req, res);
}
});
LaunchDarkly dashboard’u ile percentage’ı gradually artır: 10% → 25% → 50% → 75% → 100% birkaç hafta boyunca.
Shadow Mode Testing#
Response’ları etkilemeden yeni version’ı test et:
app.get('/api/users/:id', async (req, res) => {
// V1'e primary request (production)
const v1Promise = handleGetUserV1(req);
// V2'ye shadow request (testing)
const v2Promise = handleGetUserV2(req).catch(err => {
logger.error('V2 shadow request başarısız', { error: err });
return null;
});
// V1 response'ını bekle
const v1Result = await v1Promise;
// Result'ları asynchronously karşılaştır
v2Promise.then(v2Result => {
if (v2Result) {
compareResponses(v1Result, v2Result, req.params.id);
}
});
// V1 response'ını döndür
res.json(v1Result);
});
function compareResponses(v1: any, v2: any, userId: string): void {
const differences = deepDiff(v1, v2);
if (differences.length > 0) {
logger.warn('V1/V2 response uyumsuzluğu', {
userId,
differences
});
metrics.increment('api.v2.response_mismatch');
}
}
V2, client’ın gördüğü response’a dokunmadan production yükü altında çalıştırılmış olur.
Backward Compatibility için Adapter Pattern#
Her iki version’ı destekleyen unified endpoint:
interface UserV1Response {
id: string;
name: string;
email: string;
}
interface UserV2Response {
id: string;
fullName: string;
contactInfo: {
email: string;
phoneNumber?: string;
};
}
class UserResponseAdapter {
static toV1(v2User: UserV2Response): UserV1Response {
return {
id: v2User.id,
name: v2User.fullName,
email: v2User.contactInfo.email
};
}
}
app.get('/api/users/:id', async (req, res) => {
const requestedVersion = req.headers['api-version'] || '1';
// Her zaman full V2 data fetch et
const user = await db.users.findById(req.params.id);
if (requestedVersion === '1') {
res.setHeader('API-Version', '1');
res.setHeader('Deprecation', 'true');
return res.json(UserResponseAdapter.toV1(user));
}
res.setHeader('API-Version', '2');
return res.json(user);
});
Sürüm Kullanımını İzleme#
Hangi client’ların hangi version’ları kullandığını track et:
interface VersionMetrics {
version: string;
totalRequests: number;
uniqueClients: number;
errorRate: number;
avgLatency: number;
}
// CloudWatch custom metric'ler
const cloudwatch = new AWS.CloudWatch();
function trackVersionUsage(version: string, clientId: string): void {
cloudwatch.putMetricData({
Namespace: 'API/Versioning',
MetricData: [{
MetricName: 'RequestCount',
Dimensions: [
{ Name: 'Version', Value: version },
{ Name: 'ClientId', Value: clientId }
],
Value: 1,
Unit: 'Count',
Timestamp: new Date()
}]
});
}
Migration progress report’ları generate et:
class MigrationTracker {
async getClientMigrationStatus(): Promise<ClientMigrationReport[]> {
const clients = await this.getAllClients();
return clients.map(client => ({
clientId: client.id,
clientName: client.name,
currentVersion: client.apiVersion,
targetVersion: '2',
lastRequestDate: client.lastSeen,
requestCount7d: client.requests7d,
migrationStatus: this.getMigrationStatus(client)
}));
}
private getMigrationStatus(client: Client): string {
if (client.apiVersion === '2') return 'Tamamlandı';
if (client.lastSeen < subDays(new Date(), 30)) return 'Inactive';
if (client.requests7d > 1000) return 'Yüksek Öncelik';
return 'Beklemede';
}
}
Yaygın Hatalar#
Yetersiz Deprecation Notice: Shutdown’dan sadece 3 ay önce sunset duyurmak client’ları zor duruma sokar. Public API’ler için minimum 12 ay daha iyi çalışır.
Minor Version’larda Breaking Change: Required field ekleyip 2.0.0 yerine 1.3.0 demek semantic versioning beklentilerini bozar. Bunu yakalamak için CI/CD’de automated OpenAPI diff kullan.
Version Proliferation: 6+ concurrent version desteklemek engineering maliyetlerini katlar. Strict sunset policy yardımcı olur: maksimum 3 version (current + previous + deprecated).
Inconsistent SDK Versioning: API version 1 ile çalışan SDK version 2.3.0 developer’ları şaşırtır. SDK major version’ını API major version’ıyla align et.
Missing Contract Test’ler: Backend değişiklikleri frontend’i bozar çünkü V1 adapter compatibility test edilmemiştir. Pact bunu önler.
Unversioned Error Response’lar: Sadece success response’ları version’larken error format’ı değişirse client error handling bozulur. Error’ları consistently version’la.
Deprecation Monitoring Yok: Major client’ların hala kullandığı V1’i bilmeden kapatmak revenue-impacting outage’lara neden olur. Sunset’tan önce usage’ı track et.
Varsayılan ve İstisnaları#
URL path versioning, bir major sürüm içinde yalnızca additive değişiklik ve aylar öncesinden duyurulan bir sunset tarihi, public ve partner API’lerin çoğunu karşılar. Bu varsayılan, tüketiciler senin kontrolünün dışındayken ve breaking change’ler tek bir major sürüme toplanacak kadar seyrekken geçerlidir. Çağrının iki ucuna da aynı takım sahipse varsayılandan sap; header versioning ya da GraphQL schema evolution, takım dışında kimsenin görmediği URL değişikliklerinden kurtarır. Değişiklikler client’ların sindirebileceğinden hızlı geliyorsa yine sap: GitHub ve Stripe’ın kullandığı tarih tabanlı versioning, maliyeti senin baktığın bir compatibility katmanına taşır. Trafiğin büyük kısmı tek bir consumer’dan geliyorsa da o consumer’la yazılmış bir contract test, yeni bir sürüm numarasından daha fazla güvence verir.
Bu kararların hepsinden önce kendini amorti eden iki şey var: CI’da OpenAPI diff ve sunset tarihinden çok önce birilerinin baktığı bir dashboard’da sürüm bazlı kullanım metrikleri.
Kaynaklar#
- Microsoft REST API Kılavuzları (yeni sekmede açılır) - URL yolu ve sorgu dizisi versiyonlaması dahil yetkili REST API versiyonlama kılavuzları
- GitHub REST API: API Sürümleri (yeni sekmede açılır) - GitHub’ın 24 aylık destek penceresiyle tarih tabanlı başlık versiyonlama yaklaşımı
- Stripe API Versiyonlama (yeni sekmede açılır) - Geriye dönük uyumlu değişikliklerle Stripe’ın tarih tabanlı versiyonlama modeli
- Stripe API Yükseltmeleri (yeni sekmede açılır) - Stripe API sürümleri genelinde yıkıcı ve eklemeli değişikliklerin ayrıntılı değişim günlüğü
- Microsoft Azure REST API Kılavuzları (yeni sekmede açılır) - Versiyonlama, yıkıcı değişiklikler ve kullanımdan kaldırma üzerine Azure’a özgü rehberlik
- RFC 8594: The Sunset HTTP Header Field (yeni sekmede açılır) - Bir kaynağın ne zaman yanıt vermeyi bırakacağını duyuran
Sunsetheader’ının spesifikasyonu - RFC 9745: The Deprecation HTTP Response Header Field (yeni sekmede açılır) -
Deprecationalanının standart tanımı ve@unix-zaman-damgasıdeğer sözdizimi - RFC 9111: HTTP Caching (yeni sekmede açılır) - Güncel HTTP cache spesifikasyonu;
Warningheader alanını kullanımdan kaldıran belge
İlgili yazılar
Acele etmek hızlı hissettirir ama yeniden iş, hata ve yangın söndürme yaratır. Refactor, test ve CI bakımı için durmak neden hız kaybı değil, hıza yatırımdır.
technical-debt · testing · ci-cd +2
Bruno .bru dosyalarını repo'ya commit etmek, API sözleşmesini kodla aynı PR ve geçmişte tutar. Tek gerçek bedel, bilinçli bir secret sınırıdır.
testing · ci-cd · developer-experience +1
Mobil binary geri alınamaz ve eski sürümler kalıcıdır; güvenlik ve hız sunucuya kayar: BFF, tüketici güdümlü sözleşmeler ve geriye dönük uyumlu sürümleme.
mobile · api-design · testing +1
Gelişmiş Amazon Cognito teknik rehberi: özel auth akışları, federation, multi-tenancy, migration stratejileri ve CDK ile production-grade güvenlik.
aws · authentication · serverless +6
Distributed sistemlerde feature flag için production rehberi: LaunchDarkly, Unleash ve AWS AppConfig karşılaştırması, rollout ve A/B testing örnekleri.
feature-flags · devops · ci-cd +5