组织复杂ORM查询的设计模式?

2 投票
2 回答
1558 浏览
提问于 2025-04-15 19:18

我正在开发一个网络API,后端大约有10个表,里面有一些一对多和多对多的关系。这个API基本上是一个数据库的封装器,可以进行经过验证的更新和条件查询。它是用Python写的,我使用SQLAlchemy来处理对象关系映射(ORM),用CherryPy来处理HTTP请求。

到目前为止,我把API执行的30多个查询分成了各自的函数,代码大概是这样的:

# in module "services.inventory"
def find_inventories(session, user_id, *inventory_ids, **kwargs):
    query = session.query(Inventory, Product)
    query = query.filter_by(user_id=user_id, deleted=False)
    ...
    return query.all()

def find_inventories_by(session, app_id, user_id, by_app_id, by_type, limit, page):
    ....

# in another service module
def remove_old_goodie(session, app_id, user_id):
    try:
        old = _current_goodie(session, app_id, user_id)
        services.inventory._remove(session, app_id, user_id, [old.id])
    except ServiceException, e:
        # log it and do stuff
....

CherryPy的请求处理器会根据需要调用这些查询方法,这些方法分散在几个服务模块中。这样做的原因是,因为这些查询需要访问多个模型类,所以它们不属于单独的模型,并且这些数据库查询应该和直接处理API请求分开。

我意识到上面的代码在重构的领域里可能被称为外部方法。虽然我可以暂时接受这种组织方式,但现在看起来有点乱,我想找个办法来重构这段代码。

  • 因为这些查询直接与API及其业务逻辑相关,所以很难像获取器和设置器那样进行通用化。
  • 重复传递session参数感觉不太好,但由于当前API的实现为每个API调用创建一个新的CherryPy处理器实例,因此session对象也是新的,没有全局的方法来获取当前的session

有没有什么成熟的模式来组织这些查询?我应该继续使用外部方法,试着统一函数的参数顺序、命名规则等吗?你有什么建议?

2 个回答

1

SQLAlchemy 强烈建议将会话创建器放在一些全局配置中。

也就是说,sessionmaker() 这个函数应该在应用程序的全局范围内被调用,然后返回的类应该可以被应用程序的其他部分使用,这样大家都能用同一个类来创建会话。

如果查询分散在不同的模块中,这并不是个大问题。Django 的对象关系映射(ORM)就是这样工作的。一个网站通常由多个 Django “应用”组成,这听起来就像你的站点有很多“服务模块”。

把多个服务结合在一起就是一个应用程序的目的。其实没有很多更好的选择。

1

在多线程环境中,要想全局访问当前的会话,标准的方法是使用ScopedSession。在和你的框架结合时,有几个重要的方面需要注意,主要是事务控制和在请求之间清理会话。一个常见的做法是在一个模块中设置一个autocommit=False(默认值)的ScopedSession,然后把任何业务逻辑的执行放在一个try-catch块里,这样如果出现异常就会回滚,如果方法成功就提交,最后调用Session.remove()。这样,业务逻辑就可以在全局范围内导入Session对象,并像使用普通会话一样使用它。

似乎已经有一个现成的CherryPy-SQLAlchemy集成模块,但因为我对CherryPy不太熟悉,所以无法评论它的质量。

把查询封装成函数是完全可以的,并不是所有东西都需要放在类里面。如果函数数量太多,可以按主题拆分成不同的模块。

我发现,把常用的条件片段提取出来是很有用的。它们通常可以很好地作为模型类中的类方法。除了提高可读性和减少重复外,它们在一定程度上也起到了隐藏实现的作用,这样在重构数据库时就不会那么痛苦。(例如:你可以用Foo.is_valid()来代替(Foo.valid_from <= func.current_timestamp()) & (Foo.valid_until > func.current_timestamp())

撰写回答