Skip to content

Cullinan Framework Architecture (Updated)

Version: v0.90
Last Updated: 2025-12-25
Author: Plumeink
Status: Updated


Table of Contents

  1. Architecture Overview
  2. Core Components
  3. IoC/DI 2.0 System
  4. Extension Mechanism
  5. Startup Flow
  6. Request Processing Flow
  7. Module Scanning
  8. Performance Optimization
  9. Migration from 0.83

Architecture Overview

Cullinan is a Tornado-based web framework that adopts the IoC/DI (Inversion of Control/Dependency Injection) design pattern and provides a decorator-driven development experience.

Core Design Principles

  • Non-invasive: Implement features through decorators and annotations without inheriting complex base classes
  • Dependency Injection: Automatically manage component dependencies
  • Extension-friendly: Unified extension point registration and discovery mechanism
  • High Performance: Optimized startup and runtime performance

Architecture Layers

┌─────────────────────────────────────────────────────┐
│          Application Layer                           │
│  - Business Logic (Controllers)                      │
│  - Service Layer (Services)                          │
└─────────────────┬───────────────────────────────────┘
                  │
┌─────────────────┼───────────────────────────────────┐
│          Framework Layer                             │
│  ┌──────────────┴──────────────┐                     │
│  │    Extension Mechanism      │                     │
│  │  - Middleware               │                     │
│  │  - Extension Points         │                     │
│  └──────────────┬──────────────┘                     │
│  ┌──────────────┴──────────────┐                     │
│  │  IoC/DI 2.0 Container       │                     │
│  │  - ApplicationContext       │                     │
│  │  - Definition + Factory     │                     │
│  │  - ScopeManager             │                     │
│  │  - Structured Diagnostics   │                     │
│  └──────────────┬──────────────┘                     │
│  ┌──────────────┴──────────────┐                     │
│  │    Core Foundation          │                     │
│  │  - Lifecycle Management     │                     │
│  │  - Request Context          │                     │
│  │  - Module Scanner           │                     │
│  └─────────────────────────────┘                     │
└─────────────────┬───────────────────────────────────┘
                  │
┌─────────────────┴───────────────────────────────────┐
│          Web Server Layer (Tornado)                  │
│  - IOLoop                                            │
│  - HTTP Server                                       │
│  - Request Handler                                   │
└─────────────────────────────────────────────────────┘

Core Components

1. Core Module (cullinan/core/)

Provides framework infrastructure.

Main Components:

1.1 IoC/DI 2.0 Container

ApplicationContext (Single Entry Point)
    ├── Definition Registry (Immutable definitions)
    ├── Factory (Instance creation)
    └── ScopeManager (Scope management)
        ├── SingletonScope
        ├── PrototypeScope
        └── RequestScope

Core Components (v0.90): - ApplicationContext: Single entry point for all container operations - register(Definition) - Register dependency definition - get(name) / try_get(name) - Resolve dependency - refresh() - Freeze registry, initialize eager beans - shutdown() - Clean up resources

  • Definition: Immutable dependency definition
  • name - Unique identifier
  • factory - Instance creation function
  • scope - ScopeType (SINGLETON/PROTOTYPE/REQUEST)
  • source - Source description for diagnostics

  • Factory: Unified instance creation

  • Creates instances via Definition.factory
  • Delegates to ScopeManager for caching

  • ScopeManager: Unified scope management

  • SINGLETON - Application-level singleton (thread-safe)
  • PROTOTYPE - New instance per resolution
  • REQUEST - Request-scoped instances

New Directory Structure (v0.90):

cullinan/core/
├── container/      # IoC/DI 2.0 API
│   ├── context.py
│   ├── definitions.py
│   ├── factory.py
│   └── scope.py
├── diagnostics/    # Exceptions + rendering
├── lifecycle/      # Lifecycle management
├── request/        # Request context
└── legacy/         # Deprecated 1.x components

1.2 Lifecycle Management

class LifecycleManager:
    - initialize()    # Initialization phase
    - startup()       # Startup phase
    - shutdown()      # Shutdown phase

Lifecycle Hooks: - on_init() - Execute after Service instantiation - on_startup() - Execute after all Services are ready - on_shutdown() - Execute during application shutdown

1.3 Request Context

class RequestContext:
    - request_id: str
    - start_time: float
    - _metadata: Dict (lazy initialization)
    - _cleanup_callbacks: List (lazy initialization)

