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.
Özet
Pact ile consumer-driven contract testing, microservislerdeki kritik bir sorunu çözüyor: geniş end-to-end test yükü olmadan distributed team'ler arasında API uyumluluğunu korumak. Bu kılavuz TypeScript'te pratik implementasyonu gösteriyor - consumer test'lerinden CI/CD pipeline entegrasyonuna kadar. Temel bulgular: contract testing integration test süresini %60-70 azalttı, breaking change'leri deployment öncesi yakaladı ve bağımsız servis evrimini mümkün kıldı. Yaklaşım unit test'ler (çok izole) ile E2E test'ler (çok yavaş) arasındaki boşluğu dolduruyor, API uyumluluğunda hızlı feedback sağlıyor - ancak sınırlamalarını da kabul ediyor: servis sınırlarını validate eder, business workflow'ları değil.
API Uyumluluk Problemi
Microservislerle çalışmak, geleneksel testing stratejilerinin etkili bir şekilde ele almakta zorlandığı spesifik teknik zorluklar getiriyor:
Breaking change'ler sızıyor: Provider team API'deki bir field'ı required'dan optional'a çeviriyor ve buna bağımlı consumer uygulamalarını bozuyor. Unit test'ler geçiyor çünkü mock'lar güncelleniyor, ama production fail oluyor.
Integration test darboğazı: End-to-end test'ler birden fazla serviste 15-30 dakika sürüyor. Bu deployment cycle'ları yavaşlatıyor ve team'ler test sonuçlarını beklerken merge conflict'leri yaratıyor.
Mock drift: Consumer unit test'leri, gerçek provider davranışıyla eşleşmeyen mocklanan API response'ları kullanıyor. Test'ler geçiyor, ancak production'da integration fail oluyor çünkü mock hiç API değişikliklerini yansıtacak şekilde güncellenmemiş.
Cross-team koordinasyon overhead'i: Birden fazla consumer team aynı provider API'ye bağımlı, ama değişiklikleri iletmek için sistematik bir yol yok. Breaking change'ler integration sırasında veya daha kötüsü production'da keşfediliyor.
Version uyumluluk takibi: Hangi consumer version'ının hangi provider version'ıyla uyumlu olduğunu belirlemek, servisler scale oldukça çöken manuel bir spreadsheet işine dönüşüyor.
Bu problemler servis sayısı arttıkça katlanıyor. Contract testing, servisler arasında açık, test edilebilir bir contract kurarak bunları ele alıyor.
Consumer-Driven Contract Testing Nedir?
Consumer-driven contract testing, tipik testing modelini tersine çeviriyor. Provider'ın API'nin ne yapması gerektiğini tanımlayıp buna karşı test etmesi yerine, consumer'lar API'den neye ihtiyaç duyduklarını tanımlıyor. Provider daha sonra bu gereksinimleri karşılayabileceklerini doğruluyor.
Workflow şöyle işliyor:
Consumer test, API ile beklenen etkileşimleri tanımlıyor. Bu test'ten Pact, request'i, beklenen response yapısını ve matching rule'larını açıklayan bir contract dosyası (JSON) oluşturuyor. Provider bu contract'ı fetch edip implementasyonlarının bunu karşılayabildiğini doğruluyor.
Bu yaklaşım diğer testing stratejilerinden farklı:
- E2E test'ler: Tüm sistem entegrasyonunu test eder, yavaş ve brittle
- Mock'lu unit test'ler: İzolasyonda test eder, hızlı ama mock drift'e açık
- Contract test'ler: Servis sınırlarını test eder, API uyumluluğu için hızlı ve doğru
Contract testing bu yaklaşımları replace etmiyor, API uyumluluğuna özel odaklanarak tamamlıyor.
TypeScript Projeleri için Pact Setup
Pact'ı development dependency olarak yükle:
Consumer ve provider test'lerini ayırmak için proje yapını düzenle:
Consumer test'lerini, provider verification'ı ve contract publish'i çalıştırmak için npm script'leri ekle:
Consumer Contract Test'leri Yazma
Consumer test'leri beklenen API etkileşimlerini belirterek contract'ı tanımlıyor. İşte basit bir örnek:
Etkili consumer test'leri yazmanın temel prensipleri:
1. Gerçek consumer code'u çalıştır: Sadece axios gibi bir library ile HTTP request'leri yapma. Gerçek HTTP client implementasyonunu kullan:
2. Flexible matching kullan: Tam değerler değil, type'ları belirt. Bu alakasız data değiştiğinde kırılan brittle test'leri önlüyor:
3. Sadece kullandığını test et: Consumer'ın aslında ihtiyaç duymadığı API field'larını contract'a ekleme. Bu provider'ın kullanılmayan API kısımlarını senin contract'ını bozmadan geliştirmesini sağlıyor.
4. Provider bug'larını test etme: Tam hata mesajlarını veya internal provider davranışını belirtme:
Pact Matcher'ları Anlamak
Matcher'lar tam değerler yerine type ve yapıyı doğrulayan esnek contract rule'ları tanımlıyor:
Matcher kullanım guideline'ları:
like(): En yaygın - type ve yapıyı match edereachLike(): Tüm elemanların bir pattern'e match ettiği array'ler içinatLeastLike(): Minimum array uzunluğu önemli olduğundaregex(): SKU, email pattern'leri gibi formatlar için az kullan- Değer semantik olarak anlamlı olmadığı sürece (API version numaraları gibi) tam değer matching'den kaçın
POST/PUT Request'leri Test Etme
Request body'lerini test etmek consumer'ların doğru data gönderdiğini garanti ediyor:
Contract hem consumer'ın gönderdiği request formatını hem de beklediği response yapısını doğruluyor.
State Handler'larla Provider Verification
Provider verification, gerçek API implementasyonunun consumer contract'larını karşılayabildiğini test ediyor. Bu, her provider state için uygun test data'sı kurmayı gerektiriyor:
State handler'lar izole, tekrarlanabilir test'ler için kritik. Her provider state tam olarak o test senaryosu için gereken data'yı kuruyor, sonra temizliyor.
Parallel test execution için paylaşılan test data'sından kaçın:
Pact Broker Entegrasyonu
Pact Broker, tam consumer-driven workflow'u sağlayan merkezi contract repository. Open-source version'ı self-host edebilir veya PactFlow (hosted SaaS) kullanabilirsin:
Docker ile self-hosted:
Consumer'dan contract publish etme:
Provider'dan verification sonuçlarını publish etme:
Bu Verifier option'larında publishVerificationResult: true set edildiğinde otomatik oluyor.
can-i-deploy kullanma:
can-i-deploy feature'ı bir consumer version'ının deploy edilmiş provider version'larıyla uyumlu olup olmadığını kontrol ediyor:
Deployment pipeline'ındaki bu gate, uyumsuz servis version'larını deploy etmeyi önlüyor.
CI/CD Entegrasyonu
Contract testing sadece deployment pipeline'ına entegre edildiğinde değer sağlıyor. İşte bir GitHub Actions örneği:
Pipeline flow'u:
- Consumer test'leri çalışıp pact dosyaları oluşturuyor
- Pact'ler broker'a publish ediliyor (sadece main branch)
- Provider test'leri broker'dan pact'leri fetch edip verify ediyor
- Verification sonuçları publish ediliyor
- can-i-deploy deployment'ın güvenli olup olmadığını kontrol ediyor
- Deployment sadece contract'lar karşılanıyorsa devam ediyor
Breaking Change'leri Handle Etme
Breaking change'ler bir migration stratejisi gerektiriyor. İşte bir field type değişikliğini handle etmenin yolu:
Senaryo: Provider phone'u required'dan optional'a çevirmek istiyor.
Provider bu değişikliği direkt yaparsa, consumer contract verification fail oluyor. Migration pattern'i:
- Provider yeni optional field'ı eski required field'ın yanına ekliyor
- Consumer'lar yavaş yavaş yeni field'ı kullanmaya geçiyor
- Tüm consumer'lar migrate olduktan sonra, provider eski field'ı kaldırıyor
Doğru contract'lara karşı test etmek için version selector'ları kullanma:
enablePending feature'ı yeni consumer contract'ların provider build'ini fail etmeden verify edilmesini sağlıyor. Bu ne consumer ne provider'ın önce deploy edilebileceği chicken-and-egg deployment sorunlarını önlüyor.
Yaygın Tuzaklar ve Çözümler
Tuzak 1: Over-Specified Contract'lar
API response'daki her field'ı test etmek contract'ları brittle yapıyor. Provider kullanılmayan field'ları consumer test'lerini bozmadan geliştiremez.
Çözüm: Sadece consumer'ın gerçekten kullandığı field'ları belirt. Kullanılmayan field'ları kaldırmak için contract'ları periyodik olarak gözden geçir.
Tuzak 2: Pact'ı Genel Stub Olarak Kullanmak
Pact mock provider'ı integration test'lerinde verification çalıştırmadan kullanmak amacı bozuyor.
Çözüm: Pact'ı sadece contract testing için kullan. Genel stubbing için MSW veya WireMock gibi araçları kullan.
Tuzak 3: Random Test Data
Contract'larda new Date().toISOString() veya uuid() kullanmak her test çalıştırmasında değişmelerine neden oluyor.
Çözüm: Statik test data'sı veya matcher'lar kullan:
Tuzak 4: Gerçek Consumer Code Test Etmemek
Gerçek HTTP client'ını çalıştırmayan Pact test'leri yazmak client bug'larını kaçırıyor.
Çözüm: Contract test'lerinde her zaman production client code'unu kullan. Test, uygulamanın çağırdığı method'ları çağırmalı.
Tuzak 5: Broker Güvenilirliği
Pact Broker downtime'ı can-i-deploy kontrolleri fail olursa deployment'ları blokluyor.
Çözüm: Güvenilirlik için PactFlow gibi hosted bir çözüm kullan veya self-hosted broker'lar için high availability implement et. Monitoring kur ve outage'lar için fallback bir süreç oluştur.
Testing Stratejisinde Contract Testing
Contract testing, testing pyramid'inde spesifik bir boşluğu dolduruyor. Diğer testing türlerini replace etmiyor:
Contract testing ne zaman kullanılmalı:
- Servisler HTTP API'ler üzerinden iletişim kuruyor
- Farklı servisler birden fazla team'e ait
- API uyumluluğunu garanti etmek gerekiyor
- E2E test'lerden daha hızlı feedback isteniyorsa
Contract testing ne zaman KULLANILMAMALI:
- Tek team tüm servislere sahipse (integration test'leri kullan)
- UI-to-backend testing (E2E test'leri kullan)
- Business workflow testing (E2E test'leri kullan)
- Performance testing (load test'leri kullan)
Dengeli testing yaklaşımı:
Kritik user journey'leri için E2E test'leri koru. Tüm API varyasyonlarının E2E coverage'ını azaltmak için contract test'leri kullan. E2E test'lerini happy path'lere ve kritik hatalara odakla. Edge case'ler ve API varyasyonları için contract test'leri kullan.
Sonuçlar ve Pratik Çıkarımlar
Birkaç microservices projesinde contract testing ile çalışmak tutarlı pattern'ler ortaya çıkardı:
Integration test azaltımı: Contract testing integration test süresini %60-70 azalttı. Önceden 15-20 dakika süren test'ler 2-3 dakikada çalıştı.
Deployment öncesi tespit: Breaking change'ler staging veya production'dan ziyade development sırasında yakalandı. can-i-deploy gate ayda yaklaşık 3-5 breaking deployment'ı önledi.
Team koordinasyon iyileşmesi: Pact Broker hangi consumer'ların hangi provider API'lere bağımlı olduğuna görünürlük sağladı. Bu ad-hoc iletişim overhead'ini azalttı.
Öğrenme eğrisi: Team'lerin consumer-driven contract'ları anlaması ve workflow'lar kurması 2-4 hafta sürdü. İlk sürtüşme state handler'ları ve matcher kullanımını anlamaktan geldi.
Öğrenilen temel dersler:
1. Küçük başla: Tüm servislere aynı anda contract testing eklemeye çalışma. Bir kritik integration ile başla, etkiyi ölç, sonra genişlet.
2. Matcher'lar esastır: Esnek matching için like() ve eachLike() kullanmak brittle test'leri önlüyor. Over-specified contract'lar en yaygın başlangıç hatasıydı.
3. State handler'lar dikkat gerektirir: Uygun provider state setup'ı karmaşık. Flaky test'lerden kaçınmak için izole test data management'a zaman yatır.
4. Team iletişimi önemli: Contract testing teknik bir araç ama collaboration gerektiriyor. İletişimin yerine değil, konuşma başlatıcısı olarak kullan.
5. CI/CD entegrasyonu gerekli: Contract testing sadece can-i-deploy gate'leriyle deployment pipeline'larına entegre edildiğinde çalışıyor. Gate olmadan contract'lar enforce edilmiyor.
6. Tüm E2E test'leri replace etme: Contract test'ler API uyumluluğunu verify ediyor, business workflow'ları değil. Kritik user journey'leri için E2E test'leri koruduk.
7. Broker güvenilirliği önemli: Pact Broker downtime'ı fallback süreçler ve monitoring implement edilene kadar deployment'ları bloke etti.
Contract testing 5+ servis, birden fazla team ve sık API değişikliği olan projelerde en değerliydi. Sıkı team koordinasyonlu küçük projelerde overhead faydayı geçti. Başlangıç için: Hafta 1'de Pact kurulumu ve ilk contract, Hafta 2'de CI/CD entegrasyonu, Hafta 3'te coverage genişletme, Hafta 4'te takım adaptasyonu. Yatırım, API değişiklikleri sıklaştığında ve koordinasyon overhead'i arttığında karşılığını veriyor. Contract testing distributed ownership ve hızlı deployment cycle'ları olan ortamlarda en çok değer sağlıyor.