将 RESTful API 重构为更小的函数
Refactoring RESTful API into smaller functions
背景
我有一个 NodeJS 应用程序,打算用作 RESTful API。它使用 Mongoose 与后端的 MongoDB 数据库连接。该应用程序基于嵌套文档的理念构建。它使用以下架构存储 wikis
、sections
和 notes
:
const noteSchema = new mongoose.Schema({ title: String, content: String });
const sectionSchema = new mongoose.Schema({ title: String, notes: [noteSchema] });
const wikiSchema = new mongoose.Schema({ title: String, sections: [sectionSchema] });
所有这些都可以通过 wiki 的单一模型访问:
const wikiModel = mongoose.model("Wiki", wikiSchema);
用户可以在每个端点上执行 GET、POST、PUT、DELETE 请求来操作其中的数据。如果有人想要 ping Notes 端点(层次结构中最下方),它必须首先检查 wiki,然后检查部分端点,以确保它们都存在。
这是一个例子:
app.get('/:wikiTitle/:sectionTitle/:noteTitle', function(req, res) {
wikiModel.findOne({ title: req.params.wikiTitle }, function(err, wiki) {
if (err) {
res.send('\nAn unkown error has occured');
console.error(err);
} else if (wiki) {
const sectionTitle = req.params.sectionTitle;
wikiModel.findOne({ 'sections.title': sectionTitle }, function(err, section) {
if (err) {
res.send('\nAn unkown error has occured');
console.error(err);
} else if (section) {
const noteTitle = req.params.noteTitle;
wikiModel.findOne({ 'sections.notes.title': noteTitle }, function(err, n) {
if (err) {
res.send('\nAn unkown error has occured');
console.error(err);
} else if (n) {
const section = n.sections.find((s) => { return s.title === sectionTitle; });
const note = section.notes.find((n) => { return n.title === noteTitle; });
if (note.content) {
res.send('\n' + note.title + '\n\n' + note.content);
} else {
res.send('\n' + note.title + '\n\n[ No content to show ]');
}
} else {
res.send('\nNo such note exists');
}
});
} else {
res.send('\nNo such section exists');
}
});
} else {
res.send('\nNo such wiki exists');
}
});
});
这是一个非常冗长的方法,前两个查询实际上在整个应用程序中经常出现。我也明白 MongoDB 查询是一个异步操作,因此,为什么我将每个后续的 MongoDB 查询放在它的父查询中(我希望在那个查询开始之前完成的查询)。
问题
有没有办法将每个 MongoDB 查询拆分成自己的方法或以缩短代码的方式引入承诺?我更喜欢最终导致将我的代码拆分为单独方法的建议,因为您在上面看到的是所有使用相同查询的众多端点之一。
所以最终结果我想要一些类似的东西:
app.get('/:wikiTitle/:sectionTitle/:noteTitle', function(req, res) {
if (getWiki(req.params.wikiTitle)) {
// Continue with second query
if (getSection(req.params.sectionTitle)) {
// Continue with third query...
}
}
});
function getWiki(wikiTitle) {
wikiModel.findOne({ title: wikiTitle }, function(err, wiki) {
if (err) {
console.error(err);
res.send('An unknown error occured.');
} else if (wiki) {
// Send OK result to continue to next query
return wiki
} else {
res.send('No wiki found');
return null;
}
});
}
function getSection(sectionTitle) {
wikiModel.findOne({ 'sections.title': sectionTitle }, function(err, section) {
if (err) {
console.error(err);
res.send('An unknown error occured.');
} else if (section) {
// Send OK result to continue to next query
return section
} else {
res.send('No section found');
return null;
}
});
}
我希望这将显着缩短代码长度并利用代码的可重用性。欢迎任何关于我如何接近实现这样的目标的建议。
您绝对可以像调用模型一样使用回调。例如:
app.get('/:wikiTitle/:sectionTitle/:noteTitle', function(req, res) {
getWiki(req.params.wikiTitle, function (err, title) {
if (err) {
return res.send(err);
}
getSection(req.params.sectionTitle, function (err, section) {
if (err) {
return res.send(err);
}
// Todo: use title and section, etc...
});
});
});
function getWiki(wikiTitle, cb) {
wikiModel.findOne({ title: wikiTitle }, function(err, wiki) {
if (err) {
console.error(err);
return cb('An unknown error occured.');
} else if (wiki) {
// Send OK result to continue to next query
return cb(null, wiki);
} else {
return cb('No wiki found');
}
});
}
function getSection(sectionTitle, cb) {
wikiModel.findOne({ 'sections.title': sectionTitle }, function(err, section) {
if (err) {
console.error(err);
return cb('An unknown error occured.');
} else if (section) {
// Send OK result to continue to next query
return cb(null, section);
} else {
return cb('No section found');
}
});
}
这是在节点中使用异步函数的标准方式。按照惯例,第一个参数总是错误参数。
如果你想让你的代码更干净,你可以尝试使用guard clauses / early outs来提前退出错误情况。这将减少您对 if / else 条件语句的需求。
您还可以查看 async 等库,以更清晰地链接异步调用。
当你觉得舒服的时候,你也可以考虑使用 promises 和 'async' javascript 关键字(不同于上面的异步库,我知道很混乱)这也可以让你削减了解您必须编写的代码行以获得漂亮的异步代码。
你应该使用像
这样的异步函数(Promises)
app.get('somePath', async (req, res, next) => {
try {
const doc = await model.find({ someField: 'some value' }).exec(); // exec returns promise
res.send({ document: doc });
} catch (error) {
// here you can handle all errors or/and call next for the error middleware
next(error);
}
});
背景
我有一个 NodeJS 应用程序,打算用作 RESTful API。它使用 Mongoose 与后端的 MongoDB 数据库连接。该应用程序基于嵌套文档的理念构建。它使用以下架构存储 wikis
、sections
和 notes
:
const noteSchema = new mongoose.Schema({ title: String, content: String });
const sectionSchema = new mongoose.Schema({ title: String, notes: [noteSchema] });
const wikiSchema = new mongoose.Schema({ title: String, sections: [sectionSchema] });
所有这些都可以通过 wiki 的单一模型访问:
const wikiModel = mongoose.model("Wiki", wikiSchema);
用户可以在每个端点上执行 GET、POST、PUT、DELETE 请求来操作其中的数据。如果有人想要 ping Notes 端点(层次结构中最下方),它必须首先检查 wiki,然后检查部分端点,以确保它们都存在。
这是一个例子:
app.get('/:wikiTitle/:sectionTitle/:noteTitle', function(req, res) {
wikiModel.findOne({ title: req.params.wikiTitle }, function(err, wiki) {
if (err) {
res.send('\nAn unkown error has occured');
console.error(err);
} else if (wiki) {
const sectionTitle = req.params.sectionTitle;
wikiModel.findOne({ 'sections.title': sectionTitle }, function(err, section) {
if (err) {
res.send('\nAn unkown error has occured');
console.error(err);
} else if (section) {
const noteTitle = req.params.noteTitle;
wikiModel.findOne({ 'sections.notes.title': noteTitle }, function(err, n) {
if (err) {
res.send('\nAn unkown error has occured');
console.error(err);
} else if (n) {
const section = n.sections.find((s) => { return s.title === sectionTitle; });
const note = section.notes.find((n) => { return n.title === noteTitle; });
if (note.content) {
res.send('\n' + note.title + '\n\n' + note.content);
} else {
res.send('\n' + note.title + '\n\n[ No content to show ]');
}
} else {
res.send('\nNo such note exists');
}
});
} else {
res.send('\nNo such section exists');
}
});
} else {
res.send('\nNo such wiki exists');
}
});
});
这是一个非常冗长的方法,前两个查询实际上在整个应用程序中经常出现。我也明白 MongoDB 查询是一个异步操作,因此,为什么我将每个后续的 MongoDB 查询放在它的父查询中(我希望在那个查询开始之前完成的查询)。
问题
有没有办法将每个 MongoDB 查询拆分成自己的方法或以缩短代码的方式引入承诺?我更喜欢最终导致将我的代码拆分为单独方法的建议,因为您在上面看到的是所有使用相同查询的众多端点之一。
所以最终结果我想要一些类似的东西:
app.get('/:wikiTitle/:sectionTitle/:noteTitle', function(req, res) {
if (getWiki(req.params.wikiTitle)) {
// Continue with second query
if (getSection(req.params.sectionTitle)) {
// Continue with third query...
}
}
});
function getWiki(wikiTitle) {
wikiModel.findOne({ title: wikiTitle }, function(err, wiki) {
if (err) {
console.error(err);
res.send('An unknown error occured.');
} else if (wiki) {
// Send OK result to continue to next query
return wiki
} else {
res.send('No wiki found');
return null;
}
});
}
function getSection(sectionTitle) {
wikiModel.findOne({ 'sections.title': sectionTitle }, function(err, section) {
if (err) {
console.error(err);
res.send('An unknown error occured.');
} else if (section) {
// Send OK result to continue to next query
return section
} else {
res.send('No section found');
return null;
}
});
}
我希望这将显着缩短代码长度并利用代码的可重用性。欢迎任何关于我如何接近实现这样的目标的建议。
您绝对可以像调用模型一样使用回调。例如:
app.get('/:wikiTitle/:sectionTitle/:noteTitle', function(req, res) {
getWiki(req.params.wikiTitle, function (err, title) {
if (err) {
return res.send(err);
}
getSection(req.params.sectionTitle, function (err, section) {
if (err) {
return res.send(err);
}
// Todo: use title and section, etc...
});
});
});
function getWiki(wikiTitle, cb) {
wikiModel.findOne({ title: wikiTitle }, function(err, wiki) {
if (err) {
console.error(err);
return cb('An unknown error occured.');
} else if (wiki) {
// Send OK result to continue to next query
return cb(null, wiki);
} else {
return cb('No wiki found');
}
});
}
function getSection(sectionTitle, cb) {
wikiModel.findOne({ 'sections.title': sectionTitle }, function(err, section) {
if (err) {
console.error(err);
return cb('An unknown error occured.');
} else if (section) {
// Send OK result to continue to next query
return cb(null, section);
} else {
return cb('No section found');
}
});
}
这是在节点中使用异步函数的标准方式。按照惯例,第一个参数总是错误参数。
如果你想让你的代码更干净,你可以尝试使用guard clauses / early outs来提前退出错误情况。这将减少您对 if / else 条件语句的需求。
您还可以查看 async 等库,以更清晰地链接异步调用。
当你觉得舒服的时候,你也可以考虑使用 promises 和 'async' javascript 关键字(不同于上面的异步库,我知道很混乱)这也可以让你削减了解您必须编写的代码行以获得漂亮的异步代码。
你应该使用像
这样的异步函数(Promises)app.get('somePath', async (req, res, next) => {
try {
const doc = await model.find({ someField: 'some value' }).exec(); // exec returns promise
res.send({ document: doc });
} catch (error) {
// here you can handle all errors or/and call next for the error middleware
next(error);
}
});