Optimizations (v0.81+): - Lazy initialization: Save 20-55% memory - Performance: Initialization from 500ns → 350ns

1.4 Scope System

  • SingletonScope: Singleton scope
  • TransientScope: Transient scope (create new each time)
  • RequestScope: Request scope
  • Custom Scope: Support user extensions (e.g., SessionScope)

2. Service Layer (cullinan/service/)

Manages application business services.

Usage:

from cullinan.service import service, Service
from cullinan.core import Inject

@service
class UserService(Service):
    email_service: 'EmailService' = Inject()

    def on_init(self):
        # Initialize resources
        self.db = connect_database()

    def on_shutdown(self):
        # Clean up resources
        self.db.close()

Features: - Singleton pattern (application-level) - Automatic dependency injection - Lifecycle management - Startup error policies (strict/warn/ignore)


3. Controller Layer (cullinan/controller/)

Handles HTTP requests.

Usage:

from cullinan.controller import controller, get_api
from cullinan.core import Inject
from cullinan.params import Path

@controller(url='/api/users')
class UserController:
    user_service: 'UserService' = Inject()

    @get_api(url='/{user_id}')
    async def get_user(self, user_id: int = Path()):
        return self.user_service.get_user(user_id)

Features: - RESTful route mapping - Automatic dependency injection - Request-level instances (new per request) - Automatic parameter parsing and validation


4. Parameter System (cullinan/params/, cullinan/codec/) - New in v0.90

Type-safe parameter handling with automatic conversion and validation.

Module Structure:

cullinan/
├── codec/           # Encoding/Decoding layer
│   ├── base.py     # BodyCodec / ResponseCodec abstractions
│   ├── errors.py   # DecodeError / EncodeError
│   ├── json_codec.py
│   ├── form_codec.py
│   └── registry.py # CodecRegistry
├── params/          # Parameter handling layer
│   ├── base.py     # Param base class + UNSET
│   ├── types.py    # Path/Query/Body/Header/File
│   ├── converter.py # TypeConverter
│   ├── auto.py     # Auto type inference
│   ├── dynamic.py  # DynamicBody
│   ├── validator.py # ParamValidator
│   ├── model.py    # ModelResolver (dataclass)
│   └── resolver.py # ParamResolver
└── middleware/
    └── body_decoder.py # BodyDecoderMiddleware

Usage:

from cullinan.params import Path, Query, Body, DynamicBody

@controller(url='/api/users')
class UserController:
    @get_api(url='/{id}')
    async def get_user(
        self,
        id: int = Path(),
        include_posts: bool = Query(default=False),
    ):
        return {"id": id}

    @post_api(url='/')
    async def create_user(
        self,
        name: str = Body(required=True),
        age: int = Body(default=0, ge=0, le=150),
    ):
        return {"name": name, "age": age}

Features: - Type-safe parameter declaration - Automatic type conversion - Built-in validators (ge, le, regex, etc.) - dataclass and DynamicBody support - Custom codec registration

See Parameter System Guide for details.


5. Extension Mechanism

5.1 Middleware System

from cullinan.middleware import middleware, Middleware

@middleware(priority=100)
class LoggingMiddleware(Middleware):
    def process_request(self, handler):
        logger.info(f"Request: {handler.request.uri}")
        return handler

    def process_response(self, handler, response):
        logger.info(f"Response: {response}")
        return response

Features: - Decorator-driven registration - Priority control (lower numbers execute first) - Bidirectional request/response interception - Support short-circuiting (return None to stop)

5.2 Extension Point Discovery

from cullinan.extensions import list_extension_points

# Query available extension points
points = list_extension_points(category='middleware')
for point in points:
    print(f"{point['name']}: {point['description']}")

6 Extension Categories: 1. Middleware - Request/response interception 2. Lifecycle - Lifecycle hooks 3. Injection - Dependency injection extensions 4. Routing - Route handling 5. Configuration - Configuration management 6. Handler - Request handlers


IoC/DI 2.0 System

New Architecture (v0.90)

┌─────────────────────────────────────┐
│   ApplicationContext (Entry Point)  │
│  - register(Definition)             │
│  - get(name) / try_get(name)        │
│  - refresh() / shutdown()           │
└───────────────┬─────────────────────┘
                │
      ┌─────────┼─────────┐
      ▼         ▼         ▼
