Skip to main content

Echo Feature Toggles

Feature toggles (also known as feature flags) are a powerful technique in software development that allows developers to enable or disable functionality without deploying new code. This pattern is particularly valuable when implementing continuous delivery and working with large teams on shared codebases.

In this guide, we'll learn how to implement feature toggles in Echo applications, which can help you gradually roll out features, conduct A/B testing, or quickly disable problematic features without rolling back an entire deployment.

What Are Feature Toggles?

Feature toggles are conditional statements that determine whether a particular feature is available to users. These toggles can be controlled by configuration settings, making it easy to turn features on or off without changing code.

Common use cases include:

  1. Gradual Rollouts: Roll out features to a small percentage of users first
  2. A/B Testing: Compare different implementations of a feature
  3. Canary Releases: Test features with a subset of users
  4. Kill Switches: Quickly disable problematic features

Basic Implementation of Feature Toggles in Echo

Let's start with a simple implementation of feature toggles in an Echo application.

Step 1: Create a Feature Toggle Configuration

First, we'll define a structure to hold our feature toggle configuration:

go
package config

type FeatureToggles struct {
EnableNewDashboard bool
EnableNotifications bool
EnableBetaFeatures bool
}

func NewDefaultFeatureToggles() *FeatureToggles {
return &FeatureToggles{
EnableNewDashboard: false,
EnableNotifications: true,
EnableBetaFeatures: false,
}
}

Step 2: Implement Feature Toggle Middleware

Next, let's create middleware to inject our feature toggle configuration into the context:

go
package middleware

import (
"yourapp/config"
"github.com/labstack/echo/v4"
)

func FeatureToggleMiddleware(toggles *config.FeatureToggles) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
c.Set("featureToggles", toggles)
return next(c)
}
}
}

Step 3: Using Feature Toggles in Handlers

Now we can use these feature toggles in our handlers:

go
package handlers

import (
"net/http"
"yourapp/config"
"github.com/labstack/echo/v4"
)

func DashboardHandler(c echo.Context) error {
toggles, ok := c.Get("featureToggles").(*config.FeatureToggles)
if !ok {
// Default to old dashboard if toggle configuration isn't found
return c.Render(http.StatusOK, "old-dashboard.html", nil)
}

if toggles.EnableNewDashboard {
return c.Render(http.StatusOK, "new-dashboard.html", nil)
}

return c.Render(http.StatusOK, "old-dashboard.html", nil)
}

Step 4: Register in Echo Application

Finally, we register our middleware in the Echo application:

go
package main

import (
"yourapp/config"
"yourapp/handlers"
"yourapp/middleware"
"github.com/labstack/echo/v4"
)

func main() {
e := echo.New()

// Create feature toggle configuration
toggles := config.NewDefaultFeatureToggles()

// Register middleware
e.Use(middleware.FeatureToggleMiddleware(toggles))

// Register routes
e.GET("/dashboard", handlers.DashboardHandler)

e.Start(":8080")
}

Advanced Feature Toggle Patterns

Once you have the basic implementation, you can extend it with more advanced patterns:

Dynamic Configuration

Instead of hardcoding feature toggle settings, load them from a configuration source that can be updated without redeploying:

go
package config

import (
"encoding/json"
"io/ioutil"
"sync"
"time"
)

type DynamicFeatureToggles struct {
EnableNewDashboard bool
EnableNotifications bool
EnableBetaFeatures bool
mu sync.RWMutex
}

func NewDynamicFeatureToggles(configPath string) *DynamicFeatureToggles {
toggles := &DynamicFeatureToggles{}
toggles.loadFromFile(configPath)

// Periodically reload config
go func() {
ticker := time.NewTicker(30 * time.Second)
for range ticker.C {
toggles.loadFromFile(configPath)
}
}()

return toggles
}

func (t *DynamicFeatureToggles) loadFromFile(path string) error {
data, err := ioutil.ReadFile(path)
if err != nil {
return err
}

t.mu.Lock()
defer t.mu.Unlock()
return json.Unmarshal(data, t)
}

func (t *DynamicFeatureToggles) IsNewDashboardEnabled() bool {
t.mu.RLock()
defer t.mu.RUnlock()
return t.EnableNewDashboard
}

// Similar methods for other toggles...

User-Specific Feature Toggles

You can also implement user-specific feature toggles for targeted rollouts:

go
package middleware

import (
"yourapp/config"
"yourapp/models"
"github.com/labstack/echo/v4"
)

func UserFeatureToggleMiddleware(baseToggles *config.FeatureToggles, userService *models.UserService) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
// Get base toggles
toggles := *baseToggles

// Get user from context (set by auth middleware)
user, ok := c.Get("user").(*models.User)
if ok {
// Apply user-specific overrides
if user.IsBetaTester {
toggles.EnableBetaFeatures = true
}

// Gradual rollout based on user ID
if user.ID % 10 == 0 { // 10% of users
toggles.EnableNewDashboard = true
}
}

c.Set("featureToggles", &toggles)
return next(c)
}
}
}

