Skip to content
~/sph.sh

AWS CDK ile API Versiyonlama: Bir Üretim Vaka Çalışması

Üretimde çoklu versiyon API'ler implementasyonu üzerine teknik vaka çalışması. Başarısız yaklaşımlar, çalışan çözümler ve API evolüsyonunu yönetmek için CDK pattern'leri.

Öz

Bu vaka çalışması, AWS CDK kullanarak üretim API versiyonlama sisteminin implementasyonunu inceler. Üç başarısız yaklaşım ve bir çalışan çözümün analizi ile, müşteri uyumluluğunu korurken API evolüsyonunu yönetmek için pratik pattern'leri keşfediyoruz. Nihayetinde geliştirdiğimiz yaklaşım minimal operasyonel overhead ile birden fazla API versiyonunu yönetmek için sağlam pattern'ler sunar.

Problem Tanımı

API evolüsyonu kaçınılmaz bir çelişki yaratır: mevcut müşteriler için geriye dönük uyumluluğu korurken API'yi geliştirme ve değiştirme ihtiyacı. Bu zorluk, müşterilerin değişen güncelleme yetenekleri ve deployment pencerelerine sahip olduğu kurumsal ortamlarda yoğunlaşır.

Burada ele alınan spesifik zorluk şunları içeriyordu:

  • Farklı entegrasyon yeteneklerine sahip birden fazla kurumsal müşteri
  • Değişen deployment döngüleri (haftalık'tan 18 aylık devlet döngülerine)
  • Mevcut entegrasyonları bozmadan API iyileştirmeleri ihtiyacı
  • Birden fazla versiyonu sürdürmek için sınırlı geliştirme kaynakları

Başarısız Yaklaşımlar

Çalışan çözüme ulaşmadan önce üç yaklaşım denendi, her biri farklı teknik ve operasyonel nedenlerle başarısız oldu.

Başarısız Yaklaşım #1: Versiyonlama Stratejisi Yok

İlk yaklaşım tüm müşterilerin aynı anda güncellenebileceğini varsayarak versiyonlama ihtiyacını ortadan kaldırmaya çalıştı.

İmplementasyon: Sürekli güncellemelerle tek API endpoint'i Zaman Çizelgesi: Başlatılmadan başarısızlığa 6 ay Müşteri Büyümesi: 5 başlangıç müşterisi → 50 müşteri

Başarısızlık Noktaları:

  • Hava boşluğunda izole ağlara sahip devlet müşterisi 18 aylık güncelleme döngüleri gerektirdi
  • Güvenlik düzeltmelerinin manuel geri taşınması sürdürülemez hale geldi
  • Gölge API bakımı önemli altyapı karmaşıklığı yarattı
  • Her değişiklik uyumluluk analizi gerektirdiği için geliştirme hızı azaldı

Başarısız Yaklaşım #2: Aşırı Versiyonlama

İkinci yaklaşım API'nin her aspektini bağımsız olarak versiyonlamaya çalıştı.

İmplementasyon: Endpoint'ler, header'lar ve yanıt formatları için ayrı versiyonlama

GET /v2/users?response_version=1.3X-API-Version: 2.1Accept: application/vnd.company.user.v4+json

Başarısızlık Noktaları:

  • 25+ versiyon kombinasyonu üssel test karmaşası yarattı
  • Geliştirici bilişsel yükü sürdürülemez hale geldi
  • Müşteri entegrasyon zorluk seviyesi önemli ölçüde arttı
  • Dokümantasyon bakımı imkansız hale geldi

Başarısız Yaklaşım #3: Akıllı Yönlendirme

Üçüncü yaklaşım istekleri uygun API versiyonlarına otomatik olarak yönlendirmek için müşteri parmak izi kullandı.

İmplementasyon: Müşteri algılama mantığıyla Lambda@Edge fonksiyonu Performans Etkisi: İstek başına +150ms gecikme

Başarısızlık Noktaları:

  • Tek hata noktası tüm API versiyonlarını etkiledi
  • Müşteri algılama mantığı güvenilmez çıktı
  • Performans düşüşü üretim kullanımı için kabul edilemez
  • Minimal fayda için yüksek operasyonel karmaşa

Çalışan Çözüm: Yaşam Döngüsü Yönetimli Yol Tabanlı Versiyonlama

Başarılı yaklaşım, yol tabanlı versiyonlamayı kapsamlı yaşam döngüsü yönetimi ve otomatik kullanımdan kaldırma uyarılarıyla birleştirir.

typescript
// lib/config/api-versions.tsexport interface ApiVersion {  version: string;  status: 'alpha' | 'beta' | 'stable' | 'deprecated' | 'sunset';  launchedAt: Date;  deprecatedAt?: Date;  sunsetAt?: Date;  monthlyActiveClients?: number;  // Bunu takip edin!  breakingChanges: string[];  supportedFeatures: Set<string>;}
export const API_VERSIONS: Record<string, ApiVersion> = {  v1: {    version: 'v1',    status: 'deprecated',    launchedAt: new Date('2022-01-15'),    deprecatedAt: new Date('2024-01-15'),    sunsetAt: new Date('2025-01-15'),    monthlyActiveClients: 28,  // Legacy devlet müşterileri    breakingChanges: [],    supportedFeatures: new Set(['basic-crud']),  },  v2: {    version: 'v2',    status: 'stable',    launchedAt: new Date('2023-06-01'),    monthlyActiveClients: 156,    breakingChanges: [      'Tüm yanıtlarda userId yerine user_id kullanıldı',      'XML desteği kaldırıldı',      'Email alanı zorunlu hale getirildi',    ],    supportedFeatures: new Set(['basic-crud', 'pagination', 'filtering']),  },  v3: {    version: 'v3',    status: 'beta',    launchedAt: new Date('2024-03-01'),    monthlyActiveClients: 42,    breakingChanges: [      'JSON:API spec\'e geçildi',      'Tüm ID\'ler UUID\'ye değiştirildi',      'Kaynaklar data özelliği altında yuvalandı',    ],    supportedFeatures: new Set([      'basic-crud',      'pagination',      'filtering',      'webhooks',      'graphql',      'batch-operations'    ]),  },};

API'lerimizi Destekleyen CDK Stack'i

İşte üretimde çalışan gerçek CDK kodu. Güzel değil, ama önemli trafiği karşılıyor:

typescript
// lib/stacks/versioned-api-stack.tsimport { RestApi, MethodLoggingLevel, LambdaIntegration } from 'aws-cdk-lib/aws-apigateway';import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';import { Duration, Stack, StackProps } from 'aws-cdk-lib';import { Alarm, Metric } from 'aws-cdk-lib/aws-cloudwatch';import { Construct } from 'constructs';
export class VersionedApiStack extends Stack {  constructor(scope: Construct, id: string, props: StackProps) {    super(scope, id, props);
    const api = new RestApi(this, 'MultiVersionAPI', {      restApiName: 'production-api',      // Bunu zor yoldan öğrendim: CloudWatch'u her zaman etkinleştirin      deployOptions: {        loggingLevel: MethodLoggingLevel.INFO,        dataTraceEnabled: true,  // userId olayı sırasında beni kurtardı        metricsEnabled: true,        tracingEnabled: true,      },    });
    // Versiyon kontrol Lambda'sını ekle - bu kritik    const versionCheckFn = new NodejsFunction(this, 'VersionCheck', {      entry: 'src/middleware/version-check.ts',      memorySize: 256,  // Fazla gerek yok      timeout: Duration.seconds(3),      environment: {        VERSIONS: JSON.stringify(API_VERSIONS),        SLACK_WEBHOOK: process.env.SLACK_WEBHOOK!,  // Kullanımdan kaldırılan versiyon kullanımında uyar      },    });
    // Her versiyonu ayarla    Object.entries(API_VERSIONS).forEach(([version, config]) => {      if (config.status === 'sunset') return;  // Sunset versiyonlarını dağıtma
      const versionResource = api.root.addResource(version);      this.setupVersionEndpoints(versionResource, config);    });
    // Kritik: versiyon keşif endpoint'i    this.addVersionDiscovery(api);
    // v1 sunset sırasında bizi kurtaran alarm    new Alarm(this, 'DeprecatedVersionHighUsage', {      metric: new Metric({        namespace: 'API/Versions',        metricName: 'DeprecatedVersionCalls',        statistic: 'Sum',      }),      threshold: 1000,      evaluationPeriods: 1,    });  }
  private setupVersionEndpoints(resource: IResource, config: ApiVersion) {    // Gerçek konuşalım: versiyonlar arasında 47 Lambda fonksiyonumuz var    // Zarif değil, ama yönetilebilir
    const handlers = new Map<string, Function>();
    // Kullanıcı endpoint'leri - çoğu kırıcı değişikliğin kaynağı    const usersResource = resource.addResource('users');
    const listUsersHandler = new NodejsFunction(this, `ListUsers-${config.version}`, {      entry: `src/handlers/${config.version}/users/list.ts`,      memorySize: config.version === 'v1' ? 512 : 1024,  // V1 verimsiz      timeout: Duration.seconds(29),  // API Gateway maximum timeout (REST API'ler için 29 saniyeye kadar)      environment: {        TABLE_NAME: process.env.USERS_TABLE!,        VERSION: config.version,        FEATURES: [...config.supportedFeatures].join(','),        // Bu sayısız kez hata ayıklama zamanı kazandırdı        DEPLOYMENT_TIME: new Date().toISOString(),      },      bundling: {        // Versiyona özel bağımlılıklar        externalModules: [          '@aws-sdk/client-dynamodb',  // AWS SDK v3 for Node.js 18+ runtime          '@aws-sdk/client-cloudwatch',          ...(config.version === 'v1' ? ['xmlbuilder'] : []),  // V1 XML desteği        ],      },    });
    usersResource.addMethod('GET', new LambdaIntegration(listUsersHandler), {      requestParameters: {        'method.request.querystring.page': config.supportedFeatures.has('pagination'),        'method.request.querystring.limit': config.supportedFeatures.has('pagination'),        'method.request.querystring.filter': config.supportedFeatures.has('filtering'),        // V3 özel parametreler        'method.request.querystring.include': config.version === 'v3',        'method.request.querystring.fields': config.version === 'v3',      },    });
    // Her versiyon çağrısını takip et - bu metrik altın değerinde    listUsersHandler.metricInvocations().createAlarm(this, `HighTraffic-${config.version}`, {      threshold: 10000,      evaluationPeriods: 1,      alarmDescription: `${config.version} üzerinde yüksek trafik - ölçeklendirmeyi kontrol et`,    });  }}

Gerçekten Çalışan Versiyon Handler'ları

İşte tüm kusurlarıyla gerçek kod:

typescript
// src/handlers/v1/users/list.ts// Bu kod 3 yaşında ve belli oluyorexport const handler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {  console.log('V1 handler çağrıldı', {    path: event.path,    clientIp: event.requestContext.identity.sourceIp,    userAgent: event.headers['User-Agent'],  });
  try {    // V1 sayfalamayı desteklemiyor, her şeyi döndürüyor    // Evet, bu korkunç. Hayır, düzeltemeyiz.    const users = await getAllUsers();  // Bu bir keresinde 50K kayıt döndürdü
    // Olaya neden olan alan    const transformedUsers = users.map(u => ({      userId: u.user_id,  // V1 camelCase kullanıyor      userName: u.name,      userEmail: u.email,      createdDate: u.created_at,  // Farklı alan adı çünkü nedenler    }));
    return {      statusCode: 200,      headers: {        'Content-Type': 'application/json',        'X-API-Version': 'v1',        'X-API-Deprecated': 'true',        'X-API-Sunset': '2025-01-15',        'Warning': '299 - "API v1 kullanımdan kaldırıldı. Lütfen v2\'ye geçin. Kılavuzlar: https://docs.api.com/migration"',        // Bir bankacılık müşterisi için eklemek zorunda kaldım        'X-Total-Count': transformedUsers.length.toString(),      },      body: JSON.stringify(transformedUsers),    };  } catch (error) {    // 6 saat prod'da hata ayıkladıktan sonra her şeyi loglamamayı öğrendim    console.error('V1 handler hatası', {      error,      stack: error.stack,      event: JSON.stringify(event),    });
    return {      statusCode: 500,      body: JSON.stringify({        error: 'Internal Server Error',        // V1 müşterileri tam olarak bu formatı bekliyor        errorCode: 'INTERNAL_ERROR',        timestamp: new Date().toISOString(),      }),    };  }};
// src/handlers/v2/users/list.tsexport const handler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {  // V2 50K olayından sonra uygun sayfalama ekledi  const page = parseInt(event.queryStringParameters?.page || '1');  const limit = Math.min(    parseInt(event.queryStringParameters?.limit || '20'),    100  // Birisi limit=10000 istedikten sonra sabit limit  );
  const metrics = {    version: 'v2',    page,    limit,    clientIp: event.requestContext.identity.sourceIp,  };
  // Kullanımdan kaldırılan versiyon kullanımını takip et  if (event.headers['User-Agent']?.includes('OldSDK/1.')) {    await cloudwatch.putMetricData({      Namespace: 'API/Clients',      MetricData: [{        MetricName: 'OutdatedSDKUsage',        Value: 1,        Dimensions: [{ Name: 'Version', Value: 'v2' }],      }],    }).promise();  }
  try {    const { users, total } = await getUsersPaginated({ page, limit });
    // V2 yanıt formatı - beni rahatsız eden tutarsızlığa dikkat edin    const response = {      data: users.map(u => ({        id: u.user_id,  // userId'den değişti        name: u.name,        email: u.email,        status: u.status || 'active',  // Yeni zorunlu alan        created_at: u.created_at,  // Her yerde snake case        updated_at: u.updated_at,      })),      pagination: {        page,        limit,        total,        total_pages: Math.ceil(total / limit),        has_next: page < Math.ceil(total / limit),        has_prev: page > 1,      },      // Müşteriler sayfalamayı anlayamadıktan sonra eklendi      _links: {        self: `/v2/users?page=${page}&limit=${limit}`,        next: page < Math.ceil(total / limit) ? `/v2/users?page=${page + 1}&limit=${limit}` : null,        prev: page > 1 ? `/v2/users?page=${page - 1}&limit=${limit}` : null,      },    };
    return {      statusCode: 200,      headers: {        'Content-Type': 'application/json',        'X-API-Version': 'v2',        'X-RateLimit-Limit': '500',        'X-RateLimit-Remaining': await getRateLimitRemaining(event),        'Cache-Control': 'private, max-age=60',  // Yanlışlıkla önbellekleme olayından sonra eklendi      },      body: JSON.stringify(response),    };  } catch (error) {    logger.error('V2 handler hatası', { error, metrics });    throw error;  // API Gateway halletsin  }};
// src/handlers/v3/users/list.ts// V3: Sonunda doğru yaptığımız yer (çoğunlukla)export const handler = middy(async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {  // V3 JSON:API spec kullanıyor çünkü kurumsal müşteriler talep etti  const params = parseJsonApiParams(event.queryStringParameters);
  // Kademeli dağıtım için özellik bayrakları  const features = await getFeatureFlags('v3', event.headers['X-Client-Id']);
  const { users, total, included } = await getUsersWithRelationships({    ...params,    includeRelationships: params.include,    sparseFields: params.fields,    experimentalFeatures: features,  });
  // JSON:API formatı - sevin ya da nefret edin  const response = {    data: users.map(u => ({      type: 'users',      id: u.id,  // Sonunda her yerde UUID kullanıyoruz      attributes: {        name: u.name,        email: u.email,        status: u.status,        created_at: u.created_at,        updated_at: u.updated_at,      },      relationships: {        organization: {          data: { type: 'organizations', id: u.organization_id },        },        roles: {          data: u.role_ids.map(id => ({ type: 'roles', id })),        },      },      links: {        self: `/v3/users/${u.id}`,      },    })),    included: included,  // İlgili kaynaklar    meta: {      pagination: {        page: params.page.number,        pages: Math.ceil(total / params.page.size),        count: users.length,        total: total,      },      api_version: 'v3',      generated_at: new Date().toISOString(),      experimental_features: [...features],    },    links: generateJsonApiLinks(params, total),  };
  return {    statusCode: 200,    headers: {      'Content-Type': 'application/vnd.api+json',  // JSON:API gereksinimi      'X-API-Version': 'v3',      'X-RateLimit-Limit': '1000',      'X-RateLimit-Remaining': await getRateLimitRemaining(event),      'Vary': 'Accept, X-Client-Id',  // Önbellekleme için önemli    },    body: JSON.stringify(response),  };})  .use(jsonBodyParser())  .use(httpErrorHandler())  .use(correlationIds())  .use(logTimeout())  .use(warmup());

Migrasyon Acı Noktaları ve Çözümler

Bizi Neredeyse Öldüren Veritabanı Migrasyonu

V1'den V2'ye geçerken, userId (string) alanını user_id (UUID) olarak değiştirmemiz gerekiyordu. İşte bunu kesinti olmadan nasıl yaptık:

typescript
// migrations/v1-to-v2-user-ids.tsexport const migrateUserIds = async () => {  const BATCH_SIZE = 100;  let lastEvaluatedKey: any = undefined;  let migrated = 0;  let failed = 0;
  // İlk geçiş: Yeni alan ekle  do {    const { Items, LastEvaluatedKey } = await dynamodb.scan({      TableName: process.env.USERS_TABLE!,      Limit: BATCH_SIZE,      ExclusiveStartKey: lastEvaluatedKey,    }).promise();
    const batch = Items?.map(item => ({      PutRequest: {        Item: {          ...item,          user_id: item.userId || generateUUID(),  // Yeni alan          _migration: 'v1-to-v2-phase1',          _migrated_at: new Date().toISOString(),        },      },    })) || [];
    if (batch.length > 0) {      try {        await dynamodb.batchWrite({          RequestItems: { [process.env.USERS_TABLE!]: batch },        }).promise();        migrated += batch.length;      } catch (error) {        // Logla ama durma - başarısız öğeleri yeniden deneyeceğiz        console.error('Batch başarısız', { error, batch: batch.map(b => b.PutRequest.Item.userId) });        failed += batch.length;      }    }
    lastEvaluatedKey = LastEvaluatedKey;
    // Sıcak partition'lardan kaçınmak için throttle    await new Promise(resolve => setTimeout(resolve, 100));
  } while (lastEvaluatedKey);
  console.log(`Migrasyon tamamlandı: ${migrated} başarılı, ${failed} başarısız`);
  // İkinci geçiş: Eski alanı kaldır (tüm müşteriler güncellendikten sonra)  // Bunun için 6 ay bekledik};

Müşteri SDK Geriye Dönük Uyumluluk

SDK'mız tüm API versiyonlarıyla çalışmak zorundaydı. Bu karmaşık ama gerekli:

typescript
// sdk/src/client.tsexport class ApiClient {  private version: string;  private warned = new Set<string>();
  constructor(options: ClientOptions = {}) {    this.version = options.version || 'v2';  // Varsayılan olarak stable
    if (this.version === 'v1' && !this.warned.has('deprecation')) {      console.warn(        '\x1b[33m%s\x1b[0m',  // Sarı metin        '[KULLANIMDAN KALDIRILMA] API v1 2025-01-15 tarihinde kaldırılacak. ' +        'Migrasyon kılavuzu: https://docs.api.com/migration'      );      this.warned.add('deprecation');
      // SDK versiyon kullanımını takip et      this.trackEvent('sdk_deprecation_warning', { version: 'v1' });    }  }
  async getUsers(options?: GetUsersOptions) {    const url = this.buildUrl('users', options);    const response = await this.request(url);
    // Versiyonlar arasında yanıtları normalize et    return this.normalizeUserResponse(response);  }
  private normalizeUserResponse(response: any): User[] {    switch (this.version) {      case 'v1':        // V1 düz dizi döndürür        return response.map((u: any) => ({          id: u.userId,          name: u.userName,          email: u.userEmail,          createdAt: new Date(u.createdDate),          // V1'de bunlar yok          status: 'active',          updatedAt: new Date(u.createdDate),        }));
      case 'v2':        // V2 sayfalı yanıt döndürür        return response.data.map((u: any) => ({          id: u.id,          name: u.name,          email: u.email,          status: u.status,          createdAt: new Date(u.created_at),          updatedAt: new Date(u.updated_at),        }));
      case 'v3':        // V3 JSON:API formatı döndürür        return response.data.map((u: any) => ({          id: u.id,          name: u.attributes.name,          email: u.attributes.email,          status: u.attributes.status,          createdAt: new Date(u.attributes.created_at),          updatedAt: new Date(u.attributes.updated_at),          // V3 ilişkileri içerir          organizationId: u.relationships?.organization?.data?.id,          roleIds: u.relationships?.roles?.data?.map((r: any) => r.id) || [],        }));
      default:        throw new Error(`Bilinmeyen API versiyonu: ${this.version}`);    }  }}

Gerçekten Yardımcı Olan İzleme ve Uyarılar

Sabah 3'te çok fazla çağrıldıktan sonra, işte izleme kurulumumuz:

typescript
// lib/constructs/api-monitoring.tsexport class ApiMonitoring extends Construct {  constructor(scope: Construct, id: string) {    super(scope, id);
    // Gerçekten bakılan dashboard    const dashboard = new Dashboard(this, 'ApiDashboard', {      dashboardName: 'api-versions-prod',      defaultInterval: Duration.hours(3),  // Faydalı olacak kadar yakın    });
    // Versiyon dağılımı - v2 dağıtımı sırasında şahin gibi izledim    dashboard.addWidgets(      new GraphWidget({        title: 'API Versiyon Dağılımı (isteklerin %\'si)',        left: [v1Percentage, v2Percentage, v3Percentage],        leftYAxis: { max: 100, min: 0 },        period: Duration.minutes(5),        statistic: 'Average',        // Bu açıklama bizi v1'i çok erken kullanımdan kaldırmaktan kurtardı        leftAnnotations: [{          label: 'Min güvenli eşik',          value: 5,          color: Color.RED,        }],      })    );
    // Önemli olan metrik: versiyona göre müşteri hataları    dashboard.addWidgets(      new GraphWidget({        title: 'Versiyona Göre 4xx Hataları',        left: [          new MathExpression({            expression: 'RATE(m1)',            usingMetrics: {              m1: v1Errors,            },            label: 'V1 Hata Oranı',            color: Color.RED,          }),          // v2, v3 için benzer        ],      })    );
    // Kullanımdan kaldırma uyarısı etkinliği    const deprecationAlarm = new Alarm(this, 'V1StillHighUsage', {      metric: v1Percentage,      threshold: 10,      evaluationPeriods: 3,      comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,      alarmDescription: 'V1 hala 10%\'un üzerinde - sunset ertelenmeli mi?',      treatMissingData: TreatMissingData.NOT_BREACHING,    });
    deprecationAlarm.addAlarmAction(      new SnsAction(Topic.fromTopicArn(this, 'AlertTopic', process.env.ALERT_TOPIC_ARN!))    );  }}

Öğrenilen Dersler

1. Versiyon Sunset'i Lansman'tan Daha Zor

Kullanımdan kaldırıldıktan iki yıl sonra hala V1'de 47 müşterimiz var. Neden?

  • 18 aylık dağıtım döngülerine sahip devlet müşterisi
  • Uzaktan güncellenemeyen IoT cihazları
  • Bir müşteri URL'lerimizi firmware'e sabit kodlamış (!)

V1 bakımı kritik entegrasyon bağımlılıklarına sahip müşterileri desteklerken sürekli teknik kaynak gerektirir

2. Kırıcı Değişiklikler Bileşik Faiz Gibidir

Her kırıcı değişiklik test matrisinizi çarpar. Bizde:

  • 3 API versiyonu
  • 4 SDK versiyonu (bir eski)
  • 5 farklı yanıt formatı
  • = Test edilecek 60 kombinasyon

Entegrasyon testlerimiz 45 dakika sürüyor.

3. Dokümantasyon Kayması Gerçek

V1 dokümanlarımız uzun süredir güncellenmemişti. Bir gün büyük bir müşterinin V2'de "düzelttiğimiz" belgelenmemiş bir davranışı kullandığını öğrendik. Özellik bayrağı olarak geri eklemek zorunda kaldık.

4. Versiyon Keşfi Kritik

typescript
// Bu endpoint diğer tüm endpoint'lerden daha fazla destek bileti kaydediyorapp.get('/api', (req, res) => {  res.json({    versions: {      v1: {        status: 'deprecated',        sunset_date: '2025-01-15',        docs: 'https://docs.api.com/v1',        migration_guide: 'https://docs.api.com/v1-to-v2',      },      v2: {        status: 'stable',        docs: 'https://docs.api.com/v2',      },      v3: {        status: 'beta',        docs: 'https://docs.api.com/v3',        breaking_changes: 'https://docs.api.com/v3-breaking-changes',      },    },    current_stable: 'v2',    recommended: 'v2',    your_version: detectVersion(req),  // Müşterinin kullandığı  });});

Operasyonel Değerlendirmeler

Birden fazla API versiyonu sürdürmenin önemli teknik değerlendirmeleri:

  • Altyapı: 3x Lambda fonksiyonları, 3x API Gateway yapılandırmaları operasyonel karmaşıklık yaratır
  • Geliştirme: Her özellik versiyonlar arasında uygulanmak için 40% daha uzun sürüyor
  • Test: Kapsamlı versiyon kapsama nedeniyle CI/CD pipeline 15 dakikadan 45 dakikaya çıktı
  • Dokümantasyon: Versiyon-spesifik dokümantasyon için ayrılmış kaynaklar gerekli
  • Destek: Biletlerin 30%'u versiyonla ilgili karışıklık, net migrasyon kılavuzları gerektirir

Bu yaklaşım API evolüsyonu sırasında kritik müşteri uyumluluğunu sağlar ve hayati iş ilişkilerini korur.

Farklı Ne Yapardım

  1. İlk günden versiyonlama ile başla - Sonradan eklemek 10x daha zor
  2. Kırıcı değişiklikleri toplu yap - 3 büyük yerine 15 küçük kırıcı değişiklik yaptık
  3. Daha iyi migrasyon araçlarına yatırım yap - Otomatik migrasyon script'lerini daha erken yapmalıydık
  4. Gerçekçi sunset tarihleri belirle - 6 ay hayal. 18 ay gerçekçi.
  5. Müşteri versiyonlarını baştan takip et - Kimin neyi kullandığını çok geç öğrendik

Gerçekten İşe Yarayan CDK Pattern'i

Yeni başlıyorsanız, bu yapıyı kullanın:

/api  /v1    /users    /orders    /internal/health  /v2    /users    /orders    /internal/health  /versions (keşif endpoint'i)  /health (versiyondan bağımsız)

Lambda kodunuzu versiyona göre organize edin:

/src  /handlers    /v1      /users      /orders    /v2      /users      /orders  /shared    /database    /auth    /utils

Son Düşünceler

CDK ile API versiyonlama mükemmel pattern'i bulmakla ilgili değil - gerçeğinize uyan pattern'i bulmakla ilgili. Üç versiyonlu sistemimiz zarif değil, ama çalışıyor. Kurumsal müşterilerimizi mutlu, geliştiricilerimizi (biraz) aklı başında ve gelirimizi akışta tutuyor.

Bir dahaki sefere birisi "sadece versiyon numarası eklemeyi" önerdiğinde, bu yazıyı gösterin. Sonra gerçek uygulama için 6 ay ve aylık 15K$ bütçe ayırın.

İlgili Yazılar