ADR-001: Flavor Architecture
Context
The original Heka platform was built as a traditional microservices architecture with separate services (c13s, a12n, admin, frontend, launcher, scheduler) that communicate over HTTP. This approach led to several pain points:- Tight coupling: Services were tightly coupled despite being separate
- Deployment complexity: Managing multiple services and their dependencies
- Development overhead: Significant boilerplate code duplication across services
- Resource inefficiency: Each service requires its own container/process
- Testing challenges: Integration testing across multiple services is complex
Decision
We decided to implement a Flavor-based architecture where:- Flavors are modular components that can be composed into applications
- Single application instance can run multiple flavors
- Shared dependency injection container across all flavors
- Flexible deployment: Can run all flavors in one process or split across multiple processes
- Service injection: Each flavor can declare its dependencies and have them automatically injected
Key Design Principles
Flavor Structure
Each flavor is a self-contained module with:- Routes: HTTP endpoints using FastAPI decorators
- Services: Business logic components
- Models: Data structures (if needed)
- Dependencies: Declared in the flavor definition
Consequences
Positive
- Reduced boilerplate: Shared infrastructure code
- Flexible deployment: Single binary that can be configured per environment
- Better testing: Easier to test individual flavors and their interactions
- Resource efficiency: Optional single-process deployment
- Development speed: Faster iteration on individual components
- Maintainability: Clear separation of concerns with shared foundations
Negative
- Learning curve: New architecture pattern for the team
- Abstraction complexity: Additional layer between business logic and HTTP layer
- Migration effort: Existing microservices need to be converted to flavors
Trade-offs
- Runtime flexibility vs. deployment simplicity: Can run as monolith or distributed
- Shared state vs. isolation: Flavors share the same process space when co-located
Alternatives Considered
-
Traditional Microservices: Continue with separate services
- Rejected due to existing pain points and complexity
-
Monolithic Application: Single large codebase
- Rejected due to lack of modularity and team scaling issues
-
Plugin Architecture: Dynamic loading of plugins
- Rejected due to complexity and Python’s limitations with dynamic loading
-
Event-Driven Architecture: Message-based communication
- Considered for future enhancement, not mutually exclusive
Implementation Notes
- Base class
BaseFlavorprovides constructor-based initialization BaseController,BaseService,BaseConfigclasses for component structure- Graph-based dependency injection with automatic resolution
- FastAPI routers for HTTP endpoint registration
- Container-based service management with automatic dependency wiring