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
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:
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:
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
-
Audience’a göre versioning seç: Public API’ler için URL path, internal için header’lar, rapid iteration için GraphQL evolution.
-
Breaking change detection’ı automate et: CI/CD’deki OpenAPI diff tool’ları accidental breaking change’leri önler.
-
Deprecation 12+ ay gerektirir: Multi-channel communication (header’lar, email, doc’lar), usage monitoring, hard shutdown yerine gradual throttling.
-
Concurrent version’ları sınırla: Maksimum 3 active version technical debt explosion’ını önler.
-
Gradual rollout kullan: Feature flag’lerle 10% → 25% → 50% → 100% traffic splitting migration risk’ini azaltır.
-
Contract testing break’leri önler: Pact, consumer’lar ve provider’lar arasındaki incompatibility’leri production’dan önce yakalar.
-
GraphQL versioning complexity’sini elimine eder: Field-level deprecation version explosion olmadan smooth migration sağlar.
-
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
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.
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ı.
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.
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ı.
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.