数据库客户端抽象
Database client abstractions
我读过很多次,在任何数据库客户端上编写一个抽象层是个好主意,这样您就可以更改数据库层而不影响代码的任何其他部分。
在我的项目中,我大量使用 BigQuery 和 Firebase - 但客户端库使用起来非常简单,我不确定我可以添加什么作为抽象层,例如:
await bigquery
.dataset(datasetId)
.table(tableId)
.insert(rows);
我可以将它包装在一个函数中,其中包含一些错误处理。
另一种选择是让这个新客户端与我的域更加耦合,并公开保存特定数据集的方法,而不是只获取一个 rows
对象——这似乎是有害的。
如何在nodejs应用中抽象出一个数据库?
最重要的部分是使您的域不依赖于实施细节。数据库是一个实现细节。
因此,将其包装在一个函数中以给它另一个名称并不重要。重点是让您的域不依赖于此。
你是怎么做到的?
通过定义一个表示 "I need to store/get the data, whatever DB is behind" 的接口(这是您的抽象)。然后,您在产品中注入该接口的 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
并存储它)。
无需接触其余代码,因为它不关心实际的实现。
所以这个将是一个有用的抽象层。
我读过很多次,在任何数据库客户端上编写一个抽象层是个好主意,这样您就可以更改数据库层而不影响代码的任何其他部分。
在我的项目中,我大量使用 BigQuery 和 Firebase - 但客户端库使用起来非常简单,我不确定我可以添加什么作为抽象层,例如:
await bigquery
.dataset(datasetId)
.table(tableId)
.insert(rows);
我可以将它包装在一个函数中,其中包含一些错误处理。
另一种选择是让这个新客户端与我的域更加耦合,并公开保存特定数据集的方法,而不是只获取一个 rows
对象——这似乎是有害的。
如何在nodejs应用中抽象出一个数据库?
最重要的部分是使您的域不依赖于实施细节。数据库是一个实现细节。
因此,将其包装在一个函数中以给它另一个名称并不重要。重点是让您的域不依赖于此。
你是怎么做到的?
通过定义一个表示 "I need to store/get the data, whatever DB is behind" 的接口(这是您的抽象)。然后,您在产品中注入该接口的 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
并存储它)。
无需接触其余代码,因为它不关心实际的实现。
所以这个将是一个有用的抽象层。