AWS CDK ile API Versiyonlama: 3 Başarısız Denemeden Sonra Sonunda Nasıl Doğru Yaptım

Üretimde çoklu versiyon API'leri oluşturmanın gerçek dersleri. İlk üç versiyonlama stratejimin neden başarısız olduğu, gerçekte neyin işe yaradığı ve aklımı kurtaran CDK pattern'leri.

Geçen hafta, en büyük kurumsal müşterimize entegrasyonlarının neden yine bozulduğunu açıklamak zorunda kaldım. Suçlu? Tutarlılık için bir alanı userId'den user_id'ye yeniden adlandıran "basit" API güncellemesi. Uygun versiyonlama olmadan yaptığımız kırıcı bir değişiklik. Bu konuşma acıttı.

15 yıldır API'leri yönettikten ve olası her versiyonlama hatasını yaptıktan sonra, API versiyonlamanın sadece URL'lerinize /v1 eklemekle ilgili olmadığını öğrendim. Entropi, müşteri beklentileri ve kendi akıl sağlığınızı yönetmekle ilgili. İşte AWS CDK ile üretimde gerçekten işe yarayan çözüm.

Versiyonlama Korku Hikayelerim (Tekrar Etmemeniz İçin)#

Deneme #1: "Versiyonlamaya Asla İhtiyacımız Olmayacak" Yaklaşımı (2019)#

İlk startup'ımda bir API, beş istemci vardı. "Tüm istemcileri biz kontrol ediyoruz" diye düşündüm. "Her şeyi birlikte güncelleriz."

Altı ay sonra, 50 istemcimiz oldu, bunların arasında hava boşluğunda izole ağlarda SDK'mızı çalıştıran bir devlet kurumu da vardı. 18 ay boyunca güncelleyemediler. Sadece onlar için gölge bir API sürdürdük, güvenlik düzeltmelerini manuel olarak geri taşıdık. Aylık 8.000$ ek altyapı maliyeti.

Deneme #2: "Her Şeyi Versiyonla" Felaketi (2020)#

Aşırı düzeltme yaptım. Her şey için versiyonlar oluşturdum - endpoint'ler, header'lar, yanıt formatları. Bu canavarlıkla karşılaştım:

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

Tek bir istekte üç farklı versiyon numarası. Junior geliştiricilerimiz kelimenin tam anlamıyla bir uyumluluk matrisi yazdırıp monitörlerine yapıştırdılar. Test imkansızdı. Üretimde 47 farklı versiyon kombinasyonumuz vardı.

Deneme #3: "Akıllı" Olmayan Router (2021)#

Müşteri parmak izine göre istekleri doğru versiyona "akıllıca" yönlendirecek ayrıntılı bir Lambda@Edge fonksiyonu oluşturdum. Her isteğe 200ms gecikme ekledi ve Kara Cuma'da çökerek tüm API versiyonlarını aynı anda çökerttti. Gelir etkisi: 1.2 milyon dolar.

Gerçekten İşe Yarayan: Kullanımdan Kaldırma Uyarılarıyla Yol Tabanlı Versiyonlama#

Tüm bu acılardan sonra, işte 300+ kurumsal müşteri genelinde 2 yıldır sorunsuz çalışan yaklaşım:

TypeScript
// lib/config/api-versions.ts
export 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: 47,  // Hala devlet müşterileri var
    breakingChanges: [],
    supportedFeatures: new Set(['basic-crud']),
  },
  v2: {
    version: 'v2',
    status: 'stable',
    launchedAt: new Date('2023-06-01'),
    monthlyActiveClients: 1823,
    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-11-01'),
    monthlyActiveClients: 89,
    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 günde 50 milyon isteği karşılıyor:

TypeScript
// lib/stacks/versioned-api-stack.ts
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 limiti
      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',  // Lambda runtime versiyonunu kullan
          ...(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 oluyor
export 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.ts
export 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': event.requestContext.requestId,  // Placeholder
      '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.ts
export 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.ts
export 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.ts
export 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!))
    );
  }
}

Zor Yoldan Öğ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'i sürdürme maliyeti: ~3.000$/ay. Bu müşterileri kaybetme maliyeti: ~180.000$/ay.

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 en son 2022'de güncellendi. Geçen ay 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 kaydediyor
app.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ığı
  });
});

Gerçek Maliyetler#

Birden fazla API versiyonu çalıştırmak ücretsiz değil:

  • Altyapı: 3x Lambda fonksiyonları, 3x API Gateway yapılandırmaları = +850$/ay
  • Geliştirme: Her özellik versiyonlar arasında uygulanmak için 40% daha uzun sürüyor
  • Test: CI/CD pipeline 15 dakikadan 45 dakikaya çıktı
  • Dokümantasyon: Üç set doküman sürdürmek = 1 teknik yazar, yarı zamanlı
  • Destek: Biletlerin 30%'u versiyonla ilgili karışıklık

Toplam ek maliyet: ~15.000$/ay. Ama V1'i kaldırmak kaybedilen gelirde bize 180.000$/ay'a mal olur.

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:

Text
/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:

Text
/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.

Loading...

Yorumlar (0)

Sohbete katıl

Düşüncelerini paylaşmak ve toplulukla etkileşim kurmak için giriş yap

Henüz yorum yok

Bu yazı hakkında ilk düşüncelerini paylaşan sen ol!

Related Posts