Skip to content

Aweave15/ff

Repository files navigation

Feature Flag gRPC Service

A gRPC service for managing feature flags with Redis backend, supporting conditional evaluation and various operators.

Features

  • Enable Feature Flags: Set feature flags with optional conditions
  • Check Feature Flags: Evaluate feature flags against input values
  • Disable Feature Flags: Turn off feature flags
  • Conditional Logic: Support for EQUALS, IN, GT, LT, and negation operators
  • Redis Backend: Persistent storage with Redis
  • Async Support: High-performance async/await implementation
  • Concurrent Operations: Support for concurrent Redis operations
  • HTTP API: RESTful HTTP/JSON API via Envoy proxy
  • Docker Support: Containerized service with docker-compose

Supported Condition Operators

  • EQUALS: Exact match
  • NOT_EQUALS: Not equal to
  • IN: Value is in the list
  • NOT_IN: Value is not in the list
  • GT: Greater than (numeric)
  • LT: Less than (numeric)
  • NOT_GT: Not greater than (less than or equal)
  • NOT_LT: Not less than (greater than or equal)

Quick Start

Using Docker Compose (Recommended)

  1. Start the services:

    docker-compose up -d
  2. Run the example client:

    # Sync version
    docker-compose exec feature-flag-service python client_example.py
    
    # Async version (recommended)
    docker-compose exec feature-flag-service python async_client_example.py
    
    # HTTP version (via Envoy proxy)
    docker-compose exec feature-flag-service python http_client_example.py

Local Development

  1. Build the packages:

    make build
  2. Start Redis (if not using Docker):

    docker run -d -p 6379:6379 redis:7-alpine
  3. Start the service:

    # Sync version
    make dev-local
    
    # Async version (recommended)
    make dev-local-async
  4. Run the example client (in another terminal):

    # Sync version
    cd service && uv run python client_example.py
    
    # Async version (recommended)
    cd service && uv run python async_client_example.py

API Examples

Async Usage (Recommended)

The service now supports async/await for better performance with I/O operations:

import asyncio
import grpc
from grpc import aio
from feature_flag import feature_flag_pb2
from feature_flag import feature_flag_pb2_grpc

async def main():
    async with aio.insecure_channel('localhost:50051') as channel:
        stub = feature_flag_pb2_grpc.FeatureFlagServiceStub(channel)
        
        # Enable flag
        response = await stub.EnableFeatureFlag(
            feature_flag_pb2.EnableFeatureFlagRequest(
                flag_name="MY_FLAG"
            )
        )
        
        # Check flag
        response = await stub.CheckFeatureFlag(
            feature_flag_pb2.CheckFeatureFlagRequest(
                flag_name="MY_FLAG",
                field_value="user123"
            )
        )
        print(f"Enabled: {response.enabled}")

asyncio.run(main())

HTTP API Usage (via Envoy Proxy)

The service also provides a RESTful HTTP/JSON API through Envoy proxy:

# Enable a feature flag
curl -X POST http://localhost:8080/feature_flag.FeatureFlagService/EnableFeatureFlag \
  -H "Content-Type: application/json" \
  -d '{"flag_name": "MY_FLAG"}'

# Check a feature flag
curl -X POST http://localhost:8080/feature_flag.FeatureFlagService/CheckFeatureFlag \
  -H "Content-Type: application/json" \
  -d '{"flag_name": "MY_FLAG", "field_value": "user123"}'

# Disable a feature flag
curl -X POST http://localhost:8080/feature_flag.FeatureFlagService/DisableFeatureFlag \
  -H "Content-Type: application/json" \
  -d '{"flag_name": "MY_FLAG"}'

Python HTTP Client:

import requests

# Enable flag
response = requests.post(
    "http://localhost:8080/feature_flag.FeatureFlagService/EnableFeatureFlag",
    json={"flag_name": "MY_FLAG"}
)
print(response.json())

# Check flag
response = requests.post(
    "http://localhost:8080/feature_flag.FeatureFlagService/CheckFeatureFlag",
    json={"flag_name": "MY_FLAG", "field_value": "user123"}
)
print(response.json())

Sync Usage

Traditional synchronous usage is still supported:

Enable a feature flag for all users

stub.EnableFeatureFlag(
    feature_flag_pb2.EnableFeatureFlagRequest(
        flag_name="ENABLE_BETA_BANNER"
    )
)

