跳转至

Cullinan 2.0 迁移指南

版本:v0.90
作者:Plumeink

本指南帮助您从 Cullinan 1.x 迁移到 2.0(0.90)。

破坏性变更

1. 单一入口

之前(1.x):

from cullinan.core import get_injection_registry, get_service_registry

registry = get_injection_registry()
registry.add_provider_source(my_source)

之后(2.0):

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

ctx = ApplicationContext()
ctx.register(Definition(
    name='MyService',
    factory=lambda c: MyService(),
    scope=ScopeType.SINGLETON,
    source='service:MyService'
))
ctx.refresh()

2. 注册表冻结

在 2.0 中,注册表在 refresh() 后被冻结。任何尝试注册新依赖的操作都会抛出 RegistryFrozenError

之前(1.x):

# 可以随时注册
registry.register('NewService', NewService)

之后(2.0):

ctx = ApplicationContext()
ctx.register(...)  # refresh 前可以
ctx.refresh()
ctx.register(...)  # RegistryFrozenError!

3. 作用域强制

请求作用域依赖现在严格要求 RequestContext

之前(1.x):

# 可能静默失败或返回错误实例
instance = registry.get('RequestScoped')

之后(2.0):

ctx.enter_request_context()
try:
    instance = ctx.get('RequestScoped')  # 正常
finally:
    ctx.exit_request_context()

# 没有上下文:
ctx.get('RequestScoped')  # ScopeNotActiveError!

4. 结构化异常

所有异常现在都携带结构化诊断字段。

之前(1.x):

try:
    registry.resolve('Missing')
except Exception as e:
    print(str(e))  # 通用消息

之后(2.0):

from cullinan.core.diagnostics import DependencyNotFoundError

try:
    ctx.get('Missing')
except DependencyNotFoundError as e:
    print(e.dependency_name)      # 'Missing'
    print(e.resolution_path)      # ['ParentService', 'Missing']
    print(e.candidate_sources)    # [{'source': '...', 'reason': '...'}]

5. 循环依赖检测

循环依赖现在产生稳定、有序的链路。

之前(1.x):

# 无序,不一致的输出
CircularDependencyError: Circular dependency detected

之后(2.0):

# 稳定、有序的链路
CircularDependencyError: 检测到循环依赖: A -> B -> C -> A

迁移步骤

第 1 步:更新导入

# 旧导入(已弃用)
from cullinan.core import get_injection_registry, Inject, InjectByName

# 新导入(2.0)
from cullinan.core.container import ApplicationContext, Definition, ScopeType

第 2 步:转换服务注册

# 旧风格
@service
class UserService:
    user_repo = Inject()

# 新风格
ctx.register(Definition(
    name='UserService',
    factory=lambda c: UserService(user_repo=c.get('UserRepository')),
    scope=ScopeType.SINGLETON,
    source='service:UserService'
))

第 3 步:更新应用启动

# 旧风格(app.py)
from cullinan.app import CullinanApplication
app = CullinanApplication()
app.run()

# 新风格
from cullinan.core.container import ApplicationContext

ctx = ApplicationContext()
# ... 注册定义 ...
ctx.refresh()
# ... 运行 tornado ...
ctx.shutdown()

第 4 步:处理请求作用域

# 在请求处理器中
class MyHandler(RequestHandler):
    def get(self):
        ctx.enter_request_context()
        try:
            service = ctx.get('RequestScopedService')
            # ... 使用服务 ...
        finally:
            ctx.exit_request_context()

已弃用的 API

以下 API 在 2.0 中已弃用,将在 3.0 中移除:

已弃用 API 替代方案
get_injection_registry() ApplicationContext
get_service_registry() ApplicationContext.register()
@service 装饰器(自动注入) 显式 Definition 注册
Inject() / InjectByName() 使用 ctx.get() 的 factory
DependencyInjector ApplicationContext

兼容模式

迁移期间,您可以启用兼容模式(已弃用,将被移除):

from cullinan.core.container import ApplicationContext

ctx = ApplicationContext()
ctx.set_strict_mode(False)  # 允许一些旧行为

警告: 兼容模式仅用于迁移。生产部署前务必迁移到严格模式。

测试迁移

运行所有 2.0 测试以验证您的迁移:

python -m pytest tests/test_ioc_di_v2_*.py -v

获取帮助