当前位置: 首页 > 后端技术 > Node.js

NodeJsandbestpractices(翻译)

时间:2023-04-03 14:56:13 Node.js

文章首发于我的博客https://github.com/mcuking/bl...译者:最近在研究前端架构分层,在medium看到这篇关于node的文章觉得layered.jsarchitecture这篇文章很好,特地翻译出来分享给大家。许多想法也可以应用到前端项目中。原文链接https://blog.codeminer42.com/...软件可以随时更改,定义代码质量的一个方面是更改它的难易程度。但是是什么让它这样呢?...如果你害怕改变某些东西,那显然是设计不当。—MartinFowler关注点和责任分离“将因相同原因发生变化的事物集中在一起。将因不同原因发生变化的事物分开。”无论是函数、类还是模块,都可以适用单一职责单一职责原则和关注点分离。基于这些原则设计软件架构。##架构在软件开发中,职责是联合起来实现的任务,例如:在应用程序中表示产品的概念、处理网络请求、在数据库中保存用户等。你是否注意到这三个职责并不是在同一类别?这是因为它们属于不同的层,因此可以再次划分为概念。根据上面的例子,“将用户存储在数据库中”与“用户”的概念有关,也与数据库通信的层有关。通常,与上述概念相关的架构往往分为四层:领域、应用、基础设施、输入接口。##域层在这一层中,我们可以定义充当实体和业务规则并与我们的域有直接关系的单元。例如,在用户和团队的应用程序中,我们可能有一个User实体、一个Team实体和一个JoinTeamPolicy来回答用户是否能够加入给定的团队。这是我们软件中最孤立也是最重要的一层,应用层可以用它来定义用例。##应用层应用层定义了我们应用程序的实际行为,因此负责执行领域层单元之间的交互。例如,我们可以有一个JoinTeam用例,它接受User和Team的实例并将它们传递给JoinTeamPolicy。如果用户可以加入,它将持久性责任委托给基础设施层。应用层也可以用作基础设施层的适配器。假设我们的应用程序可以发送电子邮件;直接负责与邮件服务器通信的类(称为MailChimpService)属于基础设施层,而真正发送邮件的邮件(EmailService)则属于应用层,内部使用了MailChimpService。所以我们的应用程序的其余部分不知道有关该特定实现的详细信息——它只知道EmailService能够发送电子邮件。##基础设施层这是所有层中的最低层,它是应用程序之外的边界:数据库、电子邮件服务、队列引擎等。多层应用程序的一个共同特征是使用存储库模式与一个数据库或其他一些外部持久性服务,例如API。存储库对象本质上被视为集合,使用它们的层(域和应用程序)不需要了解底层持久性技术(类似于我们的电子邮件服务示例)。这里的想法是存储库接口属于领域层,实现属于基础设施层,即领域层只知道存储库接受的方法和参数。这使得两层都更加灵活,即使在测试方面也是如此!由于JavaScript没有实现接口的概念,我们可以想象自己的接口,并以此为基础在基础设施层创建具体的实现。##输入接口层这一层包含应用程序的所有入口点,例如控制器、CLI、websockets、GUI(如果是桌面应用程序)等。它应该不了解业务规则、用例、持久化技术,甚至其他逻辑!它应该只接受用户输入(如URL参数),将其传递给用例,最后将响应返回给用户。##NodeJS关注点分离好吧,在所有这些理论之后,它如何在Node应用程序上工作?老实说,多层架构中使用的一些模式非常适合JavaScript世界!##NodeJS和领域层Node上的领域层可以由简单的ES6类组成。有许多ES5和ES6+模块可帮助创建实体,例如:Structure、AmpersandState、tcomb和ObjectModel。让我们看一个使用结构的简单示例:`jsconst{attributes}=require('structure');constUser=attributes({id:Number,name:{type:String,required:true},age:Number})(classUser{isLegal(){returnthis.age>=User.MIN_LEGAL_AGE;}});用户.MIN_LEGAL_AGE=21;`请注意,我们的列表中不包括Backbone.Model或Sequelize和Mongoose等模块,因为它们旨在用于基础设施层以与外界通信。所以我们代码库的其余部分甚至不需要知道它们的存在。##NodeJS与应用层用例属于应用层。与承诺不同,用例可能带来成功和失败以外的结果。对于这种情况,更好的节点模式是事件发射器。要使用它,我们必须扩展EventEmitter类并为每个可能的结果发出一个事件,从而隐藏我们的存储库在内部使用承诺的事实:`jsconstEventEmitter=require('events');类CreateUser扩展EventEmitter{constructor({usersRepository}){super();this.usersRepository=usersRepository;}execute(userData){constuser=newUser(userData);this.usersRepository.add(user).then(newUser=>{this.emit('SUCCESS',newUser);}).catch(error=>{if(error.message==='ValidationError'){返回这个.emit('VALIDATION_ERROR',错误);}this.emit('ERROR',错误);});}}`这样,我们的入口点可以执行用例并为每个结果添加一个侦听器,如下所示:`jsconstUsersController={create(req,res){constcreateUser=newCreateUser({usersRepository});createUser.on('SUCCESS',user=>{res.status(201).json(user);}).on('VALIDATION_ERROR',error=>{res.status(400).json({type:'验证错误',详细信息:错误。详细信息});}).on('错误',错误=>{res.sendStatus(500);});createUser.execute(req.body.user);}};`##NodeJS和基础设施层基础设施层的实现应该不难,但要注意不要将其逻辑泄漏到上面的层中!例如,我们可以使用Sequelize模型来实现一个与SQL数据库通信的存储库,并为其提供不暗示下面有SQL层的方法名称——例如我们上一个示例的通用添加方法,我们可以实例化一个SequelizeUsersRepository和将它作为usersRepository变量传递给它的依赖项,这些依赖项可能只与其接口交互。`jsclassSequelizeUsersRepository{add(user){const{有效,错误}=user.validate();if(!valid){consterror=newError('ValidationError');error.details=错误;返回Promise.reject(错误);}returnUserModel.create(user.attributes).then(dbUser=>dbUser.dataValues);}}`NoSQL数据库、电子邮件服务、队列引擎、外部API等也是如此。##NodeJS和输入接口层有很多方法可以在Node应用程序之上实现这一层。对于HTTP请求,Express模块是使用最多的模块,但您也可以使用Hapi或Restify。最后的选择是实现细节,尽管对该层所做的更改不应影响其他细节。如果从Express迁移到Hapi意味着某些代码需要更改,那么它就是耦合的,您应该密切注意修复它。##连接这些层以直接与另一层通信可能是一个错误的决定,并导致它们之间的耦合。在面向对象的编程中,这个问题的一个常见解决方案是依赖注入(DI)。这种技术涉及使类的依赖项作为其构造函数中的参数接收,而不是引入依赖项并在类本身内部实例化它们,从而创建所谓的控制反转。使用这种技术允许我们以非常干净的方式隔离类的依赖关系,使其更加灵活和易于测试,因为解决依赖关系成为一项微不足道的任务对于Node应用程序,有一个很好的DI模块,称为Awilix,允许我们在不将代码耦合到DI模块本身的情况下利用DI,因此我们不想使用Angular1奇怪的依赖注入机制。Awilix的作者有一系列解释Node的依赖注入的文章值得阅读并解释如何使用Awilix。顺便说一句,如果你打算使用Express或Koa,你还应该看看Awilix-Express或Awilix-Koa。##一个实际的例子即使有这么多关于层次和概念的例子和解释,我相信没有什么比一个遵循多层架构的应用程序的实际例子更好的了,这足以让你相信它简单易用!您可以使用Node查看WebAPI的样板以供生产使用。它具有多层架构,并且已经为您设置了基本配置(包括文档),因此您可以练习甚至将其用作Node应用程序的起始模板。##附加信息如果您想了解有关多层架构以及如何分离关注点的更多信息,请查看以下链接:-FourLayerArchitecture-Architecture-TheLostYears-TheCleanArchitecture-HexagonalArchitecture-Domain-drivendesign