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

En esta guía, se explica cómo integrar tus aplicaciones de Go 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 Go.

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. Go instalado: Versión 1.23 o posterior.
  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 Go usa ADC para autenticarse con los Google Cloud servicios. Si ejecutas de forma local (para uso independiente o pruebas locales de Docker), 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 Go 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 Go depende de las variables de entorno para conectarse a la configuración de marcas correcta y evaluar la marca deseada.

  • 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.
    • Formato: 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 Go

Antes de ejecutar tu aplicación, inicializa tu módulo y agrega la lógica de inicialización y evaluación necesaria.

  1. Crea un directorio de proyectos:

    mkdir go-featureflag-app
    cd go-featureflag-app
    go mod init go-featureflag-app
    
  2. Crea código de la aplicación: Agrega un archivo llamado main.go con el siguiente contenido para inicializar el proveedor y evaluar una marca.

    package main
    
    import (
        "context"
        "fmt"
        "log"
        "os"
    
        flagd "github.com/open-feature/go-sdk-contrib/providers/flagd/pkg"
        "github.com/open-feature/go-sdk/openfeature"
        "google.golang.org/grpc"
        "google.golang.org/grpc/credentials"
        "google.golang.org/grpc/credentials/oauth"
        "google.golang.org/grpc/metadata"
    )
    
    // GetNewFlagdProvider initializes a flagd provider configured for  App Lifecycle Manager
    func GetNewFlagdProvider(ctx context.Context) (*flagd.Provider, error) {
        providerID := os.Getenv("FLAGD_SOURCE_PROVIDER_ID")
        if providerID == "" {
            return nil, fmt.Errorf("FLAGD_SOURCE_PROVIDER_ID environment variable is not set")
        }
    
        creds, err := oauth.NewApplicationDefault(ctx)
        if err != nil {
            return nil, err
        }
    
        // Interceptor to inject the Unit name into headers for regional routing
        routingInterceptor := func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
            md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("name=%s", providerID))
            ctx = metadata.NewOutgoingContext(ctx, md)
            return streamer(ctx, desc, cc, method, opts...)
        }
    
        options := []flagd.ProviderOption{
            flagd.WithHost("saasconfig.googleapis.com"),
            flagd.WithPort(443),
            flagd.WithInProcessResolver(),
            flagd.WithProviderID(providerID),
            flagd.WithGrpcDialOptionsOverride([]grpc.DialOption{
                grpc.WithTransportCredentials(credentials.NewTLS(nil)),
                grpc.WithPerRPCCredentials(creds),
                grpc.WithStreamInterceptor(routingInterceptor),
            }),
        }
    
        return flagd.NewProvider(options...)
    }
    
    // InitializeFeatureManagement registers the provider globally
    func InitializeFeatureManagement(ctx context.Context) error {
        provider, err := GetNewFlagdProvider(ctx)
        if err != nil {
            return err
        }
        openfeature.SetProvider(provider)
        return nil
    }
    
    func main() {
    ctx := context.Background()
    InitializeFeatureManagement(ctx);
    
    // 1. Get Client
    client := openfeature.NewClient("simple-api")
    
    // 2. Create Evaluation Context
    evalCtx := openfeature.NewEvaluationContext()
    
    // 3. Evaluate Flag
    // Default to false if evaluation fails
    isEnhanced, err := client.BooleanValue(context.Background(), "enhanced-search", false, evalCtx)
    if err != nil {
    log.Printf("Flag evaluation failed: %v", err)
    }
    
    // 4. Return response
    if isEnhanced {
    fmt.Println("Enhanced search feature is enabled.")
    } else {
    fmt.Println("Enhanced search feature is disabled.")
    }
    
    }
    
  3. Crea go.mod: El uso de versiones específicas garantiza la estabilidad.

    module go-featureflag-app
    
    go 1.23
    
    require (
        github.com/open-feature/go-sdk v1.15.1
        github.com/open-feature/go-sdk-contrib/providers/flagd v0.3.0
        google.golang.org/grpc v1.74.2
        golang.org/x/oauth2 v0.22.0
    )
    
  4. Obtén dependencias:

    go mod tidy
    go mod download
    

Ejecuta la aplicación de Go

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:

    go run main.go
    

Ejecuta de forma integrada (en contenedores)

  1. Crea un Dockerfile:

    FROM golang:1.23 as builder
    WORKDIR /app
    COPY go.mod go.sum ./
    RUN go mod download
    COPY . .
    RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /go-featureflag-app .
    
    FROM gcr.io/distroless/static-debian11
    WORKDIR /
    COPY --from=builder /go-featureflag-app /go-featureflag-app
    ENTRYPOINT ["/go-featureflag-app"]
    
  2. Compila y envía la imagen:

    export PROJECT_ID="your-gcp-project-id"
    docker build -t gcr.io/${PROJECT_ID}/my-go-app:latest .
    docker push gcr.io/${PROJECT_ID}/my-go-app:latest
    
  3. Implementa el servicio: Actualiza tu plantilla de unidad para hacer referencia a gcr.io/${PROJECT_ID}/my-go-app:latest y, luego, implementa la nueva versión con un lanzamiento de App Lifecycle Manager.

Verifica los cambios de marcas

Después de que se ejecute tu aplicación, confirma que evalúe las marcas correctamente.

  1. Observa el valor inicial: Verifica que el resultado registre el valor correspondiente al estado inicial.
  2. Actualiza la marca en App Lifecycle Manager: Modifica el comportamiento de la marca con Google Cloud la consola o gcloud.
  3. Verifica el valor actualizado: Vuelve a ejecutar la compilación local o observa los registros del contenedor para ver el resultado actualizado.

¿Qué sigue?