【问题标题】:Design pattern to organize non-trivial ORM queries?组织非平凡的 ORM 查询的设计模式?
【发布时间】:2011-01-16 22:31:41
【问题描述】:

我正在开发一个 Web 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 访问的直接处理分开。

我意识到上面的代码在重构领域可能被称为Foreign Methods。我可以在一段时间内接受这种组织方式,但随着事情开始变得有些混乱,我正在寻找一种方法来重构这段代码。

  • 由于查询直接与 API 及其业务逻辑相关联,因此它们很难像 getter 和 setter 一样一概而论。
  • 像这样重复 session 参数有点味道,但由于 API 的当前实现为每个 API 调用创建了一个新的 CherryPy 处理程序实例,因此为 session 对象,没有全局方法来获取当前session

是否有一种完善的模式来组织此类查询?我应该坚持使用外部方法并尝试统一函数签名(参数排序、命名约定等)吗?你有什么建议?

【问题讨论】:

  • “自己的方法”?你是说“功能”吗?它们不能是方法,因为没有类,也没有 self 变量。除非您已将 self 变量重命名为 session
  • 是的,你是对的。目前它们是模块级函数,而不是方法。我已经更正了问题中的用词不当。

标签: python design-patterns orm refactoring sqlalchemy


【解决方案1】:

在线程环境中全局访问当前会话的标准方法是ScopedSession。与您的框架集成时,有一些重要方面需要正确处理,主要是事务控制和清除请求之间的会话。一个常见的模式是在模块中有一个 autocommit=False(默认)ScopedSession,并将任何业务逻辑执行包装在一个 try-catch 子句中,该子句在发生异常时回滚并在方法成功时提交,然后最终调用 Session.remove ()。然后,业务逻辑会将 Session 对象导入全局范围并像常规会话一样使用它。

似乎有一个现有的CherryPy-SQLAlchemy integration module,但由于我对 CherryPy 不太熟悉,我无法评论它的质量。

将查询封装为函数就可以了。不是所有的东西都需要在一个类中。如果它们太多,只需按主题分成单独的模块。

我发现有用的是排除了常见的标准片段。它们通常非常适合模型类的类方法。除了提高可读性和减少重复之外,它们还可以在某种程度上作为实现隐藏抽象,从而减少重构数据库的痛苦。 (例如:而不是 (Foo.valid_from <= func.current_timestamp()) & (Foo.valid_until > func.current_timestamp()) 你会有 Foo.is_valid()

【讨论】:

  • 我选择你的作为提供实用的建议。谢谢
【解决方案2】:

SQLAlchemy 强烈建议会话创建者成为某些全局配置的一部分。

它的目的是 sessionmaker() 在全局范围内调用函数 申请范围,以及 返回的课程可用于 应用程序的其余部分作为 用于实例化的单个类 会议。

独立模块中的查询不是一个有趣的问题。 Django ORM 就是这样工作的。一个网站通常由多个 Django“应用程序”组成,这听起来就像您的网站有很多“服务模块”。

将多个服务组合在一起是应用程序的重点。没有很多更好的选择。

【讨论】:

  • 您的引用说 Session 类应该是全局范围的一部分,而不是会话对象本身应该是。在线程应用程序中将会话对象作为全局范围的一部分听起来不是一个好主意。
  • @Singletoned:修正了答案以澄清这一点。谢谢!
猜你喜欢
  • 2011-08-31
  • 1970-01-01
  • 2012-05-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多