Quản lý tính năng (Feature Flags) với Laravel Pennant

· 5 min read

Trong môi trường phát triển hiện đại, việc deploy code lên production thường xuyên (CI/CD) là tiêu chuẩn. Nhưng làm sao để merge code tính năng mới lên nhánh chính (main) mà không làm ảnh hưởng người dùng khi tính năng đó chưa hoàn thiện? Hoặc làm sao để chỉ bật tính năng mới cho đội ngũ Beta Tester?

Câu trả lời là Feature Flags (cờ tính năng).

Laravel Pennant là một first-party package (gói chính chủ) giúp quản lý Feature Flags cực kỳ đơn giản, nhẹ nhàng và chuẩn bài Laravel.

Tại sao cần Feature Flags?

  1. Trunk-based Development: Developer merge code hàng ngày, ẩn code chưa xong sau một cái cờ FALSE.
  2. Canary Releases: Bật tính năng mới cho 1% user để theo dõi lỗi trước khi bung cho 100%.
  3. A/B Testing: Hiển thị giao diện A cho nhóm này, B cho nhóm kia để đo lường hiệu quả.
  4. Kill Switch: Tắt ngay lập tức một tính năng bị lỗi mà không cần rollback code.

Cài đặt Pennant

composer require laravel/pennant

php artisan vendor:publish --provider="Laravel\Pennant\PennantServiceProvider"
php artisan migrate

Mặc định, Pennant lưu flags trong database. Nó cũng hỗ trợ redis hoặc array (cho testing).

Định nghĩa Feature

Thông thường, bạn định nghĩa các tính năng trong AppServiceProvider:

use Laravel\Pennant\Feature;
use App\Models\User;

public function boot(): void
{
    // Flag đơn giản: bật hoặc tắt cho tất cả
    Feature::define('new-dashboard', fn (User $user) => match ($user->role) {
        'admin', 'beta-tester' => true,
        default => false,
    });

    // Lottery: Ngẫu nhiên cho 10% user
    Feature::define('site-redesign', fn (User $user) => \Laravel\Pennant\Feature::lottery(1 in: 10));
}

Các tính năng này cũng có thể được định nghĩa bằng Class riêng nếu logic phức tạp.

Sử dụng trong Code

Trong Blade View

Helper @feature giúp code view cực sạch:

@feature('new-dashboard')
    <!-- Giao diện mới -->
    <x-dashboard-v2 />
@else
    <!-- Giao diện cũ -->
    <x-dashboard-v1 />
@endfeature

Trong Controller

public function index()
{
    if (Feature::active('new-dashboard')) {
        return view('dashboard.v2');
    }

    return view('dashboard.v1');
}

Middleware

Pennant cung cấp middleware để bảo vệ route:

Route::get('/analytics', AnalyticsController::class)
    ->middleware('features:view-analytics');

Class Based Features

Với các tính năng lớn, nên dùng Class để quản lý state tốt hơn.

php artisan pennant:feature NewBillingSystem

Class này sẽ có phương thức resolve. Giá trị trả về không nhất thiết là boolean. Nó có thể là string, number, hoặc object (Rich Feature).

// app/Features/NewBillingSystem.php
public function resolve(User $user)
{
    return $user->isPremium() ? 'stripe' : 'paypal';
}

Cách dùng:

$gateway = Feature::value(NewBillingSystem::class); // trả về 'stripe' hoặc 'paypal'

Testing với Pennant

Một điểm mạnh của gói chính chủ là hỗ trợ test tận răng.

public function test_it_shows_new_dashboard_when_enabled()
{
    Feature::activate('new-dashboard');

    $response = $this->get('/dashboard');

    $response->assertSee('Welcome to Dashboard V2');
}

Chiến lược Rollout

Pennant cực kỳ mạnh mẽ khi kết hợp với logic nghiệp vụ.

Ví dụ, bạn muốn bật tính năng AI Chat cho:

  1. Admin nội bộ.
  2. Khách hàng gói "Enterprise".
  3. Các user cũ đăng ký trước 2024.

Thay vì viết đống if/else rải rác khắp nơi, bạn gom nó vào một chỗ duy nhất trong Feature::define. Code base của bạn sạch sẽ, dễ bảo trì, và quan trọng nhất là bạn có thể bật/tắt toàn bộ logic đó chỉ bằng một dòng code mà không cần deploy lại (nếu dùng Driver database và có UI quản lý flags).

Tổng kết

Laravel Pennant biến việc quản lý Feature Flags từ một công việc thủ công, dễ lỗi thành một quy trình chuẩn hóa. Nếu bạn đang làm product có người dùng thật, hãy cân nhắc áp dụng Pennant ngay để quy trình release trở nên tự tin hơn.

Bình luận