Condividi tramite


Esercitazione: Usare flag di funzionalità varianti in un'applicazione Web Go Gin

In questa esercitazione si utilizza un flag di funzionalità variabile per gestire le esperienze per segmenti di utenti diversi in un'applicazione di esempio, Quote of the Day. Usare il flag di funzionalità variant creato in Usa flag di funzionalità varianti. Prima di procedere, assicurarsi di creare il flag di funzionalità variante denominato Messaggio di saluto nell'archivio di Configurazione app.

Prerequisiti

Configurare un'applicazione Web Go Gin

  1. Creare una nuova directory per il progetto Go e spostarsi in questa directory:

    mkdir quote-of-the-day
    cd quote-of-the-day
    
  2. Inizializzare un nuovo modulo Go:

    go mod init quote-of-the-day
    
  3. Installare i pacchetti Go necessari:

    go get github.com/gin-gonic/gin
    go get github.com/gin-contrib/sessions
    go get github.com/gin-contrib/sessions/cookie
    go get github.com/microsoft/Featuremanagement-Go/featuremanagement
    go get github.com/microsoft/Featuremanagement-Go/featuremanagement/providers/azappconfig
    
  4. Creare una directory di modelli per i modelli HTML e aggiungere i file HTML necessari:

    mkdir templates
    

    Aggiungere i file di modello HTML seguenti dal repository GitHub e inserirli nella templates directory:

  5. Creare un file denominato appconfig.go con il contenuto seguente. È possibile connettersi al proprio App Configuration store utilizzando Microsoft Entra ID (consigliato) o una stringa di connessione.

    package main
    
    import (
        "context"
        "log"
        "os"
    
        "github.com/Azure/AppConfiguration-GoProvider/azureappconfiguration"
        "github.com/Azure/azure-sdk-for-go/sdk/azidentity"
    )
    
    func loadAzureAppConfiguration(ctx context.Context) (*azureappconfiguration.AzureAppConfiguration, error) {
        // Get the endpoint from environment variable
        endpoint := os.Getenv("AZURE_APPCONFIG_ENDPOINT")
        if endpoint == "" {
            log.Fatal("AZURE_APPCONFIG_ENDPOINT environment variable is not set")
        }
    
        // Create a credential using DefaultAzureCredential
        credential, err := azidentity.NewDefaultAzureCredential(nil)
        if err != nil {
            log.Fatalf("Failed to create credential: %v", err)
        }
    
        // Set up authentication options with endpoint and credential
        authOptions := azureappconfiguration.AuthenticationOptions{
            Endpoint:   endpoint,
            Credential: credential,
        }
    
        // Set up options to enable feature flags
        options := &azureappconfiguration.Options{
            FeatureFlagOptions: azureappconfiguration.FeatureFlagOptions{
                Enabled: true,
                RefreshOptions: azureappconfiguration.RefreshOptions{
                    Enabled: true,
                },
            },
        }
    
        // Load configuration from Azure App Configuration
        appConfig, err := azureappconfiguration.Load(ctx, authOptions, options)
        if err != nil {
            log.Fatalf("Failed to load configuration: %v", err)
        }
    
        return appConfig, nil
    }
    

