Scrum'da Teknik Pratikler: Branching, CI, Feature Flag ve Teknik Borç
Scrum mühendislik pratiği dayatmaz, ama her Sprint Definition of Done'ı karşılayan bir Increment ancak kısa ömürlü branch'ler, CI ve feature flag'lerle sürdürülebilir. Gerçek koşuda Microsoft.FeatureManagement 4.8.0 ile flag kapalıyken iade endpoint'i 404, açıkken 201 döndü; aynı build iki ortamda farklı davrandı. Yazı flag'in Definition of Done'ı neden delmediğini, teknik borcu görünür kılmanın üç yolunu ve spike'ı anlatıyor.
Sürüm notu: Bu yazı Scrum Guide 2020'yi (Kasım 2020, Ken Schwaber ve Jeff Sutherland) esas alır. Yazıdaki kodlar .NET 10 ve Microsoft.FeatureManagement.AspNetCore 4.8.0 ile derlenip denenmiştir; HTTP (Hypertext Transfer Protocol) çıktıları yerel Kestrel'e curl ile atılan gerçek isteklerden.
Önce Tahmin Et
Sprint 14'ün son günü. Ayşe ve Zeynep "kendi kendine iade" özelliğini üç haftadır feature/self-service-returns branch'inde geliştiriyor; özellik iki Sprint'e yayılmış ve bitmesine en az bir hafta var. Bu arada main'e 60'tan fazla commit girdi; branch'i main'le birleştirmeyi denediklerinde yedi dosyada conflict çıkıyor. Aynı Sprint'te bitmiş olan saat dilimi düzeltmesi ise release bekliyor, çünkü ekip "büyük merge'den sonra hepsini birlikte çıkaralım" diyor.
Bu Sprint'in sonunda kullanılabilir bir Increment var mı? Ve yarım kalan bir özelliği main'e merge etmek mümkün mü, yoksa bu Definition of Done'ın ihlali mi olur? Tahminini yaz; cevap bu yazıdaki üç pratiğin kesişiminde.
Scrum Mühendislik Pratiği Dayatmaz
Guide bilerek susuyor: Scrum framework'ü içinde çeşitli süreçlerin, tekniklerin ve yöntemlerin kullanılabileceğini, Scrum'ın mevcut pratiklerin etrafını sardığını ya da onları gereksiz kıldığını söylüyor. Branch stratejisi, test yaklaşımı, deploy yöntemi Guide'da yok. Ama Guide iki şart koyuyor ve bu şartlar pratik seçimini fiilen daraltıyor: her Sprint Definition of Done'ı karşılayan kullanılabilir bir Increment, ve Sprint boyunca kalitenin düşmemesi.
Her Increment'in önceki bütün Increment'lerle birlikte çalıştığının doğrulanması gerekiyorsa, entegrasyonun Sprint sonunda bir kerede değil sürekli yapılması gerekir. Ron Jeffries "Dark Scrum" yazısında, Scrum'ın kötü uygulanmış hâllerinden çıkışın Developers için yolunu tam olarak bu pratiklere bağlıyor: acceptance test, incremental design, refactoring, programmer testing ve continuous integration. Bu pratiklerin kökeni olan Extreme Programming (XP), Agile Mindset serisinin "Extreme Programming (XP): Agile'ın Mühendislik Tarafı" yazısında anlatılıyor; burada yalnızca Scrum'ın ritmine nasıl oturduklarına bakıyoruz.
Kısa Ömürlü Branch ve Trunk-Based Development
Bookstore'un üç haftalık branch'i Scrum'ın ritmine ters düşüyor. Branch yaşadıkça main'den uzaklaşıyor, conflict riski büyüyor ve en kötüsü, entegrasyonun gerçekten çalışıp çalışmadığı ancak merge anında ortaya çıkıyor. O an da genellikle Sprint'in son günü.
Trunk-based development bunu tersine çeviriyor: herkes küçük değişikliklerle tek bir ana hatta (trunk, Bookstore'da main) sık sık entegre oluyor; branch'ler varsa da birkaç saat ya da en fazla bir iki gün yaşıyor. Bookstore'un yeni çalışma anlaşması üç maddelik: bir pull request en fazla bir iş günü açık kalır, bir pull request'te değişen satır sayısı 400'ü geçerse ekip onu bölmeyi konuşur, main her zaman deploy edilebilir durumdadır. Branch stratejilerinin kendisi Git & GitHub serisinin "Git ile Ekip Çalışması: Branch Stratejileri, Rulesets ve Conventional Commits" yazısında, review akışı aynı serinin "GitHub ile Pull Request: GitHub Flow, Review ve Merge Yöntemleri" yazısında.
Bunun Scrum'daki karşılığı Sprint Backlog'un parçalanma biçiminde görünüyor. Guide Sprint Planning'de item'ların genellikle bir gün ya da daha kısa süren iş parçalarına bölündüğünü söylüyor; bir günlük iş parçası, bir günlük branch demek. Planlama ile branch ömrü birbirini besliyor.
CI: Entegrasyonu Sprint Sonuna Bırakmamak
Continuous Integration (CI), her Developer'ın işini en az günde bir kez ana hatta entegre etmesi ve her entegrasyonun otomatik build ve test'le doğrulanması. Bir önceki yazılarda gördüğümüz Definition of Done pipeline'ı bunun üzerine kurulu: her pull request'te build, test, zafiyet ve OpenAPI kontrolü. CI'ın tanımı ve Continuous Delivery'den farkı CI/CD Temelleri serisinin "CI, Continuous Delivery ve Continuous Deployment: Farkları ve Neden Önemli" yazısında.
Scrum açısından CI'ın kazancı transparency. Main her gün entegre ve test edilmiş durumdaysa, Daily Scrum'da "entegrasyonda sorun çıkabilir" diye bir bilinmez yok; ürünün gerçek durumu her an görünür. Üç haftalık bir branch ise Sprint Backlog'daki "In Progress" kartının arkasında görünmeyen bir risk taşıyor.
Feature Flag ile Yarım İşi Main'e Merge Etmek
Peki üç haftalık bir özellik bir günlük branch'lere nasıl sığar? Cevap feature flag: yarım kalmış kod main'e merge edilir, deploy edilir, ama bir configuration anahtarıyla kapalı tutulur. Deploy ile release birbirinden ayrılır. Bookstore ekibi kendi kendine iade özelliğini Microsoft.FeatureManagement ile böyle sarmaladı:
using Microsoft.FeatureManagement;
using Microsoft.FeatureManagement.AspNetCore;
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddFeatureManagement();
WebApplication app = builder.Build();
// Sprint 14'ün yarım işi: main'de, flag arkasında kapalı
app.MapPost("/api/orders/{orderId:int}/returns", (int orderId, ReturnRequest request) =>
Results.Created($"/api/returns/{orderId}-1", new { orderId, request.Reason, Status = "Requested" }))
.WithFeatureGate(Features.SelfServiceReturns);
// mevcut endpoint: iade butonunu yalnız flag açıkken gösterir
app.MapGet("/api/orders/{orderId:int}", async (int orderId, IFeatureManager features) =>
new
{
orderId,
Status = "Delivered",
CanRequestReturn = await features.IsEnabledAsync(Features.SelfServiceReturns)
});
app.Run();
public record ReturnRequest(string Reason);
public static class Features
{
public const string SelfServiceReturns = "SelfServiceReturns";
}
Flag'in değeri appsettings.json'da, Microsoft Feature Management şemasıyla tutuluyor:
{
"Logging": { "LogLevel": { "Default": "Warning" } },
"feature_management": {
"feature_flags": [
{ "id": "SelfServiceReturns", "enabled": false }
]
}
}
Production'da flag kapalı. Uygulama çalışırken iki isteğin gerçek cevabı (Date ve Server header'ları çıkarıldı):
$ curl -i -X POST http://127.0.0.1:5391/api/orders/1042/returns -H 'Content-Type: application/json' -d '{"reason":"Damaged cover"}'
HTTP/1.1 404 Not Found
Content-Length: 0
$ curl http://127.0.0.1:5391/api/orders/1042
{"orderId":1042,"status":"Delivered","canRequestReturn":false}
WithFeatureGate flag kapalıyken endpoint'i yokmuş gibi 404 döndürüyor; sipariş detayı da Angular tarafına iade butonunu göstermemesini söylüyor. Staging'de ise flag environment variable'la açılıyor; aynı build, farklı configuration. Uygulama feature_management__feature_flags__0__enabled=true değişkeniyle yeniden başlatıldıktan sonra aynı iki istek:
$ curl -i -X POST http://127.0.0.1:5391/api/orders/1042/returns -H 'Content-Type: application/json' -d '{"reason":"Damaged cover"}'
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
Location: /api/returns/1042-1
Transfer-Encoding: chunked
{"orderId":1042,"reason":"Damaged cover","status":"Requested"}
$ curl http://127.0.0.1:5391/api/orders/1042
{"orderId":1042,"status":"Delivered","canRequestReturn":true}
Ekip artık her gün main'e merge ediyor; özellik staging'de flag açık hâlde test ediliyor, Selin ve destek ekibi Review'da onu staging'de deniyor. Saat dilimi düzeltmesi de büyük merge'i beklemeden Sprint'in üçüncü gününde production'a çıktı. Flag'in tür ve yaşam döngüsü tasarımı Yazılım Tasarımı kategorisindeki "Feature Flag'leri Kodda Tasarlamak: Toggle Türleri, Microsoft.FeatureManagement ve Flag Borcu" yazısında; deploy ile release'i ayırma stratejisi ve OpenFeature ise Deployment Stratejileri serisinin "Feature Flag'ler: Deploy'u Release'ten Ayırmak, Microsoft.FeatureManagement ve OpenFeature" yazısında.
Flag Definition of Done'ı Delmez
Burada ince bir ayrım var. Flag arkasındaki kod main'e girerken Definition of Done'ın kalite maddelerini karşılıyor: review'dan geçti, test'leri yeşil, build uyarısız. Ama "kendi kendine iade" item'ı Done değil; kullanıcı onu kullanamıyor ve Product Owner'ın kabul ettiği kriterler henüz tamamlanmadı. Sprint sonunda bu item, bir önceki yazılarda gördüğümüz kurala göre Product Backlog'a döner ve Selin onu yeniden sıralar.
Flag'in kazancı Increment'i yapay olarak büyütmek değil; yarım işin, bitmiş işin release'ini ve entegrasyonunu rehin almamasını sağlamak. Sprint 14'ün Increment'i saat dilimi düzeltmesi ile iki küçük item'dan oluşuyordu; yarım özellik ise main'de, entegre ve test edilmiş hâlde bekliyordu.
Flag'in bir de bedeli var: her flag koda bir koşul, test matrisine bir boyut ekliyor. Bookstore'un Definition of Done'ına bu yüzden bir madde girdi: release edilmiş bir özelliğin flag'i en geç iki Sprint içinde koddan silinir. Silme işi, flag'in açıldığı gün Product Backlog'a bir item olarak yazılıyor.
Teknik Borcu Görünür Kılmak
Teknik borç metaforu Ward Cunningham'ın 1992'deki OOPSLA (Object-Oriented Programming, Systems, Languages and Applications) deneyim raporundan geliyor: kodu ilk anlayışınla hızlıca çıkarmak borçlanmaktır; anlayışın geliştikçe kodu ona uydurmazsan faiz ödersin. Scrum açısından asıl soru borcun ne olduğu değil, görünür olup olmadığı. Bookstore'un denediği üç yol ve trade-off'ları:
| Yol | Nasıl | Artısı | Eksisi |
|---|---|---|---|
| Backlog item | Borç, Type alanı "teknik borç" olan bir Product Backlog item olur ve Selin onu sıralar | Tamamen görünür; maliyeti ve etkisi diğer işlerle aynı listede tartışılır | Developers etkisini iyi anlatamazsa sürekli aşağıda kalır |
| Definition of Done kuralı | "Yeni analyzer uyarısı yok", "dokunulan method'un test'i var" gibi maddeler | Yeni borcun birikmesini durdurur; Guide'ın "kalite düşmez" kuralının uygulaması | Eski borcu temizlemez |
| Kapasite payı | Her Sprint kapasitenin yüzde 15'i borca ayrılır | Düzenli ve öngörülebilir | Hangi borcun neden seçildiği Product Owner'dan saklanabilir; sıralama kararı ekibe kayar |
Bookstore ilk ikisini birleştirdi. Yeni borç Definition of Done'la durduruluyor; eski borç item olarak backlog'da, "Neden burada" gerekçesiyle sıralanıyor. Bir borç item'ı bir feature'ı doğrudan yavaşlatıyorsa (03'teki 900 satırlık method gibi) o feature'ın planına dahil ediliyor. Kapasite payı, Product Owner'ın sıralama accountability'sini sessizce aşındırdığı için bırakıldı. Borcu küçük adımlarla ödeme tekniği Clean Code serisinde ve ilerideki Refactoring serisinde.
Spike: Bilgiyi Satın Almak
Spike XP'den gelen bir terim: amacı production kodu değil bilgi üretmek olan, süresi baştan sınırlanmış bir araştırma item'ı. Bookstore'un 03'teki ilk sıradaki item'ı tam olarak buydu: "Kargo iade etiketi API'si (Application Programming Interface): test hesabıyla etiket üret", iki gün. Çıktısı üç şeydi: API'nin etiketi yalnız PDF (Portable Document Format) olarak döndürdüğü bilgisi, bir sonraki item'ların daha net yazılmış hâli ve atılmak üzere yazılmış bir prototip.
Guide'da spike kelimesi yok; ama Sprint Planning'deki "what" konusu ile Product Backlog'un risk ve öğrenmeye göre sıralanması ona doğal bir yer açıyor. İki kural Bookstore'da işe yaradı: spike'ın süresi baştan yazılıyor ve süre dolduğunda öğrenilen ne varsa o kadarıyla duruluyor; spike'ın kodu production'a taşınmıyor, öğrenilenle doğru item yeniden yazılıyor.
Üretimde Dikkat
Pratiklerin sağlığını ölçmek için Bookstore dört sayıya bakıyor: açık branch'lerin yaşı, pull request'in açılıştan merge'e kadar geçen süresi, açık feature flag sayısı ve en eski flag'in yaşı, main'in kırmızı kaldığı toplam süre. Teslim performansını ölçen DORA (DevOps Research and Assessment) metrikleri ise CI/CD Temelleri serisinin "Pipeline'ı Ölçmek ve Hızlandırmak: DORA Metrikleri, Cache ve Paralellik" yazısında.
Feature flag'ler ölçekte kendi başına bir yük. Bookstore'da açık flag sayısı 14'e çıktığında hangi kombinasyonun test edildiğini kimse bilmiyordu; Definition of Done'daki iki Sprint kuralı bu deneyimden sonra eklendi.
Sık Düşülen Tuzaklar
Her Product Backlog item için uzun ömürlü bir feature branch. Item iki Sprint sürerse branch da iki Sprint yaşar; entegrasyon riski Sprint sonuna birikir ve Increment'in kullanılabilirliği merge'e bağlanır.
Hiç silinmeyen flag'ler. Release edilmiş bir özelliğin flag'i kodda kaldıkça ölü koşullar ve test edilmemiş kombinasyonlar birikir; flag, çözdüğü sorundan daha büyük bir borca dönüşür.
"Teknik borç Sprint'i". Birkaç Sprint'te bir yalnız borç temizlenen bir Sprint, hardening sprint'in kuzeni: kaliteyi Sprint'lerin dışına iter ve Guide'ın "kalite düşmez" kuralını diğer Sprint'lerde fiilen askıya alır.
Süresi belirsiz spike. "Kargo API'sini araştırıyoruz" diye başlayıp bir Sprint süren bir item, öğrenme değil gizli geliştirmedir.
Küçük Bir Deneme
★☆☆ Ekibinin açık branch'lerini yaşlarıyla listele. Bir iş gününden eski olanların her biri için neden açık kaldığını yaz.
★★☆ Bu yazıdaki örneği kendi makinende çalıştır, sonra flag'i environment variable yerine appsettings.Development.json ile aç. Aynı build'in iki ortamda farklı davrandığını curl ile gör.
★★★ Kodunuzdaki bütün feature flag'leri bul ve her biri için "açıldığı tarih, release edildiği tarih, silinme item'ı var mı" sütunlarını doldur. Release edilip silinmemiş flag'ler için birer Product Backlog item'ı yaz ve bir sonraki Sprint Planning'e götür.
Pratikler yerinde değilse Scrum'ın event'leri boş bir kabuğa dönüşür. Bir sonraki yazıda bu kabuğun en bilinen hâllerine bakıyoruz: Dark Scrum, Zombie Scrum, ScrumBut, Sprint 0, hardening sprint ve mini-waterfall Sprint'ler; her biri için belirti, kök neden ve ilk adım.