Skip to content

Qambar/go-feature-flags

Repository files navigation

go-feature-flags

CI Go Report Card License: MIT

A lightweight, production-ready feature flag service built in Go. Supports percentage rollouts with consistent hashing, user targeting rules, flag dependencies, and audit logging.

Architecture

graph TB
    Client[Client] -->|HTTP| MW[Middleware Stack]
    MW -->|Request ID, Logging, Metrics, Recovery| Router[Chi Router]
    Router --> FH[Flag Handler]
    Router --> HH[Health Handler]
    Router --> PM[Prometheus /metrics]
    FH --> FS[Flag Service]
    FS --> EE[Evaluation Engine]
    FS --> FC[Flag Cache]
    FS --> FR[Flag Repository]
    FC -.->|TTL-based invalidation| FR
    EE -->|FlagGetter interface| FS
    FR -->|database/sql| DB[(SQLite)]

    style MW fill:#f9f,stroke:#333
    style FS fill:#bbf,stroke:#333
    style EE fill:#bfb,stroke:#333
    style FC fill:#fbb,stroke:#333
    style DB fill:#ff9,stroke:#333
Loading

Layers

Layer Package Responsibility
Transport internal/handler HTTP handlers, middleware, JSON encoding
Business internal/service Orchestration, validation, audit logging
Evaluation internal/evaluation Flag evaluation engine (targeting, rollout, dependencies)
Cache internal/cache TTL-based in-memory cache with sync.RWMutex
Persistence internal/repository SQLite via database/sql behind a FlagRepository interface
Domain internal/domain Core types, request/response structs, validation
Config internal/config YAML + env var configuration loading

Quick Start

Prerequisites

  • Go 1.23+
  • GCC (for SQLite via cgo)

Run locally

make run

Run with Docker

docker compose up

The server starts on :8080 by default.

API Reference

Feature Flags

Create a flag

curl -X POST http://localhost:8080/flags \
  -H "Content-Type: application/json" \
  -d '{
    "key": "dark-mode",
    "name": "Dark Mode",
    "description": "Enable dark mode UI",
    "enabled": true,
    "rollout_percentage": 50,
    "targeting_rules": [
      {"attribute": "email", "operator": "contains", "value": "@monzo.com"}
    ]
  }'

List all flags

curl http://localhost:8080/flags

Get a flag

curl http://localhost:8080/flags/dark-mode

Update a flag

curl -X PUT http://localhost:8080/flags/dark-mode \
  -H "Content-Type: application/json" \
  -d '{"enabled": false, "rollout_percentage": 0}'

Delete a flag

curl -X DELETE http://localhost:8080/flags/dark-mode

Evaluation

Evaluate a single flag

curl -X POST http://localhost:8080/evaluate \
  -H "Content-Type: application/json" \
  -d '{
    "flag_key": "dark-mode",
    "context": {
      "user_id": "user-123",
      "attributes": {
        "email": "[email protected]",
        "country": "GB"
      }
    }
  }'

Response:

{
  "key": "dark-mode",
  "enabled": true,
  "reason": "targeting rule matched: email contains @monzo.com"
}

Bulk evaluate

curl -X POST http://localhost:8080/evaluate/bulk \
  -H "Content-Type: application/json" \
  -d '{
    "flag_keys": ["dark-mode", "new-checkout"],
    "context": {"user_id": "user-123"}
  }'

Audit Log

curl http://localhost:8080/flags/dark-mode/history

Health & Observability

curl http://localhost:8080/healthz    # Liveness probe
curl http://localhost:8080/readyz     # Readiness probe
curl http://localhost:8080/metrics    # Prometheus metrics

Configuration

Configuration is loaded from config.yaml with environment variable overrides:

Env Var Default Description
PORT 8080 Server port
DATABASE_DSN flags.db SQLite database path
CACHE_ENABLED true Enable in-memory cache
CACHE_TTL 30s Cache TTL duration
LOG_LEVEL info Log level (debug, info, warn, error)
LOG_FORMAT json Log format (json, text)
CONFIG_PATH config.yaml Path to YAML config file

Targeting Rules

Rules support four operators:

Operator Description Example
eq Exact match {"attribute": "country", "operator": "eq", "value": "GB"}
neq Not equal {"attribute": "user_id", "operator": "neq", "value": "blocked"}
contains Substring match {"attribute": "email", "operator": "contains", "value": "@monzo.com"}
in Value in comma-separated list {"attribute": "country", "operator": "in", "value": "GB,US,DE"}

If any targeting rule matches, the flag is enabled for that user regardless of rollout percentage. If no rules match, the percentage rollout applies.

Design Decisions

Consistent Hashing for Rollouts

Percentage rollouts use CRC32 hashing of flagKey:userID to assign each user to a deterministic bucket in [0, 100). This means:

  • The same user always sees the same flag state (no flicker)
  • No external state needed to track assignments
  • Increasing the rollout percentage from 10% to 20% adds users — it doesn't reshuffle existing ones

Repository Interface

Storage is abstracted behind repository.FlagRepository. The current implementation uses SQLite with WAL mode for good read concurrency, but the interface makes it straightforward to swap in Postgres, MySQL, or DynamoDB without touching business logic.

Cache Strategy

The in-memory cache uses sync.RWMutex (not sync.Map) because the access pattern is heavily read-biased with known key types. Write-through invalidation on updates ensures consistency, and the configurable TTL provides a safety net. The cache stores copies of flag structs to prevent mutation of cached data.

Evaluation Engine Decoupling

The evaluation engine depends on a FlagGetter interface, not the repository directly. The service implements this interface, so evaluations (including recursive dependency resolution) go through the cache transparently. Circular dependencies are detected via a visited-set during recursion.

Graceful Shutdown

The server listens for SIGINT/SIGTERM and drains in-flight requests before stopping. This is essential for zero-downtime deployments in Kubernetes or similar orchestrators.

SQLite in Production

SQLite with WAL mode handles significant read throughput for a single-node service. For multi-node deployments, swap the repository implementation to Postgres — the interface is ready.

Development

make test          # Run tests with race detector
make test-verbose  # Run tests with verbose output
make lint          # Run golangci-lint
make coverage      # Generate HTML coverage report
make fmt           # Format code
make vet           # Run go vet
make clean         # Remove build artifacts

License

MIT

About

Feature Flag Service — Go microservice with evaluation engine, consistent-hashing rollouts, user targeting, and Prometheus metrics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors