TungDaDev's Blog

feature flags với unleash trong spring boot: nghệ thuật triển khai không downtime

Feature flags unleash.jpg
Published on
/6 mins read/

Triển khai code lên máy chủ (Deploy) và Bật tính năng cho người dùng (Release) là hai việc hoàn toàn khác nhau. Một đội ngũ kỹ thuật trưởng thành luôn biết cách tách rời hai khái niệm này.

Hãy tưởng tượng kịch bản quen thuộc này trong đội ngũ của bạn:

  • Nhóm bạn phát triển một tính năng thanh toán mới kéo dài suốt 2 tháng trên nhánh feature/new-payment.
  • Trong 2 tháng đó, nhánh main đã có hàng trăm commit khác được merge vào.
  • Đến ngày bàn giao: Bạn ngồi giải quyết Merge Conflicts suốt 2 ngày trời trong mồ hôi và nước mắt.
  • Khi deploy lên production lúc nửa đêm: Tính năng gặp lỗi nghiêm trọng với một ngân hàng đối tác. Toàn bộ team cuống cuồng tìm cách Rollback mã nguồn, kéo theo sự cố gián đoạn dịch vụ của cả công ty!

Tại sao chúng ta phải tự làm khổ mình như vậy?

Cộng đồng vnJUG và các công ty công nghệ hàng đầu thế giới (Netflix, Spotify, Uber) giải quyết triệt để bài toán này bằng hai vũ khí song hành: Trunk-Based Development và Feature Flags (Cờ tính năng) với nền tảng mã nguồn mở Unleash.


# tách rời deployment khỏi release

Trong mô hình truyền thống, mỗi khi bạn deploy một bản build mới, toàn bộ tính năng mới trong bản build đó sẽ lập tức xuất hiện trước mặt người dùng:

\text{Deploy Code} \equiv \text{Release Feature}

Với Feature Flags, ranh giới này được phân tách rạch ròi:

\text{Deploy Code} \neq \text{Release Feature}

Bạn có thể merge code vào main và deploy lên Production mỗi ngày 10 lần. Nhưng tính năng đó chỉ thực sự thức giấc khi Product Manager hoặc Tech Lead gạt công tắc (Toggle) trên giao diện quản trị!


# tại sao lại chọn unleash cho hệ sinh thái java / spring boot?

Trong khi LaunchDarkly rất đắt đỏ, Unleash là giải pháp mã nguồn mở (Open Source) phổ biến nhất thế giới hiện nay:

  • Tự host (Self-hosted) an toàn: Dữ liệu người dùng và cấu hình cờ nằm trọn vẹn trong mạng nội bộ của doanh nghiệp, tuân thủ nghiêm ngặt chuẩn bảo mật tài chính.
  • Hiệu năng cấp độ Microsecond: Unleash SDK trên Java duy trì một bản sao cờ trong bộ nhớ RAM cục bộ (Local Cache) và đồng bộ bất đồng bộ qua background thread. Việc gọi unleash.isEnabled("my-feature") chỉ mất vài nano-giây, hoàn toàn không tạo ra network call nào!
  • Hỗ trợ chiến lược kích hoạt đa dạng:
    • Gradual Rollout: Bật cho X\% người dùng ngẫu nhiên.
    • User ID / Email targeting: Chỉ bật cho email @company.com.
    • Tenant / Corporate targeting: Chỉ bật cho các chi nhánh ngân hàng cụ thể.

# hướng dẫn tích hợp unleash vào spring boot 3

1. Khai báo Dependency (pom.xml)

<dependency>
    <groupId>io.getunleash</groupId>
    <artifactId>unleash-client-java</artifactId>
    <version>9.2.4</version>
</dependency>

2. Cấu hình Unleash Bean trong Spring Boot

package com.tungdadev.config;
 
import io.getunleash.DefaultUnleash;
import io.getunleash.Unleash;
import io.getunleash.util.UnleashConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
@Configuration
public class UnleashConfiguration {
 
    @Value("${unleash.api-url:http://localhost:4242/api}")
    private String apiUrl;
 