Usare il flag di funzionalità variant

  1. Creare un file denominato main.go con il contenuto seguente:

    package main
    
    import (
        "context"
        "fmt"
        "log"
        "net/http"
        "strings"
    
        "github.com/Azure/AppConfiguration-GoProvider/azureappconfiguration"
        "github.com/gin-contrib/sessions"
        "github.com/gin-contrib/sessions/cookie"
        "github.com/gin-gonic/gin"
        "github.com/microsoft/Featuremanagement-Go/featuremanagement"
        "github.com/microsoft/Featuremanagement-Go/featuremanagement/providers/azappconfig"
    )
    
    type Quote struct {
        Message string `json:"message"`
        Author  string `json:"author"`
    }
    
    type WebApp struct {
        featureManager *featuremanagement.FeatureManager
        appConfig      *azureappconfiguration.AzureAppConfiguration
        quotes         []Quote
    }
    
    func main() {
        // Load Azure App Configuration
        appConfig, err := loadAzureAppConfiguration(context.Background())
        if err != nil {
            log.Fatalf("Error loading Azure App Configuration: %v", err)
        }
    
        // Create feature flag provider
        featureFlagProvider, err := azappconfig.NewFeatureFlagProvider(appConfig)
        if err != nil {
            log.Fatalf("Error creating feature flag provider: %v", err)
        }
    
        // Create feature manager
        featureManager, err := featuremanagement.NewFeatureManager(featureFlagProvider, nil)
        if err != nil {
            log.Fatalf("Error creating feature manager: %v", err)
        }
    
        // Initialize quotes
        quotes := []Quote{
            {
                Message: "You cannot change what you are, only what you do.",
                Author:  "Philip Pullman",
            },
        }
    
        // Create web app
        app := &WebApp{
            featureManager: featureManager,
            appConfig:      appConfig,
            quotes:         quotes,
        }
    
        // Setup Gin with default middleware (Logger and Recovery)
        r := gin.Default()
    
        // Start server
        if err := r.Run(":8080"); err != nil {
            log.Fatalf("Failed to start server: %v", err)
        }
    
        fmt.Println("Starting Quote of the Day server on http://localhost:8080")
        fmt.Println("Open http://localhost:8080 in your browser")
        fmt.Println()
    }
    
  2. Abilitare l'aggiornamento della configurazione e del flag di funzionalità da Configurazione app di Azure con il middleware.

    // Existing code
    // ... ...
    
    func (app *WebApp) refreshMiddleware() gin.HandlerFunc {
        return func(c *gin.Context) {
            go func() {
                if err := app.appConfig.Refresh(context.Background()); err != nil {
                    log.Printf("Error refreshing configuration: %v", err)
                }
            }()
            c.Next()
        }
    }
    // The rest of existing code
    //... ...
    
  3. Configurare le route con il contenuto seguente:

    // Existing code
    // ... ...
    
    func (app *WebApp) setupRoutes(r *gin.Engine) {
        // Setup sessions
        store := cookie.NewStore([]byte("secret-key-change-in-production"))
        store.Options(sessions.Options{
            MaxAge:   3600, // 1 hour
            HttpOnly: true,
            Secure:   false, // Set to true in production with HTTPS
        })
        r.Use(sessions.Sessions("session", store))
    
        r.Use(app.refreshMiddleware())
    
        // Load HTML templates
        r.LoadHTMLGlob("templates/*.html")
        // Routes
        r.GET("/", app.homeHandler)
        r.GET("/login", app.loginPageHandler)
        r.POST("/login", app.loginHandler)
        r.GET("/logout", app.logoutHandler)
    }
    
    // Home page handler
    func (app *WebApp) homeHandler(c *gin.Context) {
        session := sessions.Default(c)
        username := session.Get("username")
        quote := app.quotes[0]
    
        var greetingMessage string
        var targetingContext featuremanagement.TargetingContext
        if username != nil {
            // Create targeting context for the user
            targetingContext = createTargetingContext(username.(string))
    
            // Get the Greeting variant for the current user
            if variant, err := app.featureManager.GetVariant("Greeting", targetingContext); err != nil {
                log.Printf("Error getting Greeting variant: %v", err)
            } else if variant != nil && variant.ConfigurationValue != nil {
                // Extract the greeting message from the variant configuration
                if configValue, ok := variant.ConfigurationValue.(string); ok {
                    greetingMessage = configValue
                }
            }
        }
    
        c.HTML(http.StatusOK, "index.html", gin.H{
            "title":           "Quote of the Day",
            "user":            username,
            "greetingMessage": greetingMessage,
            "quote":           quote,
        })
    }
    
    func (app *WebApp) loginPageHandler(c *gin.Context) {
        c.HTML(http.StatusOK, "login.html", gin.H{
            "title": "Login - Quote of the Day",
        })
    }
    
    func (app *WebApp) loginHandler(c *gin.Context) {
        email := strings.TrimSpace(c.PostForm("email"))
    
        // Basic validation
        if email == "" {
            c.HTML(http.StatusOK, "login.html", gin.H{
                "title": "Login - Quote of the Day",
                "error": "Email cannot be empty",
            })
            return
        }
    
        if !strings.Contains(email, "@") {
            c.HTML(http.StatusOK, "login.html", gin.H{
                "title": "Login - Quote of the Day",
                "error": "Please enter a valid email address",
            })
            return
        }
    
        // Store email in session
        session := sessions.Default(c)
        session.Set("username", email)
        if err := session.Save(); err != nil {
            log.Printf("Error saving session: %v", err)
        }
    
        c.Redirect(http.StatusFound, "/")
    }
    
    func (app *WebApp) logoutHandler(c *gin.Context) {
        session := sessions.Default(c)
        session.Clear()
        if err := session.Save(); err != nil {
            log.Printf("Error saving session: %v", err)
        }
        c.Redirect(http.StatusFound, "/")
    }
    
    // Helper function to create TargetingContext
    func createTargetingContext(userID string) featuremanagement.TargetingContext {
        targetingContext := featuremanagement.TargetingContext{
            UserID: userID,
            Groups: []string{},
        }
    
        if strings.Contains(userID, "@") {
            parts := strings.Split(userID, "@")
            if len(parts) == 2 {
                domain := parts[1]
                targetingContext.Groups = append(targetingContext.Groups, domain) // Add domain as group
            }
        }
    
        return targetingContext
    }
    // The rest of existing code
    //... ...
    
  4. Aggiornare main.go con il contenuto seguente:

    // Existing code
    // ... ...
     r := gin.Default()
    
     // Setup routes
     app.setupRoutes(r)
    
     // Start server
     if err := r.Run(":8080"); err != nil {
        log.Fatalf("Failed to start server: %v", err)
    }
    // The rest of existing code
    // ... ...
    

Compilare ed eseguire l'app

  1. Impostare la variabile di ambiente per l'autenticazione ed eseguire l'applicazione:

    go mod tidy
    go run .
    
  2. Aprire il browser e passare a http://localhost:8080. Selezionare Login (Accedi ) in alto a destra per accedere come usera@contoso.com.

    Screenshot dell'app Web Gin prima dell'accesso dell'utente.

  3. Dopo aver eseguito l'accesso, viene visualizzato un messaggio di saluto lungo per usera@contoso.com.

    Screenshot dell'app web Gin, che mostra un lungo messaggio utente.

  4. Fare clic su Disconnetti e accedere come userb@contoso.com. Verrà visualizzato il messaggio di saluto semplice.

    Screenshot dell'app Web Gin, che mostra un semplice messaggio per l'utente.

    Annotazioni

    Ai fini di questa esercitazione è importante usare esattamente questi nomi. Se la funzionalità è stata configurata come previsto, i due utenti dovrebbero visualizzare varianti diverse.

Passaggi successivi

Per altre informazioni sulla gestione delle funzionalità in Go, continuare con i documenti seguenti: