Files
dance-lessons-coach/adr/0010-api-v2-feature-flag.md
Gabriel Radureau 52065c9cf3
Some checks failed
CI/CD Pipeline / Build Docker Cache (push) Successful in 10s
CI/CD Pipeline / CI Pipeline (push) Failing after 13s
refactor: convert all DanceLessonsCoach mentions to kebab-case
2026-04-07 19:11:39 +02:00

5.8 KiB

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": "<greeting>"} 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

# 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

# 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

Changelog Entry

### 2026-04-04 - API v2 Implementation
- ✅ Added /api/v2/greet POST endpoint with JSON request/response
- ✅ Implemented ServiceV2 with "Hello my friend <name>!" 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