Skip to main content

ADR-008: Flavor Architecture Modernization

Context

The framework’s flavor architecture used class attributes to define components, which prevented dependency injection, made testing difficult, and couldn’t leverage the new graph-based dependency resolution system.

Decision

Migrated from class attribute-based component definition to constructor-based initialization for all flavor components, enabling proper dependency injection and improved testability.

Key Changes

  • Constructor-Based Flavor Definition: Components defined through constructor parameters
  • Dependency Injection Integration: Components receive dependencies through constructors
  • Base Class Separation: Dedicated base classes for controllers, services, repositories, and config
  • Graph-Based Resolution: Full compatibility with automatic dependency resolution

Migration Pattern

Old Class Attribute Pattern:
class UserFlavor(BaseFlavor):
    controller = UserController
    services = [UserService]
    repositories = [UserRepository]

class UserController(BaseController):
    prefix = "/users"
    
    def get_user(self, user_id: int):
        user_service = get_service(UserService)  # Anti-pattern
        return user_service.get_user(user_id)
New Constructor Pattern:
class UserFlavor(BaseFlavor):
    def __init__(self):
        super().__init__(
            controller=UserController,
            services=[UserService],
            repositories=[UserRepository],
            config=UserConfig
        )

class UserController(BaseController):
    def __init__(self, user_service: UserService):
        super().__init__(prefix="/users", tags=["users"])
        self.user_service = user_service
    
    @Get("/{user_id}")
    def get_user(self, user_id: int):
        return self.user_service.get_user(user_id)

Consequences

Positive

  • Proper dependency injection through constructors
  • Improved testability with easy mocking
  • Configuration injection into specific components
  • Runtime flexibility for conditional components
  • Type safety with constructor parameters
  • Compatibility with graph-based dependency resolution

Negative

  • Migration effort for existing flavors
  • Slightly more verbose component definitions
  • Learning curve for dependency injection concepts
  • Breaking change from class attribute pattern

Usage Example

class UserService(BaseService):
    def __init__(self, user_repository: UserRepository, config: UserConfig):
        super().__init__()
        self.user_repository = user_repository
        self.config = config

class UserFlavor(BaseFlavor):
    def __init__(self):
        super().__init__(
            controller=UserController,
            services=[UserService],
            repositories=[UserRepository],
            config=UserConfig
        )
Components now receive dependencies through constructors, enabling clean architecture and easy testing.
I