    @Value("${unleash.api-token:default:development.unleash-insecure-api-token}")
    private String apiToken;
 
    @Bean
    public Unleash unleash() {
        UnleashConfig config = UnleashConfig.builder()
                .appName("ecommerce-service")
                .instanceId("instance-" + System.getenv("HOSTNAME"))
                .unleashAPI(apiUrl)
                .apiKey(apiToken)
                .synchronousFetchOnInitialisation(true) // Lấy cờ ngay khi khởi động
                .sendMetricsInterval(60)              // Gửi metric sử dụng sau mỗi 60s
                .build();
 
        return new DefaultUnleash(config);
    }
}

3. Sử dụng Unleash Context với User ID & Phân Quyền

Để kích hoạt tính năng theo từng người dùng (Canary Release theo User ID):

package com.tungdadev.service;
 
import io.getunleash.Unleash;
import io.getunleash.UnleashContext;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
 
@Service
@RequiredArgsConstructor
public class CheckoutService {
 
    private final Unleash unleash;
    private final LegacyPaymentGateway legacyPaymentGateway;
    private final FastCheckoutGateway fastCheckoutGateway;
 
    public PaymentResult processPayment(String userId, PaymentRequest request) {
        // Tạo ngữ cảnh người dùng hiện tại
        UnleashContext context = UnleashContext.builder()
                .userId(userId)
                .addProperty("tier", request.getUserTier()) // Free hay VIP
                .build();
 
        // Kiểm tra xem cờ 'enable-fast-checkout' có bật cho user này không
        if (unleash.isEnabled("enable-fast-checkout", context)) {
            log.info("Người dùng {} được trải nghiệm tính năng Thanh Toán Nhanh mới!", userId);
            return fastCheckoutGateway.execute(request);
        } else {
            return legacyPaymentGateway.execute(request);
        }
    }
}

# 3 cạm bẫy cần tránh khi dùng feature flags

Feature Flags mang lại sự tự do phi thường, nhưng nếu lạm dụng, bạn sẽ biến codebase của mình thành một "bãi tha ma" chứa đầy các câu lệnh if...else:

  1. Nợ kỹ thuật từ Cờ mồ côi (Stale Flags): Khi một tính năng đã được bật cho 100% người dùng và chạy ổn định suốt 2 tuần, hãy tạo ngay một Jira ticket để xóa bỏ Feature Flag đó khỏi code. Đừng để các khối code cũ tồn tại mãi mãi trong ứng dụng!
  2. Thiếu Fallback an toàn: Luôn luôn tự hỏi: "Nếu server Unleash bị sập mạng hoặc không phản hồi, giá trị mặc định của cờ là gì?". Hãy luôn truyền tham số fallback: unleash.isEnabled("my-flag", false).
  3. Bùng nổ tổ hợp kiểm thử (Combinatorial Explosion): Nếu bạn có 10 cờ lồng nhau trong cùng một hàm, bạn sẽ có 2^{10} = 1.024 trạng thái có thể xảy ra! Hãy cố gắng giữ các cờ độc lập ở tầng bề mặt (Controller / Entrypoint) thay vì nhét sâu vào tầng Repository.

# tổng kết

"Dũng cảm không phải là việc bạn dám deploy code vào tối thứ Sáu rồi cầu nguyện. Dũng cảm thực sự là bạn biết chắc rằng nếu có bất kỳ sự cố nào xảy ra, bạn chỉ cần 1 cú click chuột để vô hiệu hóa tính năng đó trong vòng 1 giây."

Sự kết hợp giữa Trunk-Based Development và Unleash Feature Flags chính là bệ phóng đưa các đội ngũ kỹ thuật phần mềm thoát khỏi nỗi sợ hãi ngày release, tự tin triển khai liên tục (CI/CD) và mang lại giá trị nhanh nhất cho người dùng doanh nghiệp.


Chỉ là những ghi chép cá nhân với hy vọng mang lại chút giá trị. Nếu thấy hữu ích, đừng ngại chia sẻ cho bạn bè & đồng nghiệp nhé!

Happy coding 😎 👍🏻 🚀 🔥.