如何组织数据库访问层?

Posted

技术标签:

【中文标题】如何组织数据库访问层?【英文标题】:How to organize a Data Base Access layer? 【发布时间】:2009-08-25 05:35:09 【问题描述】:

我正在使用 SqlAlchemy,一个 Python ORM 库。而我以前是通过调用SqlAlchemy API直接从业务层直接访问数据库的。

但后来我发现这会导致运行所有测试用例的时间过多,现在我想也许我应该创建一个数据库访问层,这样我就可以在测试期间使用模拟对象而不是直接访问数据库。

我认为有两种选择:

    使用包含数据库连接和许多方法(如 addUser/delUser/updateUser、addBook/delBook/updateBook)的单个类。但这意味着这个会非常大。

    另一种方法是创建不同的管理器类,例如“UserManager”、“BookManager”。但这意味着我必须将管理器列表传递给业务层,这似乎有点麻烦。

您将如何组织数据库层?

【问题讨论】:

【参考方案1】:

这是个好问题! 这个问题并非微不足道,可能需要多种方法来解决。 例如:

    组织代码,这样就可以在不访问数据库的情况下测试大部分应用程序逻辑。这意味着每个类都有访问数据的方法和处理数据的方法,并且可以轻松测试第二个类。 当您需要测试数据库访问时,您可以使用代理(因此,如解决方案 #1);您可以将其视为 SqlAlchemy 的引擎或 SA 的替代品。在这两种情况下,您可能需要考虑self initializing fake。 如果代码不涉及存储过程,请考虑使用内存数据库,就像 Lennart 所说的那样(即使在这种情况下,将其称为“单元测试”可能听起来有点奇怪!)。

但是,根据我的经验,一切都很容易说出来,然后在你上场时突然跌倒。例如,当大部分逻辑都在 SQL 语句中时该怎么办?如果访问数据与其处理严格交错怎么办?有时您可能能够进行重构,有时(尤其是对于大型和遗留应用程序)则不能。

最后,我认为这主要是心态的问题。 如果您认为需要进行单元测试,并且需要让它们快速运行,那么您可以以某种方式设计您的应用程序,以便更轻松地进行单元测试。 不幸的是,这并不总是正确的(许多人认为单元测试可以在一夜之间运行,所以时间不是问题),而且你得到的东西并不是真正可单元测试的。

【讨论】:

【参考方案2】:

我会在测试期间建立一个连接到内存数据库的数据库连接。像这样:

sqlite_memory_db = create_engine('sqlite://')

这将尽可能快,您也没有连接到真正的数据库,而只是内存中的一个临时数据库,因此您不必担心测试后剩余的更改测试等等。而且你不必嘲笑任何东西。

【讨论】:

你好,因为我的一些同事坚持我们应该使用存储过程,我无法控制,所以 sqlite 对我来说不是一个可能的选择。 顺便说一句:sqlite 不支持存储过程。 嗯。这意味着您将不得不模拟代码的重要部分(存储过程)。这将使测试变得不那么有用。棘手的情况。【参考方案3】:

捕获对数据库的修改的一种方法是使用 SQLAlchemy 会话扩展机制并使用以下方式拦截对数据库的刷新:

from sqlalchemy.orm.attributes import instance_state
from sqlalchemy.orm import SessionExtension

class MockExtension(SessionExtension):
    def __init__(self):
        self.clear()

    def clear(self):
        self.updates = set()
        self.inserts = set()
        self.deletes = set()

    def before_flush(self, session, flush_context, instances):
        for obj in session.dirty:
            self.updates.add(obj)
            state = instance_state(obj)
            state.commit_all()
            session.identity_map._mutable_attrs.discard(state)
            session.identity_map._modified.discard(state)

        for obj in session.deleted:
            self.deletes.add(obj)
            session.expunge(obj)

        self.inserts.update(session.new)
        session._new = 

然后对于测试,您可以使用该模拟配置您的会话,看看它是否符合您的期望。

mock = MockExtension()
Session = sessionmaker(extension=[mock], expire_on_commit=False)

def do_something(attr):
    session = Session()
    obj = session.query(Cls).first()
    obj.attr = attr
    session.commit()

def test_something():
    mock.clear()
    do_something('foobar')
    assert len(mock.updates) == 1
    updated_obj = mock.updates.pop()
    assert updated_obj.attr == 'foobar'

但是无论如何,您至少需要对数据库进行一些测试,因为您至少想知道您的查询是否按预期工作。请记住,您还可以通过session.update().delete().execute() 对数据库进行修改。

【讨论】:

【参考方案4】:

SQLAlchemy 为making mocking easier 提供了一些工具——也许这比尝试重写项目的整个部分更容易?

【讨论】:

感谢@brool,但该链接现在已断开。 :(

以上是关于如何组织数据库访问层?的主要内容,如果未能解决你的问题,请参考以下文章

开始构建数据访问层。要考虑的事情?

01三层架构简介

如何使用 GitHub REST API 通过我的个人访问令牌访问组织拥有的私有存储库中的文件数据?

数据访问层应该如何构建?

如何适当地设计数据访问层?

当使用实体框架作为数据访问层时,如何实现业务逻辑层?