Cómo usar marcas de funciones de App Lifecycle Manager con Java

En esta guía, se explica cómo integrar tus aplicaciones de Java con las marcas de funciones de App Lifecycle Manager. Aprenderás a usar el SDK de OpenFeature con el proveedor flagd para evaluar las marcas administradas por App Lifecycle Manager, lo que te permitirá controlar de forma dinámica la disponibilidad y el comportamiento de las funciones en tus servicios de Java.

En esta guía, se supone que ya configuraste los recursos necesarios de App Lifecycle Manager (como ofertas de SaaS, tipos de unidades, unidades, marcas y lanzamientos) completando una de las siguientes guías de inicio rápido:

Requisitos previos

Antes de comenzar, asegúrate de tener lo siguiente:

  1. Java instalado: JDK 17 o versiones posteriores.
  2. Completaste una guía de inicio rápido de marcas de funciones de App Lifecycle Manager:
    • Completaste correctamente la guía de inicio rápido de marcas de funciones integradas o independientes para aprovisionar tu unidad de destino.
    • Debes tener variables o valores de entorno de esa guía de inicio rápido, como PROJECT_ID, LOCATION_1 (la región de tu unidad), UNIT_ID y FLAG_KEY.
  3. Autenticación de gcloud para las credenciales predeterminadas de la aplicación (ADC): La aplicación de Java usa ADC para autenticarse con Google Cloud los servicios. Asegúrate de haberte autenticado en tu entorno:

    gcloud auth application-default login
    
  4. Permisos de IAM: La identidad que ejecuta tu aplicación de Java necesita el rol de Identity and Access Management roles/saasconfig.viewer en tu Google Cloud proyecto para leer las configuraciones de marcas:

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

    Reemplaza YOUR_PROJECT_ID y YOUR_EMAIL_ADDRESS según corresponda.

Comprende las variables de entorno clave

Tu aplicación de Java depende de las variables de entorno para conectarse a la configuración de marcas correcta.

  • FLAGD_SOURCE_PROVIDER_ID: Especifica qué configuración de marcas de funciones recuperar del servicio de App Lifecycle Manager. Debe ser el nombre completo de recurso de FeatureFlagConfig asociado con tu unidad.
    • Format: projects/PROJECT_ID/locations/LOCATION/featureFlagsConfigs/UNIT_ID
    • Ejemplo: projects/my-gcp-project/locations/us-central1/featureFlagsConfigs/my-app-instance-01

Configura tu proyecto de Java

Antes de ejecutar la aplicación, debes inicializar el proyecto y configurar las dependencias necesarias.

  1. Crea un directorio de proyectos:

    mkdir java-featureflag-app
    cd java-featureflag-app
    
  2. Agrega dependencias: Configura tu sistema de compilación (Maven o Gradle) para incluir las siguientes bibliotecas. Para garantizar la compatibilidad, usa versiones superiores a OpenFeature 1.14.1, flagd 0.11.5 y 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. Crea el código de inicialización: Agrega un archivo llamado FeatureManagement.java con el siguiente contenido. Esta clase configura los interceptores de gRPC para adjuntar ADC y encabezados de enrutamiento regionales, y, luego, inicializa FlagdProvider con la resolución en proceso.

    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. Crea la lógica de la aplicación: Agrega un archivo llamado Main.java con el siguiente contenido para ejecutar la evaluación de marcas.

    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...");
        }
    
        }
    }
    

Ejecuta la aplicación de Java

Puedes ejecutar la aplicación de forma local con tu configuración independiente o implementarla como un contenedor.

Ejecuta de forma independiente (local)

  1. Configura las variables de entorno:

    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. Ejecuta la aplicación: Ejecuta el proyecto con el comando de la herramienta de compilación que prefieras (p.ej., mvn compile exec:java o ./gradlew run).

Ejecuta de forma integrada (en contenedores)

  1. Crea un Dockerfile: Define una compilación de contenedor que empaquete el objeto binario de 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. Compila y envía la imagen:

    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. Implementa el servicio: Actualiza tu plantilla de unidad para hacer referencia a gcr.io/${PROJECT_ID}/my-java-app:latest y, luego, implementa la nueva versión con un lanzamiento de App Lifecycle Manager.

Verifica los cambios de marcas

Una vez que se ejecute la aplicación, confirma que evalúe las marcas correctamente.

  1. Observa el valor inicial: Verifica que el resultado de la consola registre el valor evaluado correspondiente al estado de configuración inicial.
  2. Actualiza la marca en App Lifecycle Manager: Modifica la carga útil de la marca o las reglas de segmentación con Google Cloud la consola o gcloud.
  3. Verifica el valor actualizado: Vuelve a ejecutar la ejecución local o observa los registros del contenedor para confirmar el nuevo estado evaluado.

¿Qué sigue?