Use App Lifecycle Manager feature flags with Go

This guide explains how to integrate your Go applications with App Lifecycle Manager feature flags. You'll learn how to use the OpenFeature SDK with the flagd provider to evaluate flags managed by App Lifecycle Manager, allowing you to dynamically control feature availability and behavior in your Go services.

This guide assumes you have already set up the necessary App Lifecycle Manager resources (like SaaS offerings, unit kinds, units, flags, and rollouts) by completing one of the following quickstarts:

Prerequisites

Before you begin, ensure you have the following:

  1. Go installed: Version 1.23 or later.
  2. Completed an App Lifecycle Manager feature flags Quickstart:
    • Successfully completed either the integrated or standalone feature flags quickstart to provision your target unit.
    • You should have environment variables or values from that quickstart, such as PROJECT_ID, LOCATION_1 (the region of your Unit), UNIT_ID, and FLAG_KEY.
  3. Authenticated gcloud for Application Default Credentials (ADC): The Go application uses ADC to authenticate with Google Cloud services. If running locally (for standalone usage, or local Docker testing), ensure you've authenticated in your environment:

    gcloud auth application-default login
    
  4. IAM Permissions: The identity running your Go application needs the roles/saasconfig.viewer Identity and Access Management role on your Google Cloud project to read flag configurations:

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

    Replace YOUR_PROJECT_ID and YOUR_EMAIL_ADDRESS accordingly.

Understand key environment variables

Your Go application relies on environment variables to connect to the correct flag configuration and evaluate the intended flag.

  • FLAGD_SOURCE_PROVIDER_ID: Specifies which feature flag configuration to fetch from the App Lifecycle Manager service. It must be the full resource name of the FeatureFlagConfig associated with your unit.
    • Format: projects/PROJECT_ID/locations/LOCATION/featureFlagsConfigs/UNIT_ID
    • Example: projects/my-gcp-project/locations/us-central1/featureFlagsConfigs/my-app-instance-01

Set up your Go project

Before you can run your application, initialize your module and add the necessary initialization and evaluation logic.

  1. Create a project directory:

    mkdir go-featureflag-app
    cd go-featureflag-app
    go mod init go-featureflag-app
    
  2. Create application code: Add a file named main.go with the following content to initialize the provider and evaluate a flag.

    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. Create go.mod: Using specific versions ensures stability.

    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. Get dependencies:

    go mod tidy
    go mod download
    

Run the Go application

You can execute the application either locally using your standalone configuration or deployed as a container.

Run standalone (locally)

  1. Set environment variables:

    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. Run the application:

    go run main.go
    

Run integrated (containerized)

  1. Create a 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. Build and push the image:

    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. Deploy the service: Update your unit blueprint to reference gcr.io/${PROJECT_ID}/my-go-app:latest and deploy the new release using an App Lifecycle Manager rollout.

Verify flag changes

After your application is running, confirm that it evaluates flags correctly.

  1. Observe the initial value: Verify that the output logs the value corresponding to the initial state.
  2. Update the flag in App Lifecycle Manager: Modify the flag behavior using Google Cloud console or gcloud.
  3. Verify the updated value: Re-run the local compilation or observe the container logs to see the updated result.

What's next