A gRPC service for managing feature flags with Redis backend, supporting conditional evaluation and various operators.
- 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
EQUALS
: Exact matchNOT_EQUALS
: Not equal toIN
: Value is in the listNOT_IN
: Value is not in the listGT
: 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)
-
Start the services:
docker-compose up -d
-
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
-
Build the packages:
make build
-
Start Redis (if not using Docker):
docker run -d -p 6379:6379 redis:7-alpine
-
Start the service:
# Sync version make dev-local # Async version (recommended) make dev-local-async
-
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
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())
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())
Traditional synchronous usage is still supported:
stub.EnableFeatureFlag(
feature_flag_pb2.EnableFeatureFlagRequest(
flag_name="ENABLE_BETA_BANNER"
)
)
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
)
)
response = stub.CheckFeatureFlag(
feature_flag_pb2.CheckFeatureFlagRequest(
flag_name="ENABLE_BETA_BANNER",
field_value="Austin"
)
)
print(f"Enabled: {response.enabled}")
stub.DisableFeatureFlag(
feature_flag_pb2.DisableFeatureFlagRequest(
flag_name="ENABLE_BETA_BANNER"
)
)
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)
EnableFeatureFlag
- Enable a feature flag with optional conditionsCheckFeatureFlag
- Check if a feature flag is enabledDisableFeatureFlag
- Disable a feature flag
POST /feature_flag.FeatureFlagService/EnableFeatureFlag
POST /feature_flag.FeatureFlagService/CheckFeatureFlag
POST /feature_flag.FeatureFlagService/DisableFeatureFlag
http://localhost:9901
- Envoy admin dashboard
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
The client_example.py
demonstrates various usage patterns:
- Enable flag for all users
- Enable flag with IN condition (specific users)
- Enable flag with GT condition (version check)
- Enable flag with NOT_IN condition (exclude test accounts)
- Disable a feature flag
Run the example to see all scenarios in action.
-
Build protobuf package:
make build-protobuf # or cd protobuf && uv build
-
Build service package:
make build-service # or cd service && uv sync
If you modify the .proto
file:
make proto
# or
cd protobuf && uv run python feature_flag/generate.py
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
- Add the operator to the
ConditionOperator
enum inprotobuf/feature_flag.proto
- Implement the logic in the
_evaluate_condition
method inservice/feature_flag_service.py
- Regenerate the protobuf stubs:
make proto
- Rebuild the packages:
make build
- Update tests and documentation