Enable a feature flag for specific users

condition = feature_flag_pb2.Condition(
    field="user_id",
    operator=feature_flag_pb2.ConditionOperator.IN,
    values=["Edgar", "Austin", "Michael"]
)

stub.EnableFeatureFlag(
    feature_flag_pb2.EnableFeatureFlagRequest(
        flag_name="ENABLE_BETA_BANNER",
        condition=condition
    )
)

Check a feature flag

response = stub.CheckFeatureFlag(
    feature_flag_pb2.CheckFeatureFlagRequest(
        flag_name="ENABLE_BETA_BANNER",
        field_value="Austin"
    )
)
print(f"Enabled: {response.enabled}")

Disable a feature flag

stub.DisableFeatureFlag(
    feature_flag_pb2.DisableFeatureFlagRequest(
        flag_name="ENABLE_BETA_BANNER"
    )
)

Configuration

The service can be configured using environment variables:

  • PORT: gRPC server port (default: 50051)
  • REDIS_HOST: Redis host (default: localhost)
  • REDIS_PORT: Redis port (default: 6379)

Available Endpoints

gRPC Endpoints (Port 50051)

  • EnableFeatureFlag - Enable a feature flag with optional conditions
  • CheckFeatureFlag - Check if a feature flag is enabled
  • DisableFeatureFlag - Disable a feature flag

HTTP Endpoints (Port 8080 via Envoy)

  • POST /feature_flag.FeatureFlagService/EnableFeatureFlag
  • POST /feature_flag.FeatureFlagService/CheckFeatureFlag
  • POST /feature_flag.FeatureFlagService/DisableFeatureFlag

Envoy Admin Interface (Port 9901)

  • http://localhost:9901 - Envoy admin dashboard

Project Structure

feature-flag/
├── protobuf/                   # Protobuf package
│   ├── feature_flag.proto      # Protobuf schema
│   ├── feature_flag/           # Generated protobuf package
│   │   ├── __init__.py
│   │   ├── feature_flag_pb2.py
│   │   ├── feature_flag_pb2_grpc.py
│   │   └── generate.py         # Generation script
│   ├── pyproject.toml          # Protobuf package config
│   └── README.md
├── service/                    # Service implementation
│   ├── feature_flag_service.py # Main service implementation
│   ├── async_feature_flag_service.py # Async service implementation
│   ├── client_example.py       # Example client usage
│   ├── async_client_example.py # Async client example
│   ├── http_client_example.py  # HTTP client example
│   ├── http_client_curl.sh     # HTTP client with curl
│   ├── test_service.py         # Service tests
│   ├── async_test_service.py   # Async service tests
│   ├── start_service.py        # Service startup script
│   ├── async_start_service.py  # Async service startup script
│   ├── pyproject.toml          # Service package config
│   └── README.md
├── envoy.yaml                  # Envoy proxy configuration
├── feature_flag.pb             # Protobuf descriptor file
├── Dockerfile                  # Docker configuration
├── docker-compose.yml          # Docker Compose setup
├── Makefile                    # Build and run commands
└── README.md                   # This file

Testing

The client_example.py demonstrates various usage patterns:

  1. Enable flag for all users
  2. Enable flag with IN condition (specific users)
  3. Enable flag with GT condition (version check)
  4. Enable flag with NOT_IN condition (exclude test accounts)
  5. Disable a feature flag

Run the example to see all scenarios in action.

Development

Building the Project

  1. Build protobuf package:

    make build-protobuf
    # or
    cd protobuf && uv build
  2. Build service package:

    make build-service
    # or
    cd service && uv sync

Regenerating Protobuf Stubs

If you modify the .proto file:

make proto
# or
cd protobuf && uv run python feature_flag/generate.py

Local Development

For local development without Docker:

# Start Redis
docker run -d -p 6379:6379 redis:7-alpine

# Start the service
make dev-local
# or
cd service && uv run python start_service.py

Adding New Operators

  1. Add the operator to the ConditionOperator enum in protobuf/feature_flag.proto
  2. Implement the logic in the _evaluate_condition method in service/feature_flag_service.py
  3. Regenerate the protobuf stubs: make proto
  4. Rebuild the packages: make build
  5. Update tests and documentation

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published