┌──────────┐ ┌─────────┐ ┌────────────┐
│Definition│ │ Factory │ │ScopeManager│
│ Registry │ │         │ │            │
└──────────┘ └─────────┘ └────────────┘

Key Improvements

Feature 0.83 (Legacy) 0.90 (2.0)
Entry Point Multiple (IoCFacade, Registries) Single (ApplicationContext)
Definition Mutable Immutable (frozen)
Registry Modifiable at runtime Frozen after refresh()
Scopes Implicit Explicit ScopeType enum
Diagnostics String-based errors Structured exceptions

Usage Patterns

from cullinan.service import service, Service
from cullinan.core import Inject

@service
class UserService(Service):
    email_service: EmailService = Inject()  # Auto-inject

    def on_init(self):
        # Initialization logic
        pass

    def on_startup(self):
        # Execute when service starts
        pass

Features: - Decorators internally use the new IoC/DI 2.0 registration logic - Automatic dependency resolution and lifecycle management - Supports Inject() attribute injection

Pattern 2: Definition-Based Registration (Advanced)

from cullinan.core.container import ApplicationContext, Definition, ScopeType

ctx = ApplicationContext()

# Register with explicit definition (for complex scenarios or third-party integration)
ctx.register(Definition(
    name='UserService',
    factory=lambda c: UserService(c.get('UserRepository')),
    scope=ScopeType.SINGLETON,
    source='service:UserService'
))

ctx.refresh()  # Freeze registry
user_service = ctx.get('UserService')

Use Cases: - Custom instance creation logic required - Integrating third-party library components - Dynamic dependency registration needed


Extension Mechanism

Middleware Execution Flow

Client Request
  ↓
CorsMiddleware (priority=10)      → process_request
  ↓
AuthMiddleware (priority=50)      → process_request
  ↓
LoggingMiddleware (priority=100)  → process_request
  ↓
Handler Processing
  ↓
LoggingMiddleware (priority=100)  → process_response
  ↓
AuthMiddleware (priority=50)      → process_response
  ↓
CorsMiddleware (priority=10)      → process_response
  ↓
Client Response

Priority Guidelines: - 0-50: Critical middleware (CORS, security) - 51-100: Standard middleware (logging, metrics) - 101-200: Application-specific middleware


Startup Flow

Startup Sequence

1. configure(...)
   └── Load configuration

2. Module Scanning
   ├── Auto-scan mode
   │   ├── Detect packaging environment (development/nuitka/pyinstaller)
   │   ├── Scan user packages (user_packages)
   │   └── Collect statistics (New in v0.81+)
   │
   └── Explicit registration mode (New in v0.81+)
       └── Skip scanning, use configured classes directly

3. IoC Container Initialization
   ├── Initialize IoCFacade
   ├── Register Providers
   └── Configure InjectionRegistry

4. Service Initialization
   ├── Initialize in dependency order
   ├── Call on_init() hooks
   ├── Call on_startup() hooks
   └── Error handling (per startup_error_policy)

5. Controller Registration
   ├── Register routes
   └── Configure Handlers

6. Middleware Chain Building
   ├── Sort by priority
   ├── Initialize middleware (on_init)
   └── Build processing chain

7. Start Web Server
   ├── Create Tornado Application
   ├── Register signal handlers (SIGINT/SIGTERM)
   └── Start IOLoop

Startup Performance (v0.81+ Optimized)

Scenario Before After Improvement
Explicit registration mode 69.56 ms 0.08 ms 902x
Small project (10 modules) 50-100 ms 50-100 ms -
Medium project (50 modules) 300-800 ms 150-300 ms 2x

Request Processing Flow

1. Request arrives at Tornado
   └── IOLoop dispatches to Handler

2. Create request context
   ├── RequestContext.create()
   ├── Set request_id
   └── Initialize request-level Scope

3. Middleware processing (request phase)
   ├── Execute process_request() by priority
   ├── May short-circuit (e.g., auth failure)
   └── Pass to next middleware

4. Controller instantiation
   ├── Create Controller instance
   ├── Inject dependencies (via InjectionRegistry)
   └── Parse request parameters

5. Execute business logic
   ├── Call Controller method
   ├── Service layer processing
   └── Return response

