将 App Lifecycle Manager 功能标志与 Java 搭配使用

本指南介绍了如何将 Java 应用与 App Lifecycle Manager 功能标志集成。您将了解如何将 OpenFeature SDK 与 flagd 提供程序搭配使用,以评估由 App Lifecycle Manager 管理的标志,从而动态控制 Java 服务中的功能可用性和行为。

本指南假定您已完成以下任一快速入门,设置了必要的 App Lifecycle Manager 资源(例如 SaaS 产品、单元类型、单元、标志和发布):

前提条件

在开始之前,请确保您具备以下条件:

  1. 已安装 Java: JDK 17 或更高版本。
  2. 已完成 App Lifecycle Manager 功能标志快速入门
    • 已成功完成 集成独立 功能标志快速入门,以预配目标单元。
    • 您应该拥有该快速入门中的环境变量或值,例如 PROJECT_IDLOCATION_1(单元的区域)、UNIT_IDFLAG_KEY
  3. 已针对应用默认凭据 (ADC) 进行了 gcloud 身份验证: Java 应用使用 ADC 向 Google Cloud 服务进行身份验证。确保您已在环境中进行身份验证:

    gcloud auth application-default login
    
  4. IAM 权限: 运行 Java 应用的身份需要对您的 Google Cloud 项目拥有 roles/saasconfig.viewer Identity and Access Management 角色,才能读取标志配置:

    gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \
        --member="user:YOUR_EMAIL_ADDRESS" \
        --role="roles/saasconfig.viewer"
    

    请相应地替换 YOUR_PROJECT_IDYOUR_EMAIL_ADDRESS

了解关键环境变量

您的 Java 应用依赖于环境变量来连接到正确的标志配置。

  • FLAGD_SOURCE_PROVIDER_ID:指定要从 App Lifecycle Manager 服务中提取哪个功能标志配置。它必须是与您的单元关联的 FeatureFlagConfig 的完整资源名称。
    • 格式: projects/PROJECT_ID/locations/LOCATION/featureFlagsConfigs/UNIT_ID
    • 示例projects/my-gcp-project/locations/us-central1/featureFlagsConfigs/my-app-instance-01

设置 Java 项目

