Node REST API 中的控制器和服务有什么区别?
最初发表于coreycleary.me。这篇文章来自我的内容博客。我每周或每两周发布一次新内容,如果您想直接在邮箱中收到我的文章,可以订阅我的新闻通讯!我还会定期发送一些小抄和其他免费赠品。
如果您使用 Node(或其他语言)构建过 REST API,那么您可能已经使用过“控制器”的概念来帮助组织应用程序。您可能将对数据库或模型的调用放在那里,调用了一些其他端点,并添加了一些业务逻辑来处理返回结果。
该控制器可能看起来像这样:
const registerUser = async (req, res, next) => {
const {userName, userEmail} = req.body
try {
// add user to database
const client = new Client(getConnection())
await client.connect()
await client.query(`INSERT INTO users (userName) VALUES ('${userName}');`)
await client.end()
// send registration confirmation email to user
const ses = new aws.SES()
const params = {
Source: sender,
Destination: {
ToAddresses: [
`${userEmail}`
],
},
Message: {
Subject: {
Data: subject,
Charset: charset
},
Body: {
Text: {
Data: body_text,
Charset: charset
},
Html: {
Data: body_html,
Charset: charset
}
}
}
await ses.sendEmail(params)
res.sendStatus(201)
next()
} catch(e) {
console.log(e.message)
res.sendStatus(500) && next(error)
}
}
但你可能很少用到,甚至没听说过“服务”这个概念。又或许你听说过这个概念,也知道应该使用它们,但却对“服务”的逻辑和控制器的逻辑有什么区别感到疑惑。
在 API 中使用服务是我在 Node-land 中不常见到的,但它对 API 结构来说是一个非常强大的补充,可以让测试、代码组织和代码重用变得更加容易。
那么,如果它们是构建 API 的一种有用方式,那么服务到底是 什么呢?
为了回答这个问题,我们将深入研究控制器和服务之间的区别以及它们的作用,以便您可以更正确地构建 Node API。
经理/工人类比
我能想到的最有效的解释两者区别的方法之一,就是用商业世界中的类比——“经理”/“员工”二分法。我们将使用简化的刻板印象来描述经理和员工的职责——我绝不是说所有经理都扮演一种角色,而所有员工都扮演另一种角色!
在我们的类比中,控制器是管理者,而服务是工作者。
如果你思考一下经理的角色是什么,他/她通常:
- 管理传入的工作请求
- 决定哪个工人应该做这项工作
- 将工作分成相当大的单元
- 通行证
- 如果工作需要多人同时处理多项事务,则需要协调工作
- 但不亲自做这项工作(再次,这里使用了一个基本的刻板印象!)
并且,工人通常:
- 收到经理的请求
- 弄清楚完成请求所涉及的具体细节
- 通常只关心他/她必须完成的任务
- 不负责做出有关“更大”前景的决策
- 完成任务/请求所需的实际工作
- 将完成的工作返回给经理
这里的核心思想是,管理器/控制器接收工作,决定由谁来执行,然后传递待完成的请求。而工作器/服务则是接收请求并实际完成它的那个。你可能有多个工作器在处理不同的请求/任务,这些请求/任务构成了整体,而管理器会将它们连接在一起,这样就变得合理了。
什么逻辑适用?
使用这个类比,让我们从技术角度来看看控制器与服务:
控制器:
- 管理传入
工作HTTP 请求 - 决定
哪个工人什么服务应该做这项工作 - 将工作分成相当大的单元
- 通行证
这项工作将 HTTP 请求中的必要数据发送给服务 - 如果工作需要多个
人们负责处理多项事务的服务,作品那些服务电话 - 但不亲自做工作
(再次强调,这里使用基本的刻板印象!)(这里不是刻板印象,控制器不应该做这项工作)
综上所述,控制器从 Express(或您正在使用的任何框架)获取所需的信息,进行一些检查/验证以确定应将请求中的数据发送到哪个服务,然后协调这些服务调用。
因此,控制器中确实存在一些逻辑,但服务负责的业务逻辑/算法/数据库调用等并非由控制器负责。再说一次,控制器是管理者/主管。
以及一项服务:
- 收到
要求为了执行任务,需要从管理器获取数据 - 算出
个人详细信息完成请求所涉及的算法/业务逻辑/数据库调用/等 - 通常只关心他/她必须完成的任务
- 不负责
做出关于“更大”前景的决策协调不同的服务调用 - 完成任务/请求所需的实际工作
- 返回
完成的作品回复经理
现在总结一下服务,它负责完成工作并将其返回给控制器。它包含实际满足需求并返回 API 使用者请求内容所需的业务逻辑。
关于“业务逻辑”含义的说明
我喜欢将业务逻辑视为更“纯粹”的逻辑形式。这种逻辑通常不关心验证请求或处理任何特定于框架的内容。它只处理用于处理数据、存储数据、获取数据、格式化数据等的算法/规则。这些规则通常由业务需求决定。
例如,如果您有一个 API,可以返回过去 X 天内在您的平台上注册的用户数量,那么这里的业务逻辑将是查询数据库并在将数据返回给控制器之前对其进行任何格式化。
控制器和服务分离的示例
让我们重构原始的仅限控制器的代码,以查看控制器和服务之间的关注点分离的示例:
首先,我们将提取将用户添加到服务的逻辑。
注册服务:
const addUser = async (userName) => {
const client = new Client(getConnection())
await client.connect()
await client.query(`INSERT INTO users (userName) VALUES ('${userName}');`)
await client.end()
}
module.exports = {
addUser
}
接下来我们将提取向用户发送注册电子邮件的逻辑。
电子邮件服务:
const ses = new aws.SES()
const sendEmail = async (userEmail) => {
const params = {
Source: sender,
Destination: {
ToAddresses: [
`${userEmail}`
],
},
Message: {
Subject: {
Data: subject,
Charset: charset
},
Body: {
Text: {
Data: body_text,
Charset: charset
},
Html: {
Data: body_html,
Charset: charset
}
}
}
}
await ses.sendEmail(params)
}
module.exports = {
sendEmail
}
最后,我们将大大简化控制器,只需进行以下两个服务调用:
const {addUser} = require('./registration-service')
const {sendEmail} = require('./email-service')
const registerUser = async (req, res, next) => {
const {userName, userEmail} = req.body
try {
// add user to database
await addUser(userName)
// send registration confirmation email to user
await sendEmail(userEmail)
res.sendStatus(201)
next()
} catch(e) {
console.log(e.message)
res.sendStatus(500) && next(error)
}
}
module.exports = {
registerUser
}
总之
差不多就到这里了。希望现在你对控制器和服务中的逻辑有了更好的理解。简单的记忆方法是:
- 控制者:管理/协调工作
- 服务:执行工作
像这样的分离会成为代码复用和代码组织的有力工具。在你构建的下一个 REST API 中尝试一下,我相信你会发现它很有帮助。
我正在写很多新内容,帮助大家更容易理解 Node 和 JavaScript。之所以说更简单,是因为我认为它没必要像有时那样复杂。如果你喜欢这篇文章并觉得它对你有帮助,可以再次点击链接订阅我的新闻通讯!
链接链接:https://dev.to/ccleary00/what-is-the-difference-between-controllers-and-services-in-node-rest-apis-2c9