6. Middleware processing (response phase)
   ├── Execute process_response() in reverse
   ├── Can modify response
   └── Add response headers

7. Clean up request context
   ├── Execute cleanup callbacks
   ├── Clear request-level dependencies
   └── Destroy RequestContext

8. Return response to client

Module Scanning

Scanning Strategy (Multi-environment Support)

┌─────────────────────────────────────┐
│    Detect Packaging Environment      │
└──────────┬──────────────────────────┘
           │
     ┌─────┴─────┬─────────┬─────────┐
     ▼           ▼         ▼         ▼
Development  Nuitka   PyInstaller  Other
     │           │         │         │
     ▼           ▼         ▼         ▼
  Standard   sys.modules  _MEIPASS  Fallback
  Scanning

Scanning Statistics (New in v0.81+)

from cullinan.scan_stats import get_scan_stats_collector

collector = get_scan_stats_collector()
stats = collector.get_aggregate_stats()

# Output:
# {
#   'total_scans': 1,
#   'avg_duration_ms': 66.06,
#   'total_modules': 35,
#   'fastest_scan_ms': 66.06,
#   'slowest_scan_ms': 66.06
# }

Collected Metrics: - Total duration (phased) - Module count (discovered/filtered/cached) - Scan mode (auto/explicit/cached) - Packaging environment (development/nuitka/pyinstaller) - Error recording


Performance Optimization

Implemented Optimizations (v0.81+)

Optimization Before After Improvement
Module Scanning (Explicit) 69.56 ms 0.08 ms 902x
RequestContext Init 500 ns 350 ns 30%
RequestContext Memory 536 B 240 B 55%
Dependency Resolution (Cached) - 0.26 μs Extremely Fast
Logging Overhead (Production) 150 ns 100 ns 33%

Optimization Strategies

1. Explicit Registration Mode

from cullinan import configure

configure(
    explicit_services=[DatabaseService, CacheService],
    explicit_controllers=[UserController, AdminController],
    auto_scan=False  # Skip scanning
)

Benefit: 902x startup speed improvement (test scenario)

2. Lazy Initialization

  • RequestContext fields created on demand
  • 55% memory reduction

3. Smart Caching

  • Module scanning result cache
  • IoC dependency resolution cache (0.26 μs)
  • Provider instance cache

4. Structured Logging

  • Production log optimization
  • Lazy evaluation reduces overhead

Configuration Options

Core Configuration

from cullinan import configure

configure(
    # Basic configuration
    port=8080,
    debug=True,

    # Performance optimization
    explicit_services=[...],      # Explicit service registration
    explicit_controllers=[...],   # Explicit controller registration
    auto_scan=False,              # Disable auto-scanning
    user_packages=['myapp'],      # Limit scan scope

    # Startup behavior
    startup_error_policy='strict', # 'strict'/'warn'/'ignore'

    # Extension configuration
    middlewares=[...],            # Custom middleware
    handlers=[...],               # Custom Handler
)

Best Practices

1. Dependency Injection

Recommended: Use type injection

@service
class UserService(Service):
    email_service: EmailService = Inject()

Avoid: Manually getting dependencies

# Not recommended
registry = get_service_registry()
email_service = registry.get_instance('EmailService')

2. Performance Optimization

Recommended: Use explicit registration (large projects)

configure(
    explicit_services=[...],
    explicit_controllers=[...],
    auto_scan=False
)

Recommended: Limit scan scope

configure(
    user_packages=['myapp', 'myapp.extensions'],
    exclude_packages=['tests', '__pycache__']
)

3. Middleware Design

Recommended: Single responsibility

@middleware(priority=50)
class AuthMiddleware(Middleware):  # Only handles auth
    pass

@middleware(priority=60)
class RoleMiddleware(Middleware):  # Only handles permissions
    pass

Avoid: Mixed responsibilities

@middleware(priority=50)
class AuthAndLoggingMiddleware(Middleware):  # Mixed
    pass


References


Migration from 0.83

For migration from version 0.83 to 0.90, see the Import Migration Guide.

Key changes: - cullinan.core.application_contextcullinan.core.container - cullinan.core.definitionscullinan.core.container - cullinan.core.exceptionscullinan.core.diagnostics - cullinan.core.contextcullinan.core.request - Legacy components moved to cullinan.core.legacy/


Version: v0.90
Author: Plumeink
Last Updated: 2025-12-25