在运行应用之前,您必须先初始化项目并配置必要的依赖项。

  1. 创建项目目录

    mkdir java-featureflag-app
    cd java-featureflag-app
    
  2. 添加依赖项: 配置构建系统(Maven 或 Gradle),以包含以下库。请使用高于 OpenFeature 1.14.1、flagd 0.11.5 和 gRPC 1.71.0 的版本,以确保兼容性。

    • dev.openfeature:javasdk:1.14.1+
    • dev.openfeature.contrib.providers:flagd:0.11.5+
    • io.grpc:grpc-netty-shaded:1.71.0+
    • io.grpc:grpc-auth:1.71.0+
    • com.google.auth:google-auth-library-oauth2-http
  3. 创建初始化代码: 添加一个名为 FeatureManagement.java 且包含以下内容的文件。此类设置 gRPC 拦截器以附加 ADC 和区域路由标头,并使用进程内解析初始化 FlagdProvider

    import com.google.auth.oauth2.GoogleCredentials;
    import dev.openfeature.contrib.providers.flagd.Config;
    import dev.openfeature.contrib.providers.flagd.FlagdOptions;
    import dev.openfeature.contrib.providers.flagd.FlagdProvider;
    import dev.openfeature.sdk.OpenFeatureAPI;
    import io.grpc.*;
    import io.grpc.auth.MoreCallCredentials;
    import java.util.ArrayList;
    import java.util.List;
    
    public class FeatureManagement {
        public static void initialize() throws Exception {
            // Read the full Unit resource name
            String flagConfigId = System.getenv("FLAGD_SOURCE_PROVIDER_ID");
            if (flagConfigId == null || flagConfigId.isEmpty()) {
                throw new IllegalStateException("FLAGD_SOURCE_PROVIDER_ID environment variable is not set.");
            }
    
            // Configure gRPC Security and Routing
            GoogleCredentials credentials = GoogleCredentials.getApplicationDefault();
            CallCredentials callCredentials = MoreCallCredentials.from(credentials);
    
            List<ClientInterceptor> interceptors = new ArrayList<>();
    
            // Interceptor to inject the Unit name into headers for regional routing
            interceptors.add(new ClientInterceptor() {
                @Override
                public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
                    return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(next.newCall(method, callOptions)) {
                        @Override
                        public void start(Listener<RespT> responseListener, Metadata headers) {
                            headers.put(Metadata.Key.of("x-goog-request-params", Metadata.ASCII_STRING_MARSHALLER), "name=" + flagConfigId);
                            super.start(responseListener, headers);
                        }
                    };
                }
            });
    
            // Interceptor to attach ADC to the gRPC calls
            interceptors.add(new ClientInterceptor() {
                @Override
                public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
                    return next.newCall(method, callOptions.withCallCredentials(callCredentials));
                }
            });
    
            // Initialize the flagd provider with In-Process resolution
            FlagdProvider provider = new FlagdProvider(
                FlagdOptions.builder()
                    .resolverType(Config.Resolver.IN_PROCESS)
                    .host("saasconfig.googleapis.com").port(443)
                    .tls(true)
                    .providerId(flagConfigId)
                    .clientInterceptors(interceptors)
                    .syncMetadataDisabled(true)
                    .deadline(10000)
                    .build()
            );
    
            // Set the global provider
            OpenFeatureAPI.getInstance().setProviderAndWait(provider);
        }
    }
    
  4. 创建应用逻辑: 添加一个名为 Main.java 且包含以下内容的文件,以执行标志评估。

    import dev.openfeature.sdk.OpenFeatureAPI;
    import dev.openfeature.sdk.MutableContext;
    
    public class Main {
        public static void main(String[] args) throws Exception {
    
        // 1. Get Client
        var client = OpenFeatureAPI.getInstance().getClient("simple-api");
    
        // 2. Create Evaluation Context 
        var context = new dev.openfeature.sdk.MutableContext();
    
        // 3. Evaluate Flag
        // Default to false if evaluation fails or service is unreachable
        boolean isEnhanced = client.getBooleanValue("enhanced-search", false, context);
    
        // 4. Execute business logic based on result
        if (isEnhanced) {
            System.out.println("Executing enhanced search algorithm...");
        } else {
            System.out.println("Standard search algorithm...");
        }
    
        }
    }
    

运行 Java 应用

您可以使用独立配置在本地执行应用,也可以将应用部署为容器。

独立运行(本地)

  1. 设置环境变量

    export PROJECT_ID="your-gcp-project-id"
    export LOCATION_1="us-central1"
    export UNIT_ID="my-app-instance-01"
    export FLAGD_SOURCE_PROVIDER_ID="projects/${PROJECT_ID}/locations/${LOCATION_1}/featureFlagsConfigs/${UNIT_ID}"
    
  2. 运行应用: 使用您首选的构建工具命令(例如 mvn compile exec:java./gradlew run)执行项目。

集成运行(容器化)

  1. 创建 Dockerfile: 定义用于打包 Java 二进制文件的容器构建。

    FROM maven:3.9-eclipse-temurin-17 AS builder
    WORKDIR /app
    COPY pom.xml .
    COPY src ./src
    RUN mvn clean package
    
    FROM eclipse-temurin:17-jre
    WORKDIR /app
    COPY --from=builder /app/target/java-featureflag-app.jar app.jar
    ENTRYPOINT ["java", "-jar", "app.jar"]
    
  2. 构建并推送映像

    export PROJECT_ID="your-gcp-project-id"
    docker build -t gcr.io/${PROJECT_ID}/my-java-app:latest .
    docker push gcr.io/${PROJECT_ID}/my-java-app:latest
    
  3. 部署服务: 更新单元蓝图以引用 gcr.io/${PROJECT_ID}/my-java-app:latest,并使用 App Lifecycle Manager 发布部署新版本。

验证标志更改

应用运行后,请确认其可以正确评估标志。

  1. 观察初始值: 验证控制台输出是否记录了与初始配置状态对应的评估值。
  2. 在 App Lifecycle Manager 中更新标志: 使用 Google Cloud 控制台或 gcloud 修改标志载荷或定位规则。
  3. 验证更新后的值: 重新运行本地执行或观察容器日志,以确认新的评估状态。

后续步骤