Kaynak: hafiz.dev
Laravel uygulamalarının çoğunda zaten feature flag’ler bulunmaktadır. Ancak bu flagler genellikle en kötü yerlerde yaşarlar: bir config/features.php dosyasında boole değerleri halinde, farklı controller’larda okunan bir .env değişkeni ya da birinin kaldıracağına yemin ettiği bir if ($user->id === 1) ifadesi gibi. Bu yöntemler işe yarar, ta ki bir gün işe yaramayana dek, ardından kodunuzu dağıtmanız gerekir çünkü bir özelliği kimlerin göreceğini değiştirmek istiyorsunuz. İşte feature flag’lerin engellemeye çalıştığı tam da budur.
Laravel Pennant, birinci taraf çözümüdür. Küçüktür, herhangi bir harici hizmet için ücret ödemeniz gerekmez ve zihin modeli yerine oturduğunda her bir hack’i bir tutarlı API ile değiştirir. Ancak bu zihin modelinin bir keskin tarafı vardır ki, bu çoğu insanı ilk denemesinde yavaşlatır ve çoğu eğitim kaynağı bunun üzerinden geçer. Bu kılavuz, flag’leri tanımlamaktan, onları kullanıcılar ya da takımlar ile sınırlandırmaktan, yüzde roll-out işlemine, zengin değerlere, test etmeye ve yeni taşınma sürecinde eğer bilinmiyorsa sessizce kırılacak olan davranışa kadar her şeyi kapsar.
Pennant Kurulumu
Pennant Kurulumu
Burada sürpriz bir şey yok:
composer require laravel/pennant
php artisan vendor:publish --provider="Laravel\Pennant\PennantServiceProvider"
php artisan migrate
Yayınlama işlemi sırasında config/pennant.php dosyası ve bir features tablosunu oluşturan bir migration oluşturulur. Bu tablo, Pennant’ın varsayılan sürücüsünün çözümlediği flag değerlerini saklayacağı yerdir. “Çözümleme” burada önemli bir anahtar kelimedir. Kısa bir süre sonra daha fazla bilgi verilecektir.
Pennant, iki depolama sürücüsü ile birlikte gelir. database sürücüsü (varsayılan), çözümlediği değerleri o features tablosunda, böylece bir değer, isteklere, dağıtımlara ve sunucu yeniden başlatmalarına karşı kalıcı hale getirir. array sürücüsü ise değerleri tek bir istekte bellekte tutar ve sonrasında unutur, bu nedenle testler için doğru seçimdir. Varsayılanı config/pennant.php dosyasında seçersiniz.
İlk Flag’inizi Tanımlama
İlk Flag’inizi Tanımlama
Flag’ler, Feature::define metodu ile tanımlanır, genellikle bir servis sağlayıcının boot metodunda. Flag’e bir isim verirsiniz ve değerini çözümleyen bir closure. Closure, varsayılan olarak şu anda kimliği doğrulanmış kullanıcıdır.
// app/Providers/AppServiceProvider.php
use App\Models\User;
use Laravel\Pennant\Feature;
public function boot(): void
{
Feature::define('new-checkout', fn (User $user) =>
$user->created_at->isAfter(now()->subDays(30))
);
}
Bu flag, son 30 günde kaydolan kullanıcılar için aktiftir. Diğer herkes eski ödeme sayfasını görür. Uygulamanızda herhangi bir yerden kontrol etmek için tek bir çağrı yapmanız yeterlidir:
use Laravel\Pennant\Feature;
if (Feature::active('new-checkout')) {
return view('checkout.new');
}
return view('checkout.legacy');
Feature::active() mevcut kullanıcıyı kontrol eder. Belirli bir kapsamı kontrol etmek için for() metodunu kullanın:
if (Feature::for($user)->active('new-checkout')) {
// ...
}
Blade dosyalarınızda, satır içi kontrollerden daha temiz bir görünüm sağlayan bir direktif vardır:
@feature('new-checkout')
@else
@endfeature
İşte tüm mutlu yol. Eğer Pennant sadece bu olsaydı, boole değerleri için daha güzel bir sentaks olurdu. Ancak, benimsenmesini değerli kılan, kapsam ve kalıcılık özellikleridir, aynı zamanda burada tuzağa düşen şeydir.
Herkesi Düşüren Tek Bir Şey
Herkesi Düşüren Tek Bir Şey
İşte, insanların şaşırdığı o davranış ve buna dikkat etmelisiniz çünkü aksi takdirde başınızı belaya sokacak.
Veritabanı sürücüsü ile bir flag’in değeri, her kapsam için bir kez çözülür ve sonra saklanır. Closure, belirli bir kullanıcı için flag’i kontrol ettiğinizde, ilk defa çalıştırılır. Sonrasında, Pennant bu değeri features tablosundan okuyacak ve closure’ınızı bir daha çalıştırmayacaktır.
Bir kez daha okuyun, çünkü sonuçlar çok bariz değil. Eğer bir flag’i %10 roller ile tanımladıysanız, kullanıcılarınız için kontrol edersiniz ve sonra closure’u %25’e değiştirdiğinizde, hiçbir şey olmaz. Daha önce çözüm üretilmiş kullanıcılar, ilk kez aldıkları değeri korur. Closure’daki düzenleme, artık değerlendirilen herkes için yok sayılır.
Bu bir özellik, hata değil. Kullanıcı deneyiminiz için tutarlı bir deneyim sunar: yeni checkout’ı alan bir kullanıcı, bir sonraki isteğinde bunu kaybetmez çünkü bir piyango yeniden korkutmadı. Ama bunu bilmediğinizde, ilk yüzde roll-out işleminiz tamamen bozuk görünecektir. Sayıyı arttırırsınız, yenilersiniz ve bir değişiklik görmezsiniz ve Pennant’ın sizi yok saydığını düşünürsünüz.
Cevap, saklanan değerleri temizlemektir, böylece kullanıcılar yeni closure’a karşı yeniden çözülür:
php artisan pennant:purge new-checkout
Artık her kullanıcı için bir sonraki kontrol yeni yüzdeye göre closure’ı yeniden çalıştırır. Bunu bir dağıtım adımı gibi düşünün: bir flag’in çözümleyici mantığını değiştirdiğinizde ve mevcut kullanıcıların yeniden değerlendirildiğinden emin olmak istediğinizde, bunu temizlemelisiniz. Bunun yanı sıra migration ile dağıtım sürecine script olarak yazıyorum, route:cache komutunu çalıştırdığınız gibi (tam komut referansı için Laravel Artisan Commands kılavuzu‘na göz atabilirsiniz).
Yüzde Rollout’ları ile Piyango
Yüzde Rollout’ları ile Piyango
Flag’leri kullanmanın en yaygın nedeni kademeli rollout’tur: %10 kullanıcıya ulaştırın, hata oranlarını izleyin ve sonra genişletin. Pennant, bunu yapmak için Laravel’ın Illuminate\Support\Lottery yardımcı kütüphanesini kullanır ve resmi Pennant belgeleri de aynı deseni kullanmaktadır.
use App\Models\User;
use Illuminate\Support\Lottery;
use Laravel\Pennant\Feature;
Feature::define('new-checkout', fn (User $user) => match (true) {
$user->isInternalTester() => true,
default => Lottery::odds(1, 10),
});
İç testçiler her zaman özelliği kazanır. Diğer herkesin 1’de 10 oranı vardır, bir kez çözülür ve sonra saklanır. Sonuç değerli olduğundan, kazanırsa, gelen her istekte özelliği korur, piyango yeniden devir aldıkça açılıp kapanmaz. O tutarlılık, bütün amacın bir parçasıdır. %25’e genişletme işlemi, oranları düzenleme ve pennant:purge çalıştırma ile birlikte gelir; böylece hem çözülmemiş hem de daha önce çözülmüş kullanıcılar yeni sayıya göre değerlendirilecektir.
Bu, insanların şaşırdığı tam etkileşim olduğundan, bağlamda bir kez daha söylemek önemlidir: closure’lar ve depolama birlikte çalışır. Closure, ilk seferde değeri belirler; depolama ardından bunu her zaman hatırlar. Rollout yüzdesindeki değişiklikler depolama işlemleri, sadece kod düzenlemeleri değildir.
Flag’leri Takımlara ve Hesaplara Göre Sınırlandırma
Flag’leri Takımlara ve Hesaplara Göre Sınırlandırma
Varsayılan kapsam, kimliği doğrulanmış kullanıcıdır; ancak birçok rollout, kişi başına değil takım başına ya da hesap başına gerçekleşir. Eğer bir firma üyesi yeni faturalama ekranını görürken, bir diğer görmüyorsa, bu bir destek bileti bekliyor demektir.
Takım seviyesinde sınırlandırmak için closure’da type-hint kullanarak bunu for()‘ya geçirebilirsiniz:
Feature::define('billing-v2', fn (Team $team) =>
in_array($team->plan, ['pro', 'enterprise'])
);
if (Feature::for($request->user()->currentTeam)->active('billing-v2')) {
// ...
}
Eğer sürekli for() yazmaktan sıkılırsanız, HasFeatures trait’inin varsayılan yöntemini uygulayabilir ya da servis sağlayıcınızda global bir varsayılan kapsam ayarlayabilirsiniz, böylece her kontrol otomatik olarak takım üzerinden çözülür. Bir SaaS uygulaması için, varsayılan kapsamı bir kez mevcut takıma ayarlamak, her çağrı noktasında for() kullanmaktan genellikle daha temizdir.
Kuyruklar ve Komutlarda Null Scope Tuzağı
Kuyruklar ve Komutlarda Null Scope Tuzağı
Bu, yapışkan depolama hatası ile bağlantılı ikinci şeyi meydana getirir ve bu, HTTP isteği dışında belirir.
Flag closure’ınız User ile type-hint’tir. Bu, bir controller içinde birinin giriş yaptığı durumda iyidir. Ancak aynı flag’i bir kuyruk işi, bir Artisan komutu veya kimliği doğrulanmamış bir rotada kontrol ederseniz, kimliği doğrulanmış bir kullanıcı yoktur, bu nedenle kapsam null olur. Kapsam null olduğunda ve closure’nız null kabul etmiyorsa, Pennant closure’ı tamamen atlar ve false döner.
Yani, tarayıcıda bir kullanıcı için aktif olan bir flag, o kullanıcı verilerini işleyen bir kuyruk işinde sessizce inaktif olarak görünür. İşinizin davranışı bu flag’e bağlıysa, gizlice yanlış bir dalı alır.
İki yolu vardır. Ya kimliği doğrulanmamış kullanıcı olmayan bağlamlarda kapsamı açıkça geçin:
// Kullanıcıyı bildiğiniz bir iş içinde
if (Feature::for($this->user)->active('new-checkout')) {
// ...
}
Ya da closure’ı null-safe yaparak tahmin edilebilir bir şekilde başarısız olmasını sağlayın:
Feature::define('new-checkout', fn (User $user = null) =>
$user?->created_at->isAfter(now()->subDays(30)) ?? false
);
Açık kapsam versiyonu benim tercihimdir, kullanıcıya bağlı bir flag’in her zaman hangi kullanıcıya bağlı olduğu belirtilmelidir. Ortamda doğrulanan kullanıcıya güvenmek, tarayıcı ve kuyruk çakışmasının oluşmasına neden olur.
Zengin Değerler: Doğru veya Yanlış Dönmeyen Flag’ler
Zengin Değerler: Doğru veya Yanlış Dönmeyen Flag’ler
Flag’ler sadece boolean olarak sınırlı değildir. Bir closure bir string dönebilir ve Feature::active() literal false dışında her şeyi aktif olarak değerlendirir. Bu, bir flag’i hafif bir A/B testine dönüştürür:
use Illuminate\Support\Arr;
Feature::define('purchase-button', fn () =>
Arr::random(['blue-sapphire', 'seafoam-green', 'tart-orange'])
);
Çözümlediğiniz değeri almak için aktif durumu kontrol etmekten daha önemlidir:
$color = Feature::value('purchase-button'); // 'seafoam-green'
Kapsam başına değerler yapışkandır, dolayısıyla her kullanıcı atanan varyantlarını istekler arasında korur ki bu da A/B testi için ideal bir ortam sağlar. Ayrı bir deney tablosu yok, çerez denklemi yok. Birden fazla flag almak için Feature::values(['billing-v2', 'purchase-button']) kullanabilir ya da mevcut kapsam için tanımlanan her şeyi almak için Feature::all() ile tüm bayrak setini frontend’inize tek bir yükle geçebilirsiniz.
Feature::all() ile ilgili tek bir uyarı: henüz istekte kontrol edilmemiş sınıf tabanlı özellikler görünmeyecek, çünkü Pennant sadece bunlar çözümlendiğinde bilmektedir. Eğer bir flag kontrol paneli oluşturuyorsanız veya bir Inertia sayfasını her flag ile besliyorsanız, henüz dokunulmadığından bazıları eksik olabilir.
Pennant ve Yönlendirme Meta Verisi
Pennant ve Yönlendirme Meta Verisi
Geçtiğimiz hafta, bir yönlendirme meta verisisinin bütün bir yönlendirme grubunu bir feature flag ile set ettiğini gösteriyordu, bu bir middleware ile feature anahtarını rotadan okuduydu. Pennant da bu düzen içinde coğrafyalanmaktadır. İşte tam versiyonu.
Yönlendirmeleri meta veri ile işaretleyin:
Route::metadata(['feature' => 'billing-v2'])
->prefix('billing')
->group(function () {
Route::get('https://dev.to/', [BillingController::class, 'index']);
Route::resource('invoices', InvoiceController::class);
});
Daha sonra bir middleware bu etiketi okur ve Pennant’dan sorar:
use Laravel\Pennant\Feature;
public function handle(Request $request, Closure $next): Response
{
$flag = $request->route()->getMetadata('feature');
if ($flag && !Feature::active($flag)) {
abort(404);
}
return $next($request);
}
Bunu web grubunda kaydedin, böylece her etiketli yönlendirme sıfır yönlendirme kodu ile korunur. Rollout’u silmek, sadece bir meta veri satırını silmek olur. İki özelliğin bir araya getirildiğinde kazancı budur: yönlendirme meta verisi, hangi flag’in bir yönlendirmeyi koruduğunu tanımlar, Pennant onun açık olup olmadığını belirler ve middleware aralarındaki bağlantıyı sağlayan beş satırdır. Bu, Feature::active() kontrollerini controller’lara dağıtmak yerine daha temiz bir ayrım sağlar.
Feature Flag’lerin Test Edilmesi
Feature Flag’lerin Test Edilmesi
Davranışları değiştiren flag’ler, her iki taraf için de test edilmelidir ve Pennant bir flag’in durumunu zorlamayı kolaylaştırır. Bir testte, tanımı yeniden yapın veya flag’i etkinleştirin:
use Laravel\Pennant\Feature;
it('shows the new checkout when the flag is on', function () {
Feature::define('new-checkout', fn () => true);
$this->actingAs($this->user)
->get('/checkout')
->assertViewIs('checkout.new');
});
it('shows the legacy checkout when the flag is off', function () {
Feature::define('new-checkout', fn () => false);
$this->actingAs($this->user)
->get('/checkout')
->assertViewIs('checkout.legacy');
});
Test suite’iniz array sürücüsünü kullanmalı, böylece hiçbir şey testler arasında kalıcı olmaz ve her biri temiz bir şekilde başlar. Flag’i testin başında yeniden tanımlamak, servis sağlayıcınızın ayarlarını geçersiz kılar, böylece hangi dalın çalıştığını kesin olarak kontrol edersiniz. Ayrıca, yapışkan depolama davranışının testlerde sizi asla ısırmamasının nedeni budur: array sürücüsü her istekten sonra her şeyi unutur.
Pennant’ın Yanlış Araç Olduğu Durumlar
Pennant’ın Yanlış Araç Olduğu Durumlar
Her togglenin flag paketi gerektirmediği için dürüst sınırlar gereklidir.
Eğer bir değer asla kullanıcıya göre değişmiyorsa ve asla kademeli olarak dağıtılmıyorsa, bu bir konfigurasyondur, feature flag değildir. config('services.stripe.enabled') Pennant’ta yer almaz. Pennant, bir togglenin kapsamda değiştiği veya kademeli olarak dağıtıldığı zaman kendine yer bulur, bir kez ayarladığınız global biranahtar için değil.
Flag değişikliklerinin bir panoda mühendis olmayanlar tarafından yönetilmesi gerekiyorsa, denetim günlükleri, planlı dağıtımlar ve çoklu ortam hedefleme gibi şeyler gerekiyorsa, LaunchDarkly gibi bir hizmet, Pennant’ın sağlanamadığı şeyleri yapmaktadır. Pennant bir kütüphanedir, bir ürün değil. Herhangi bir UI’ye sahip değil. Birçok ekip için bu nokta, tam tersi olmaktadır; ancak ürün yöneticilerinizin flag’leri kendi başlarına çevirmek ihtiyacı varsa, yalnızca Pennant ile o noktaya gelemeyebilirsiniz.
Ve uzun ömürlü dalları yönetmek için flag’lere başvuruyorsanız, flag, bir belirtinin tedavi edilmesini sağlayarak iyi sonuç vermez. Feature flag’leri en iyi tronka dayalı geliştirme ve küçük, kendine düşkün değişiklikler ile eşleştirir, her push gönderilir ve flag yalnızca görünürlük kontrol eder. Altı aylık bir dalın etrafında bir flag, ek adımlarla bir birleştirme çelişkisidir.
Ancak, çoğu durumda, gerçek kullanıcılar için özelliği kademeli olarak dağıtmak için kendi altyapınızda, aylık bir fatura olmadan, Pennant ile başka bir şeyle karşılaştırmak oldukça zor.
SSS
SSS
Pennant flag’inin closure’unu değiştirmek mevcut kullanıcıları günceller mi?
Hayır, ve bu neredeyse herkesin gözünden kaçar. Veritabanı sürücüsü ile, bir flag, her kapsamda bir kez çözülür ve sonucu saklar. Closure’ı düzenlemek, zaten çözümü yapılmış kullanıcıları yeniden değerlendirmez. Saklanan değerleri temizlemek ve yeni mantıkla yeniden çözülmelerini sağlamak için php artisan pennant:purge {flag} komutunu çalıştırın.
Neden bir queued job içinde feature flag’im false döner?
Çünkü kapsam null’dır. İşler, Artisan komutları ve kimliği doğrulanmamış rotalar, kimliği doğrulanmış kullanıcıya sahip olmadıkları için, closure’ınız (null kabul etmeden) atlanır ve Pennant false döner. Feature::for($user)->active(...) ile açıkça kapsamı geçin ya da closure’ı nullable yapın.
Pennant’ın array ve veritabanı sürücüleri arasındaki fark nedir?
database sürücüsü, çözümlediği değerleri features tablosunda saklar, böylece bunlar isteklere ve dağıtımlara karşı kalıcıdır. Bu, üretim varsayılanıdır. array sürücüsü, değerleri yalnızca bir istek için bellekte tutar; bu uygulama testleri sırasında hiçbir şeyin sızmasını istemezsiniz.
Pennant yüzde bazlı rollout’lar yapabilir mi?
Evet. Var olan bir flag’i yüzde kadar aktif hale getirmek için closure içinde Illuminate\Support\Lottery‘yı kullanın. Ancak, bir kez çözülünce değerlerin yapışkan olduğunu unutmayın; dolayısıyla, mevcut kullanıcıların yeniden değerlendirilmesi için daha sonra yüzdeleri artırdığınızda pennant:purge çalıştırmanız gerekecek.
Ayrı bir hizmete, örneğin LaunchDarkly’ye ihtiyacım var mı?
Çoğu Laravel uygulaması için hayır. Pennant, kapsamlı flag’ler, zengin değerler ve kademeli dağıtım yaratmayı uygun olan bir yöntem sunar, bunun için harici bir bağımlılığa gerek yoktur. Mühendis olmayanlar için bir panoya, denetim günlüklerine veya karmaşık çoklu ortam hedeflemeye ihtiyacınız olduğunda, Pennant bilerek göz ardı eder.
Sonuç
Sonuç
Pennant, bir öğleden sonra içinde öğrenebileceğiniz kadar küçük ve sonsuza kadar tutmak için yeterince kullanışlıdır. API basit bir kısımdır. Gerçekten önemli olan ve eğitimlerin geçiştirdiği iki husus, değerlerin kapsam başına yapışkan olması ve bir iş veya komut içinde null kapsamın sessizce false döndürmesidir. Bu ikisini kas hafızanıza yerleştirin ve başka her şey Feature::active() ile ilgilidir.
Başlangıç olarak en çirkin .env boole’unuzu gerçek bir flag ile değiştirin. İlkini ekledikten ve bir dağıtımın nasıl davrandığını izledikten sonra, configurasyon dosyasının daha önce bu mantığı neden elinde tuttuğunu sorgulayacaksınız.
Kaynak: Orijinal Makale
- Pennant Kurulumu
- İlk Flag’inizi Tanımlama
- Herkesi Düşüren Tek Bir Şey
- Yüzde Rollout’ları ile Piyango
- Flag’leri Takımlara ve Hesaplara Göre Sınırlandırma
- Kuyruklar ve Komutlarda Null Scope Tuzağı
- Zengin Değerler: Doğru veya Yanlış Dönmeyen Flag’ler
- Pennant ve Yönlendirme Meta Verisi
- Feature Flag’lerin Test Edilmesi
- Pennant’ın Yanlış Araç Olduğu Durumlar
- SSS
- Sonuç


