【问题标题】:Database client abstractions数据库客户端抽象
【发布时间】:2020-05-27 05:03:31
【问题描述】:

我读过很多次,在任何数据库客户端上写一个抽象层是个好主意,这样您就可以在不影响代码的任何其他部分的情况下更改 DB 层。

在我的项目中,我大量使用 BigQuery 和 Firebase - 但客户端库使用起来非常简单,我不确定我可以添加什么作为抽象层,例如:

  await bigquery
    .dataset(datasetId)
    .table(tableId)
    .insert(rows);

我可以将它包装在一个函数中,其中包括一些错误处理。

另一种选择是让这个新客户端与我的域更加耦合,并公开可以保存特定数据集的方法,而不是仅仅采用 rows 对象 - 这似乎是有害的。

nodejs应用中如何抽象数据库?

【问题讨论】:

    标签: node.js database design-patterns architecture abstraction


    【解决方案1】:

    最重要的部分是让您的域不依赖于实施细节。数据库是一个实现细节。

    因此,将其包装在一个函数中以赋予它另一个名称并不重要。关键是让您的域不依赖于此。

    你是怎么做到的?

    通过定义一个接口(这是您的抽象),它说“我需要存储/获取数据,无论数据库后面是什么”。然后,您在 prod 中注入该接口的 BigQuery 实现......并且很容易在测试甚至开发模式下注入内存中的实现。

    现在,在 JavaScript 中,没有显式接口。但是抽象的想法仍然存在。这只是隐含的。界面将是您实际使用的东西(鸭子打字)。

    使用你的具体例子

    假设你有:

    async function doSomething() {
      // Some other domain stuff…
    
      await bigquery
        .dataset(datasetId)
        .table(tableId)
        .insert(rows);
    }
    

    这没有多大帮助:

    function insertInDb(rows) {
      return bigquery
        .dataset(datasetId)
        .table(tableId)
        .insert(rows);
    }
    
    
    async function doSomething() {
      // Some other domain stuff…
    
      await insertInDb(rows);
    }
    

    但是,这会有所帮助:

    function insertInDb(rows) {
      return bigquery
        .dataset(datasetId)
        .table(tableId)
        .insert(rows);
    }
    
    
    async function doSomething(insertInDb) {
      // Some other domain stuff…
    
      await insertInDb(rows);
    }
    

    区别很细微,但实际的insertInDb函数是在运行时注入的,这会反转依赖关系。

    更进一步:数据库上的 Repository 抽象

    现在,这个概念通常被命名为存储库。

    如果您花一些时间更好地表达领域概念,您的最终代码可能如下所示:

    class ScoreRepositoryBigQuery {
      save(newScore) {
        // Some logic to convert `newScore` into BigQuery compatible `rows`…
    
        return this.bigquery
          .dataset(this.datasetId)
          .table(this.tableId)
          .insert(rows);
      }
    }
    
    
    async function answerQuestion(scoreRepository) {
      // Some other domain stuff…
    
      await scoreRepository.save(newScore);
    }
    

    使用不同的存储机制(例如 MongoDB、第三方服务、内存实现等)创建新的 ScoreRepository 会很容易。

    您只需要实现隐式接口(例如,它应该有一个异步 save() 方法,该方法接受 newScore 并存储它)。

    无需触及其余代码,因为它不关心实际的实现。

    所以 this 将是一个有用的抽象层。

    【讨论】:

    • 谢谢@nicoespeon!在您的第一个示例中,您“反转了依赖项”,但为什么这很有用?我会导入要存储到文件中的函数,或者必须将其作为 arg 传递给函数 - 我不确定这样做的好处。
    • 嘿@dendog!感谢您的提问,我意识到我没有解释为什么它很有用。通过将其作为参数传递给函数,您可以决定传递一个不同的参数。你什么时候会通过一个不同的?如果您想更改数据的来源。例如。您想提供一个在开发模式下不连接到 Firebase 的内存实现......或在测试中!职责分离,让调用者决定数据应该存储在哪里。保持该决定的逻辑不可知论。这只能通过“反转依赖”来实现。
    • 再次感谢@nicoespeon - 我认为它开始融合在一起,存储库模式看起来不错,我认为这是一个很好的例子github.com/ErickWendel/…
    猜你喜欢
    • 1970-01-01
    • 2011-03-30
    • 2011-12-18
    • 1970-01-01
    • 2011-11-09
    • 1970-01-01
    • 2017-06-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多