İçeriğe atla

2025-12-22

API Versioning Stratejileri: İlk Release'den Sunset'a Kadar

URL ve header versioning yaklaşımları, breaking change yönetimi, Sunset header'ları ile deprecation, AWS API Gateway pattern'leri, GraphQL schema evolution ve consumer-driven contract testing'i kapsayan kapsamlı bir API versioning rehberi.

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 bir sözdür ve bunun arkasındaki strateji (URL yolu, header, content negotiation ya da sürümlenmiş kaynak grafı) istemci migrasyonlarının ne kadar maliyetli olacağını, kullanımdan kaldırılmış yüzeyin ne kadar süre deploy’da kalacağını ve geçişler sırasında altyapının ne kadar ikiye katlanacağını belirler. Çoğu API versioning yeniden yazımı, /v2/ ile v2. arasında seçim yapmakla değil, ekibin sözleşmeyi örtük olarak iletiyor olduğunu fark edip onu açık hale getirmekle ilgilidir.

Özet

API versioning, backward compatibility ile forward progress arasında denge kurmayı gerektirir. Bu rehber, URL path ve header versioning karşılaştırması, OpenAPI diff tool’ları ile breaking change yönetimi, RFC 8594 deprecation header’larının implementasyonu, Lambda alias’ları ile AWS API Gateway versioning pattern’leri, traditional versioning olmadan GraphQL schema evolution, Pact ile consumer-driven contract testing ve koordineli migration pattern’lerini kapsar. Yaklaşım, continuous API improvement’ı sağlarken disruption’ı minimize etmek için gradual rollout’lar, comprehensive monitoring ve clear communication’a odaklanır.

Teknik Challenge

API evolution birkaç birbirine bağlı problem yaratır:

Breaking Change Management: Bir field’ı name’den fullName’e rename ettiğinde, name bekleyen mevcut client’lar fail olur. Soru breaking change’lerin yapılıp yapılmaması değil; production incident’larına neden olmadan nasıl yapılacağıdır.

Version Proliferation: Sunset policy olmadığı için altı concurrent API version’ı destekleyen takımlar gördüm. Her version testing matrix’ini, security patch burden’ını ve infrastructure maliyetlerini katlar. Engineering time hızla birikiyor.

Migration Coordination: API’nize 50 farklı client depend ettiğinde, zero-downtime migration’ları coordinate etmek complex hale geliyor. Bazı client’lar hemen update oluyor, diğerleri aylar alıyor. Her ikisini de accommodate eden 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.

Versioning Stratejini Seçmek

Üç ana yaklaşım var, her birinin spesifik trade-off’ları mevcut:

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

Evet

Hayir

Evet

Hayir

Evet

Hayir

Evet

Hayir

API Versioning Stratejisi Sec

Public API?

Cok Sayida

External Consumer?

Internal/Partner API

URL Path Versioning

/api/v1/resource

Medium Scale API

Sik Breaking

Change?

Header Versioning

API-Version: 2

Ayni Takim Consumer

ve Provider Control Eder?

GraphQL

Schema Evolution Dusun

Cache-friendly

Test etmesi kolay

Clear documentation

Flexible

Clean URL'ler

Granular control

Continuous evolution

Field deprecation

Versioning gereksiz

Seçim specific context’ine bağlı. External partner’lı public REST API için URL path versioning clarity sağlar. Internal microservice’ler için header versioning flexibility sunar. Tight consumer-provider coupling’li takımlar için GraphQL evolution modeli versioning complexity’sini elimine eder.

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’ı Doğru Implement Etmek

Deprecation bir event değil; bir process’tir. Realistic timeline:

Jan 2024Apr 2024Jul 2024Oct 2024Jan 2025Apr 2025Jul 2025Oct 2025Jan 2026Apr 2026Jul 2026Oct 2026Active & Destekleniyor Development Beta Testing General Availability Migration Guide Yayinlandi Deprecated ama Fonksiyonel Deprecation Notice Gonderildi Son Hatirlatma (3 ay) Son Hatirlatma (1 ay) Sunset & Devre Disi V1 Kapatma V1 APIV2 APICommunicationAPI Version Deprecation Timeline

