共计 4579 个字符,预计需要花费 12 分钟才能阅读完成。
文章首发于我的博客 https://github.com/mcuking/bl…
译者:最近在钻研前端架构分层,在 medium 看到了这篇对于 node.js 架构分层的文章,感觉很不错,顺便翻译过去分享给大家,其中很多思维也能够利用到前端我的项目中。
原文链接 https://blog.codeminer42.com/…
软件随时可能更改,而定义代码品质的一个方面就是更改代码的难易水平。然而是什么使它是这样的?
… 如果您胆怯扭转某些货色,显然是设计得不好。—马丁·福勒
关注点和职责拆散
“将因为雷同起因而发生变化的事物汇集在一起。离开那些因不同起因而扭转的事物。” |
无论是性能,类还是模块,它们都能够利用于繁多职责准则和关注点拆散 the single responsibility principle and the separation of concerns。基于这些原理进行设计软件架构。
## 架构
在软件开发中,职责是团结一致要实现的工作,例如:在应用程序中示意产品的概念,解决网络申请,将用户保留在数据库中等等。
您是否留神到这三个职责不在同一类别中?这是因为它们属于不同的层,因而又能够分为概念。依据下面的示例,“在数据库中保留用户”与“用户”概念无关,也与数据库进行通信的层无关。
通常,与上述概念相干的体系结构偏向于分为四层:domain
, application
, infrastructure
, input interfaces
。
## Domain 层
在这一层中,咱们能够定义充当实体和业务规定的角色并与咱们的 domain 有间接关系的单元。例如,在用户和团队的应用程序中,咱们可能会有一个 User 实体,一个 Team 实体和一个 JoinTeamPolicy 来答复用户是否可能退出给定的团队。
这是咱们软件中最孤立最重要的层,Application 层能够应用它来定义用例。
## Application 层
Application 层定义了咱们应用程序的理论行为,因而负责执行 domain 层各单元之间的交互。例如,咱们能够有一个 JoinTeam 用例,该用例接管 User 和 Team 的实例,并将它们传递给 JoinTeamPolicy。如果用户能够退出,它将长久化职责委托给 infrastructure 层。
Application 层也能够用作 infrastructure 层的适配器。假如咱们的应用程序能够发送电子邮件;间接负责与电子邮件服务器通信的类(称为 MailChimpService)属于 infrastructure 层,然而理论发送电子邮件的电子邮件(EmailService)属于 application 层,并在外部应用 MailChimpService。因而,咱们的应用程序的其余部分不晓得无关特定实现的详细信息 - 它仅晓得 EmailService 可能发送电子邮件。
## Infrastructure 层
这是所有层中的最低层,它是应用程序内部的边界:数据库,电子邮件服务,队列引擎等。
多层应用程序的一个独特特色是应用 repository pattern 与数据库或其余一些内部长久化服务(例如 API)进行通信。Repository 对象实质上被视为汇合,应用它们的层(domain 和 application)不须要晓得底层的长久化技术(相似于咱们的电子邮件服务示例)。
这里的想法是,repository 接口属于 domain 层,而实现又属于 infrastructure 层,即 domain 层仅晓得 repository 承受的办法和参数。即便在测试方面,这也使两层都更加灵便!因为 JavaScript 并未实现接口的概念,因而咱们能够设想本人的接口,并以此为根底在 infrastructure 层上创立具体的实现。
## Input interfaces 层
该层蕴含应用程序的所有入口点,例如控制器,CLI,websocket,图形用户界面(如果是桌面应用程序)等等。
它应该不具备无关业务规定、用例、长久化技术的常识,甚至不具备其余逻辑的常识!它应该只接管用户输出(如 URL 参数),将其传递给用例,最初将响应返回给用户。
## NodeJS 与关注点拆散
好了,通过所有这些实践之后,它如何在 Node 应用程序上工作?说实话,多层体系结构中应用的某些模式非常适合 JavaScript 世界!
## NodeJS 和 domain 层
Node 上的 domain 层能够由简略的 ES6 classes 组成。有许多 ES5 和 ES6 + 模块可帮忙创立实体,例如:Structure, Ampersand State, tcomb 和 ObjectModel。
让咱们看一个应用 Structure 的简略示例:
`
js
const {attributes} = require(‘structure’);
const User = attributes({
id: Number,
name: {
type: String,
required: true
},
age: Number
})(
class User {
isLegal() {
return this.age >= User.MIN_LEGAL_AGE;
}
}
);
User.MIN_LEGAL_AGE = 21;`
请留神,咱们的列表中不蕴含 Backbone.Model
或 Sequelize
和 Mongoose
之类的模块,因为它们打算在 infrastructure 层中用于与内部世界进行通信。因而,咱们代码库的其余部分甚至不须要理解它们的存在。
## NodeJS 与 application 层
用例属于 application 层,与 promises 不同,用例可能会带来胜利与失败之外的后果。对于这种状况,比拟好的 Node 模式是 event emitter。要应用它,咱们必须扩大 EventEmitter 类并为每个可能的后果收回一个事件,从而暗藏了咱们的 repository 在外部应用了 promise 的事实:
`
js
const EventEmitter = require(‘events’);
class CreateUser extends EventEmitter {
constructor({usersRepository}) {
super();
this.usersRepository = usersRepository;
}
execute(userData) {
const user = new User(userData);
this.usersRepository
.add(user)
.then(newUser => {
this.emit(‘SUCCESS’, newUser);
})
.catch(error => {
if (error.message === ‘ValidationError’) {
return this.emit(‘VALIDATION_ERROR’, error);
}
this.emit(‘ERROR’, error);
});
}
}`
这样,咱们的入口点就能够执行用例并为每个后果增加一个监听器,如下所示:
`
js
const UsersController = {
create(req, res) {
const createUser = new CreateUser({usersRepository});
createUser
.on(‘SUCCESS’, user => {
res.status(201).json(user);
})
.on(‘VALIDATION_ERROR’, error => {
res.status(400).json({
type: ‘ValidationError’,
details: error.details
});
})
.on(‘ERROR’, error => {
res.sendStatus(500);
});
createUser.execute(req.body.user);
}
};`
## NodeJS 与 infrastructure 层
infrastructure 层的实现不应很艰难,但要留神其逻辑不要透露到以上各层!例如咱们能够应用 Sequelize 模型来实现与 SQL 数据库进行通信的存储库,并为其提供办法名称,而这些名称并不暗示其下存在 SQL 层 - 例如咱们上一个示例的通用 add 办法。
咱们能够实例化一个 SequelizeUsersRepository 并将其作为 usersRepository 变量传递给它的依赖项,这些依赖项可能只是与其接口交互。
`
js
class SequelizeUsersRepository {
add(user) {
const {valid, errors} = user.validate();
if (!valid) {
const error = new Error(‘ValidationError’);
error.details = errors;
return Promise.reject(error);
}
return UserModel.create(user.attributes).then(dbUser => dbUser.dataValues);
}
}`
对于 NoSQL 数据库,电子邮件服务,队列引擎,内部 API 等,也是如此。
## NodeJS 和 input interfaces 层
在 Node 应用程序上实现此层有很多种形式。对于 HTTP 申请,Express 模块是应用最多的模块,但您也能够应用 Hapi 或 Restify。最终抉择取决于实现细节,只管对此层所做的更改不应影响其余细节。如果从 Express 迁徙到 Hapi 某种程度上意味着在要更改某些代码时,则示意已耦合,并且您应密切注意对其进行修复。
## 连贯这些层
间接与另一层进行通信可能是一个谬误的决定,并导致它们之间的耦合。在面向对象的编程中,解决此问题的常见办法是依赖注入 dependency injection(DI)。这种技术包含使类的依赖项在其构造函数中作为参数接管,而不是引入依赖项并将其实例化到类自身外部,从而创立了所谓的管制反转。
应用这种技术使咱们可能以一种十分简洁的形式隔离一个类的依赖关系,使其更加灵便且易于测试,因为解决依赖关系成为一项琐碎的工作
对于 Node 应用程序,有一个很好的 DI 模块,称为 Awilix,它使咱们可能在不将代码耦合到 DI 模块自身的状况下利用 DI,因而咱们不心愿应用 Angular 1 那种奇怪的依赖注入机制。Awilix 的作者有一系列的文章,它们解释了 Node 的依赖注入,值得一读,并且还介绍了如何应用 Awilix。顺便说一句,如果您打算应用 Express 或 Koa,还应该看看 Awilix-Express 或 Awilix-Koa。
## 一个实际的例子
即便有了所有这些无关层和概念的示例和阐明,我置信没有什么比遵循多层架构的应用程序的理论示例更好的了,这足以使您确信应用起来很简略!
你能够查看可用在生产环境的 boilerplate for web APIs with Node。它采纳了多层架构,并曾经为您设置了根底配置(包含文档),因而您能够练习甚至将其用作 Node 应用程序的开始模板。
## 额定信息
如果您想理解无关多层架构以及如何拆散关注点的更多信息,请查看以下链接:
– FourLayerArchitecture
– Architecture — The Lost Years
– The Clean Architecture
– Hexagonal Architecture
– Domain-driven design