# 10. API v2 Feature Flag Implementation **Date:** 2026-04-04 **Status:** Accepted **Authors:** AI Agent ## Context The dance-lessons-coach application needed to add a new API version (v2) that provides different greeting behavior while maintaining backward compatibility with the existing v1 API. The v2 API should only be available when explicitly enabled via a feature flag. ## Decision Implement API v2 with the following characteristics: 1. **Separate Service Implementation**: Create a new `ServiceV2` that implements different greeting logic 2. **Feature Flag Control**: Add a configuration flag `api.v2_enabled` to control v2 availability 3. **Environment Variable Support**: Allow enabling via `DLC_API_V2_ENABLED` environment variable 4. **Default Disabled**: v2 API is disabled by default to maintain backward compatibility 5. **Separate Routing**: v2 endpoints use `/api/v2` prefix, separate from `/api/v1` ## Implementation Details ### Service Layer - **New Service**: `pkg/greet/greet_v2.go` with `ServiceV2` struct - **New Interface**: `GreeterV2` interface with `GreetV2()` method - **Different Behavior**: Returns "Hello my friend !" instead of "Hello !" - **Empty Name Handling**: Returns "Hello my friend!" when name is empty ### API Layer - **New Handler**: `pkg/greet/api_v2.go` with `apiV2GreetHandler` struct - **New Interface**: `ApiV2Greet` interface with `RegisterRoutes()` method - **POST Endpoint**: `/api/v2/greet` expects JSON body with `name` field - **JSON Response**: Returns `{"message": ""}` format ### Configuration - **New Config Struct**: `APIConfig` in `pkg/config/config.go` - **Feature Flag**: `V2Enabled bool` field - **Environment Variable**: `DLC_API_V2_ENABLED` binding - **Default Value**: `false` (disabled by default) - **Config Method**: `GetV2Enabled()` method on `Config` struct ### Server Integration - **Conditional Routing**: v2 routes only registered when `config.GetV2Enabled()` returns true - **Separate Registration**: `registerApiV2Routes()` method in server - **Same Middleware**: v2 routes use same middleware stack as v1 - **Graceful Coexistence**: Both v1 and v2 can run simultaneously ### Testing - **Unit Tests**: `pkg/greet/greet_v2_test.go` with table-driven tests - **BDD Tests**: Extended `features/greet.feature` with v2 scenarios - **Step Definitions**: Added v2-specific steps in `pkg/bdd/steps/steps.go` - **Test Server**: Modified test server to enable v2 by default for testing ## Consequences ### Positive - **Backward Compatibility**: v1 API continues to work unchanged - **Feature Flag Control**: v2 can be enabled/disabled without code changes - **Clean Separation**: v1 and v2 implementations are independent - **Test Coverage**: Comprehensive BDD tests validate both versions - **Configuration Flexibility**: Can be controlled via config file or environment variables ### Negative - **Increased Complexity**: More code paths to maintain - **Configuration Management**: Additional configuration option to document - **Testing Overhead**: Need to test both enabled and disabled states - **Deployment Considerations**: Need to ensure feature flag is set correctly in production ## Alternatives Considered ### 1. Always Enable v2 - **Rejected**: Would break backward compatibility - **Reason**: Customers might not be ready for API changes ### 2. Replace v1 with v2 - **Rejected**: Would be a breaking change - **Reason**: Violates semantic versioning principles ### 3. Use URL Query Parameter - **Rejected**: Less clean API design - **Reason**: Version should be in URL path, not query string ### 4. Use Header-Based Versioning - **Rejected**: More complex for clients - **Reason**: URL-based versioning is more RESTful and discoverable ## Validation ### Test Results ```bash # Unit tests pass go test ./pkg/greet/... # BDD tests pass go test ./features/... -v # All scenarios pass: - Default greeting (v1) - Personalized greeting (v1) - v2 greeting with JSON POST request - v2 default greeting with empty name - Health check returns healthy status ``` ### Manual Testing ```bash # Test v2 disabled (default) curl -X POST http://localhost:8080/api/v2/greet -H "Content-Type: application/json" -d '{"name":"John"}' # Expected: 404 Not Found # Test v2 enabled DLC_API_V2_ENABLED=true go run ./cmd/server curl -X POST http://localhost:8080/api/v2/greet -H "Content-Type: application/json" -d '{"name":"John"}' # Expected: {"message":"Hello my friend John!"} # Test v1 still works curl http://localhost:8080/api/v1/greet/John # Expected: {"message":"Hello John!"} ``` ## Migration Path 1. **Deploy with v2 Disabled**: Initial deployment keeps v2 disabled 2. **Enable in Staging**: Test v2 in staging environment 3. **Gradual Rollout**: Enable v2 for select customers 4. **Monitor**: Track usage and performance 5. **Full Enable**: Enable v2 for all customers when ready 6. **Deprecation**: Eventually deprecate v1 (future decision) ## Future Considerations - **Deprecation Timeline**: When to deprecate v1 - **Version Negotiation**: Content negotiation for API versions - **OpenAPI Documentation**: Update Swagger/OpenAPI docs for v2 - **Client SDKs**: Update client libraries to support v2 - **Metrics**: Add metrics to track v1 vs v2 usage ## References - [Semantic Versioning](https://semver.org/) - [REST API Versioning Best Practices](https://restfulapi.net/versioning/) - [Feature Flags Pattern](https://martinfowler.com/articles/feature-toggles.html) ## Changelog Entry ``` ### 2026-04-04 - API v2 Implementation - ✅ Added /api/v2/greet POST endpoint with JSON request/response - ✅ Implemented ServiceV2 with "Hello my friend !" greeting format - ✅ Added api.v2_enabled feature flag (default: false) - ✅ Extended BDD tests to cover v2 scenarios - ✅ Maintained full backward compatibility with v1 API - ✅ Added DLC_API_V2_ENABLED environment variable support ```