Percentage-Based Rollout

Implementing percentage-based rollouts for A/B testing:

go
package middleware

import (
"hash/fnv"
"strconv"
"yourapp/config"
"github.com/labstack/echo/v4"
)

func PercentageRolloutMiddleware(baseToggles *config.FeatureToggles) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
// Get base toggles
toggles := *baseToggles

// Get unique identifier (user ID or session ID)
identifier := getUserIdentifier(c)

// Hash the identifier to get consistent results
h := fnv.New32a()
h.Write([]byte(identifier))
hashValue := h.Sum32() % 100 // Get a value between 0-99

// Enable feature for 20% of users
if hashValue < 20 {
toggles.EnableNewDashboard = true
}

c.Set("featureToggles", &toggles)
return next(c)
}
}
}

func getUserIdentifier(c echo.Context) string {
// Try to get user ID first
if user, ok := c.Get("user").(*models.User); ok {
return strconv.Itoa(user.ID)
}

// Fall back to session ID
sessionID, err := c.Cookie("session_id")
if err == nil {
return sessionID.Value
}

// Last resort, use IP address
return c.RealIP()
}

Real-World Example: Feature Toggles for an E-commerce Application

Let's look at a real-world example for an e-commerce site implementing a new checkout process:

go
package main

import (
"database/sql"
"yourapp/config"
"github.com/labstack/echo/v4"
)

func main() {
e := echo.New()
db, _ := sql.Open("postgres", "postgresql://user:password@localhost/ecommerce")

// Create toggle configuration with remote source
toggles := config.NewRemoteFeatureToggles("https://config-server.example.com/toggles")

// Register middleware
e.Use(middleware.FeatureToggleMiddleware(toggles))

// Setup routes
e.GET("/checkout", func(c echo.Context) error {
toggles := c.Get("featureToggles").(*config.FeatureToggles)

if toggles.IsEnabled("new-checkout-process") {
// Use new checkout
return c.Render(http.StatusOK, "checkout-v2.html", nil)
}

// Use old checkout
return c.Render(http.StatusOK, "checkout-v1.html", nil)
})

e.POST("/checkout", func(c echo.Context) error {
toggles := c.Get("featureToggles").(*config.FeatureToggles)

if toggles.IsEnabled("new-checkout-process") {
// Process checkout with new system
return handleNewCheckoutProcess(c, db)
}

// Process checkout with old system
return handleLegacyCheckoutProcess(c, db)
})

// Start server
e.Start(":8080")
}

Tracking and Analytics

We can extend our implementation to track toggle usage for analytics:

go
package middleware

import (
"yourapp/config"
"yourapp/analytics"
"github.com/labstack/echo/v4"
)

func FeatureToggleAnalyticsMiddleware(toggles *config.FeatureToggles, reporter *analytics.Reporter) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
wrappedToggles := &AnalyticsToggles{
base: toggles,
reporter: reporter,
context: c,
}
c.Set("featureToggles", wrappedToggles)
return next(c)
}
}
}

type AnalyticsToggles struct {
base *config.FeatureToggles
reporter *analytics.Reporter
context echo.Context
}

func (t *AnalyticsToggles) IsEnabled(feature string) bool {
enabled := t.base.IsEnabled(feature)

// Report toggle usage
userID := getUserID(t.context)
t.reporter.LogToggleUsage(feature, enabled, userID)

return enabled
}

Best Practices for Feature Toggles

When implementing feature toggles, keep these best practices in mind:

  1. Keep toggles temporary: Feature toggles should not live forever. Once a feature is fully deployed, remove the toggle.
  2. Limit the number of toggles: Too many toggles can make code complex and harder to test.
  3. Document all active toggles: Make sure everyone on the team knows what toggles exist and what they do.
  4. Test all toggle states: Ensure your application works correctly with all possible toggle configurations.
  5. Use descriptive names: Choose clear, descriptive names for your toggles that indicate their purpose.
  6. Implement a management interface: Create an admin dashboard to control toggle states.
  7. Consider toggle lifecycles: Different toggles have different lifespans (release toggles vs. experiment toggles).

Summary

Feature toggles in Echo applications provide a powerful way to control feature rollouts, conduct experiments, and manage risk. We've covered:

  • Basic implementation of feature toggles using middleware
  • Advanced patterns for dynamic configuration
  • User-specific and percentage-based rollouts
  • Real-world examples for e-commerce applications
  • Best practices for maintaining feature toggles

By incorporating feature toggles into your Echo applications, you can deliver features more safely, get feedback faster, and reduce the risk of deployments.

Additional Resources

Exercises

  1. Implement a simple feature toggle system for an Echo application that enables a "dark mode" feature for 50% of users.
  2. Create a feature toggle admin dashboard that allows administrators to turn features on or off.
  3. Extend the feature toggle system to load configuration from a JSON file and automatically reload when the file changes.
  4. Implement A/B testing using feature toggles to compare two versions of a user interface component.
  5. Create a database-backed feature toggle system that allows for user-specific feature enablement.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)