Skip to main content

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
The team wanted to maintain the benefits of modular architecture while addressing these issues.

Decision

We decided to implement a Flavor-based architecture where:
  1. Flavors are modular components that can be composed into applications
  2. Single application instance can run multiple flavors
  3. Shared dependency injection container across all flavors
  4. Flexible deployment: Can run all flavors in one process or split across multiple processes
  5. Service injection: Each flavor can declare its dependencies and have them automatically injected

Key Design Principles

from tasteful import TastefulApp
from tasteful.flavors import HealthFlavor
from my_app.flavors import CustomFlavor


# All services in one process
app = TastefulApp(
    title="My Application",
    version="1.0.0",
    flavors=[
        HealthFlavor,
        CustomFlavor
    ]
)

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

  1. Traditional Microservices: Continue with separate services
    • Rejected due to existing pain points and complexity
  2. Monolithic Application: Single large codebase
    • Rejected due to lack of modularity and team scaling issues
  3. Plugin Architecture: Dynamic loading of plugins
    • Rejected due to complexity and Python’s limitations with dynamic loading
  4. Event-Driven Architecture: Message-based communication
    • Considered for future enhancement, not mutually exclusive

Implementation Notes

  • Base class BaseFlavor provides constructor-based initialization
  • BaseController, BaseService, BaseConfig classes for component structure
  • Graph-based dependency injection with automatic resolution
  • FastAPI routers for HTTP endpoint registration
  • Container-based service management with automatic dependency wiring

References

I