Timeline’ı programmatically communicate etmek için RFC 8594 deprecation header’larını implement et:

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();
});

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.

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;
};

Bu yaklaşım header-based version selection’ı desteklerken URL’leri clean tutar.

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: '[email protected]',
            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:

DatabaseAPI V2API V1Load BalancerClientDatabaseAPI V2API V1Load BalancerClientHafta 1-2: V2'ye %10 trafficHafta 3-4: V2'ye %50 trafficHafta 5-6: V2'ye %90 trafficHafta 7: V2'ye %100 trafficV1'i decommission etAPI Request%90 trafficQueryDataResponse%10 trafficQueryDataResponse

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');
  }
}

Bu, risk olmadan production load altında V2 behavior’ını validate eder.

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);
});

Version Kullanımını Monitor Etmek

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.

Önemli Çıkarımlar

  1. Audience’a göre versioning seç: Public API’ler için URL path, internal için header’lar, rapid iteration için GraphQL evolution.

  2. Breaking change detection’ı automate et: CI/CD’deki OpenAPI diff tool’ları accidental breaking change’leri önler.

  3. Deprecation 12+ ay gerektirir: Multi-channel communication (header’lar, email, doc’lar), usage monitoring, hard shutdown yerine gradual throttling.

  4. Concurrent version’ları sınırla: Maksimum 3 active version technical debt explosion’ını önler.

  5. Gradual rollout kullan: Feature flag’lerle 10% → 25% → 50% → 100% traffic splitting migration risk’ini azaltır.

  6. Contract testing break’leri önler: Pact, consumer’lar ve provider’lar arasındaki incompatibility’leri production’dan önce yakalar.

  7. GraphQL versioning complexity’sini elimine eder: Field-level deprecation version explosion olmadan smooth migration sağlar.

  8. Version usage’ı continuously monitor et: Deprecated version’ları kullanan client’ları track et, straggler’ları erken identify et.

Çalışan versioning stratejisi specific context’ine bağlı; public vs internal API, consumer sayısı, change frequency. Requirement’larını karşılayan en basit yaklaşımla başla, sadece gerektiğinde complexity ekle.

İlgili yazılar

Çok Kanallı İçerik Yönetimi: Headless CMS Dünyasında Yol Haritası

Headless CMS çözümlerinin pratik karşılaştırması - Strapi, Contentful, Kontent ve Storyblok - Cloudinary ile görsel yönetimi ve web ile mobil uygulamalar için framework entegrasyon pattern'leri.

typescriptnextjsreact-native+7
MCP Katmanını Atla: AI Agentleri için Kapsamlı API Erişimi

Production takımlarının geniş MCP erişimini neden scoped API proxy'leriyle değiştirdiğini anlatan rehber. Atlassian (Jira/Confluence), Google Workspace ve Notion örnekleriyle FastAPI proxy, CLI wrapper ve n8n workflow'ları.

mcpapi-designpython+5
Pact ile Contract Testing - Microservislerde API Uyumluluğunu Sağlama

TypeScript microservislerde consumer-driven contract testing'i Pact ile uygulamaya yönelik pratik bir kılavuz. Breaking API değişikliklerini deployment öncesi yakalayın ve integration test yükünü azaltın.

testingmicroservicesapi+7
Kafka mı, Event Bus mı? SNS/SQS/EventBridge'i Aşmanız Gerektiğini Söyleyen Sinyaller

Yönetilen bir event bus'tan Kafka'ya geçişi hak eden sinyaller ve rip-and-replace yapmadan taşımak için outbox tabanlı dört aşamalı geçiş planı.

kafkaevent-drivenaws+4
Web ve Mobil için Asenkron API Desenleri: Görüşlü Bir Varsayılan

Tek bir backend üzerinde çalışan web SPA ve mobil uygulama için uzun süreli işlere dair tek bir varsayılan desen ve onu geçersiz kılmanız gereken durumlar.

api-designwebsocketsserver-sent-events+5