出于更好的可测试性和其他原因,最好将SQLAlchemy数据库会话配置为非全局的,如以下问题所述:
how to setup sqlalchemy session in celery tasks with no global variable(并在https://github.com/celery/celery/issues/3561中进行了讨论)
现在,问题是,如何优雅地处理元数据?如果我的理解是正确的,则元数据只能有一次,例如:
engine = create_engine(DB_URL, encoding='utf-8', pool_recycle=3600,
pool_size=10)
# db_session = get_session() # this is old global session
meta = MetaData()
meta.reflect(bind=engine)
由于性能原因,对每个任务的执行进行反思是不好的,元数据或多或少是稳定的并且是线程安全的结构(如果我们只读的话)。
但是,元数据有时会发生变化(celery不是数据库模式的“所有者”),从而导致工作程序出错。
以一种可测试的方式处理meta
的优雅方法,又仍然能够对基础数据库的更改做出反应是什么? (如果相关,请使用英语)。
我当时曾想过使用Alembic版本更改作为重新反映的信号,但还不确定如何使它在芹菜中正常工作。例如,如果一个以上的工作人员立即感知到更改,则可以以非线程安全的方式对待全局meta
。
如果这很重要,那么在这种情况下celery的使用是独立的,celery应用程序中不存在任何Web框架模块/应用程序/任何内容。由于仅使用SQLAlchemy Core,而不使用对象映射器,因此该问题也得到了简化。
答案 0 :(得分:1)
这只是部分解决方案,适用于SQLAlchemy ORM(但我想类似的事情对于Core来说很容易实现)。
要点:
os.environ
BaseModel = automap_base()
,然后表类将BaseModel用作超类,通常仅使用一个参数-__tablename__
,但是可以在其中添加任意关系,属性(非常类似于正常的ORM使用) BaseModel.prepare(ENGINE, reflect=True)
在模块级别的DB_URL
中测试(使用pytest)注入环境变量(例如conftest.py
)。
重要的时刻:database_session
始终在任务函数中启动(即调用了工厂函数),并明确地传播到所有函数中。这种方式允许自然地控制工作单元,通常每个任务一个事务。这也简化了测试,因为所有使用数据库的功能都可以通过伪或真实(测试)数据库会话提供。
“任务功能”是上面的一个函数,该函数在该函数中被调用,并由任务装饰-这样就可以在没有任务机制的情况下测试任务功能。
这只是部分解决方案,因为重做反射不存在。如果任务工作者可以停止一会儿(并且由于架构更改数据库总是会停机),因为它们通常是后台任务,那么这不会造成问题。还可以通过某些外部监视程序来重新启动工作程序,该监视程序可以监视数据库更改。通过使用监督者或其他方法来控制在前台运行的芹菜工人,可以使此操作变得方便。
总而言之,在我解决了上述问题之后,我更加重视“显性胜于隐性”的哲学。所有那些神奇的“ app”(无论是芹菜还是Flask中的“ request”)都可能在功能签名中引入小写的缩写,但我宁愿在调用链中传递某种上下文,以提高可测试性和更好的上下文理解以及管理。