学习Nestjs 有一周的时间了,前几天基本是对Typescipt 基本语法的一些学习,再这里就不在讲述了。当然也对IOC这么伟大的编程思想有了初步的了解。今天在这里主要是想对Nestjs 的一周学习做一个总结和概括。
老外起名字 一般都有单词本身的一些含义的。那我们就先来 讲讲什么是Nest。Nest 就是一组的完整的巢穴。这里基本两层意思1. 单个nest 功能的完整性 2. nest 和 nest 直接的功能的复制和嵌套性。我们要在阅读全文的时候 要多从这两个维度来思考整个架构。有的时候我们就会豁然明朗。 那我们就切入正题吧。
Nest 是一个强悍的基于Node.js 的网站框架。它可以帮助我们不费吹灰之力的去构建高效和可控的应用。在流行的 JavaScript基础,它是被构建在Typescript语法 以及OOP 和 FP这些伟大的编程思想之上。由于它的底层用的是 Express 和 Socket.io ,有着强大的第三方插件。所以我们不需要担心它的稳健性和库的不方便性。
核心思想:
Nest的核心思想就是提供了一个层与层直接的耦合度极小,抽象化极高的一个架构体系。
配置程序:
Nest的功能使用了 ES6和ES7的语法(decorators, async / await)。我们可以方便的使用Babel 或 TypeScript。在这篇文章里面我讲用 TypeScript。
首先我们来配置一下 tsconfig.json file:
让我们从将最基本的开始。首先我们去创建一个入口模块 (app.module.ts):
在这个时候,模块的元数据是空 ({}), 因为我们只是为了先把程序跑起来 。除了一个空模块 我们一无所有 (例如 controlles or components 这些基本的模块). 第二部 创建一个index.ts (当然 你可以随意起名) ,然后使用 NestFactory 去创建一个 Nest 实例程序 基于我们的类
添加一个 Express 实例,如果我们想对express 实例有一个全周期的掌控的话,我们就必须要在创建对象的时候把 express 实例传入到 NestFactory.create() 中,如下图:
这就意味着,我们可以直接添加一些我在特有的配置 (比如添加一些像morgan 或 body-parser的插件).
首先我们要讲的是 Controller
作为网站框架那一定要有收发包分发的功能模块,在这个框架里Controller就是干这个的。
(官网定义:Controllers are responsible for handling incoming requests and returning responses to the client.)
所以说 Controllers 就是负责处理来的请求和回送的响应到客户端的一个功能模块。
在Nest中,Controller 就是一个简单的类,通常我们用 @Controller() 这个修饰符来操作
好了,上面我们已经设置好了一个程序的入口(3000端口)。那现在我们要继续做进一步的解析,为了分析出报文之间的不同 我们来搭建第一个服务入口 ( /users):
估计你已经猜到了,我们的这个入口点油三种不同的形式
上面的写法是不是有点太冗余不太友好。我们需要在不同的形式里面 重复这相同的故事。那有没有一个简洁的写法呢? 哈哈 这种不费吹灰之力的问题 对我们码农来说 答案肯定是 有的 Nest 允许我们直接传入Metadata 到 @Controller() 通过 修饰符 {path } 来作为相同路由的前缀如下:
在这里 我们要注意一下 Nest Controllers 里面的函数方法 他们是不是参数,行为都一样一样滴
(req,res,next)当然 这些都是 Express 的一些知识。 我仔细看过 KOA2。 对 Express 没有仔细了解过 不过 看名字应该不当误我们去对它有个简单的认知。如果你想对他们有更多的学习请自行了解 或者等我研究完了 写一篇 。 在这里 我们可以简单的把 Next Controller 的方法看做是一种 Express 路由的封装Users Controller 已经可是使用了,但是我们的模块还不知道它的存在。让我们创建一个 ApplicationModule,并且添加一些 Metadata(我就不再这里翻译一些专业单词 感觉真的不是一两个单词可以说明白的 不过我想我会单独的写一篇文章对专业词汇的意思进行解释和说明一下。这也是我以前感觉要改进的地方)
从上图看来,我们只是把我们的Controller 插入到了一个 Controllers的数组里面。 就这么简单。从某种角度而言 这很像Serverless 的编程,我们不需要去配置什么服务,只需要关注我们对事件消息的具体内容的对码,解析,和响应。
Providers
在这里我们是基于最新的5 进行解释的。在4中 我们还称之为component. 为什么从component改为Providers 呢。 我觉得 component 的相对关系比较强烈一点 组件是针对整体而言 是一层关系。 而Providers 就是角色阐明。只要我提供了某种功能,方法的给另外一个方的 都可以称之为提供者。我觉得这个单词能更好的表达出 IOC或者DI思想。(定义搬砖: almost everything may be considered as a provider – service, repository, factory, helper, and so on. All of them can inject dependencies through a constructor, meaning, they can create various relationships with each other. But in fact, a provider is nothing else than just a simple class annotated with an @Injectable() decorator.)这到底是个啥子意思啊。三句话简明的告诉我们1. 几乎所有的东西都可以被视为 provider (还真是他妈的 世上万物只要有价值就是被用的 哈哈 )
2. 在我们的Nes里它是怎么提供的 是通过constructor 这个被服务的创造函数注入到被服务对象中的
3. 怎么成为Provider呢? 来个制服吧,工牌 @Injectable()
在上一章中 我们创造了一个简单的 Controller (Userscontroller)。这个Controller可以访问我们的数据(当然这里我们还尚未接入任何数据库 )。当然这不是什么正途。在Nest 中要牢记一点 分层,尽量把功能单一的放在一层中,这也是AOP编程的一个重要思想。当然在这里我们的Controllers 应该只是处理消息的请求,然后下放更多复杂的任务到服务组件。那也是为什么我们要创建UsersService 这个组件。在实际中,UsersService 应该合理的调用底层的方法(例如从UsersRepository 组件中调用 )。在这里我们没有任何的数据库。我们将使用fake data。
Nest Provider 是一个很简单的类。我们如果想让一个类变成Provider 只需要 给一个工号标签 @Injectable() 。写到这里有没有发现什么问题吗? 上图的工号标签 我是不是写错了? 那是4中的写法。在5中 换成 @Injectable() 就好了。还有上图当中 我们是不是在 getUser() 这个方法当中 用到了 HttpException 这个异常类。它是 Nest 内置的异常,传入两个参数就好了 error message and status code
好了,我们的service 已经准备就绪。让我们在UsersController中调用它
Notice: 我删除了next 参数在我们的方法中。因为在这里我们不会用到它
如上图所示, UsersService 被注入到了 constructor. 当然在最新的版本中 我们还可以 用TS的语法 给一个 readonly 的读写权限的检验
用声明的TS语法就是好 简单一行 就实现了 DI 的注入方式
简单的兴许之余,千万别忘了在 tsconfig.json中把 emitDecoratorMetadata 这个属性设置为true。否则上面的写法不会生效的。
到目前为止,我们的程序还不能开始工作。 为什么呢? 因为Nest 不知道任何 UserService 的信息。这个Provider(Service) 还不是ApplicationModule的一部分。那我们就应该配置中添加
同理这里的写法是4的 5中 需要 替换成 providers
好了 让我们跑一下我们的代码。不过你会发 还是不能很好的跑起来。当adduser 的时候。
为什么呢?因为当我们尝试去解析 请求报文的时候(req.body.user)并没有相对于的中间件对报文提前解析。 来 我们按照一下插件
然后设置一下 Express 的配置
Async / await
Nest 是支持 ES7中的async / await 的功能的。所以我们改一下书写风格
是不是看着舒服多了?
Modules
搬砖定义:(A module is a class annotated with a @Module() decorator. The @Module() decorator provides metadata that Nest makes use of to organize the application structure.) 一句话 这个修饰器 就是用来组织程序架构的
目前 我们的程序 ApplicationModule(碰到compents 就自行更改成Providers 吧 不啰嗦了):
模块封装了每一个依赖。这就意味着我们不可以使用它的Provider 和 controller 在它之外。想交流必须通过 他们的module 这个模块。 这种方法也让Nest 在微服务的场景中游刃有余,清晰明了。每一个模块可以导入别的模块。从某种意义上 我们可以理解为Nest Modules 是一个树形 modules
那我们把我们以前写的UsersController 和 UsersService 挪到 UsersModule. 只需要简单的创建一个文件 例如(. users.module.ts) ,内容如下:
然后我们把UsersModule 导入到 ApplicationModule (我们的主程序模块):
一切搞定! 是不是很明晰。
通过上面的手法 我们可以非常简洁自然的把我们的代码划分成 独立 可重用的 模块
Middlewares
搬砖定义:( The middleware is a function which is called before the route handler. Middleware functions have access to the request and response objects, and the next middleware function in the application’s request-response cycle. The next middleware function is commonly denoted by a variable named next.)
1. 它不是类 是函数
2. 它在路由处理之前被调用
3. 它可以访问和更改请求和回复的对象
4. 如果你不调用 next()的话,请求报文将不会被 路由处理
让我们来搭建一个假的认证中间件。我们将用 X-Access-Token HTTP header 来提供名字 (只是举例现实中别这么用 ).
记住以下几点:
1. @Injectable() 是来告诉 Nest 这是一个 middleware
2. 使用 NestMiddleware 这个接口,可以让我们去实现 resolve() 这个方法
middlewares 可以像 Provider 一样 通过创造函数来注入依赖对体中。middlewares必须包含 resolve() 这个方法,它是用来返回给另外一个高阶函数的 ,这也就意味着 中间件有可能不只一个 。当然我们还可以引入 中间件函数类型 对 我们写的resolve函数的返回做验证
友情提示: 我们添加getUsers 方法 到 UsersService, 用来返回一个users 的数组。
我们已经准备好了 一个中间件了,那我们下一步就是要把它注入到调用的地方去
如下:
从上图得知,Modules 有额外的方法 configure(). 这个方法接收 MiddlewareBuilder 类型的对象作为参数, 它可以有效的帮助我们去配置 middlewares. 这个对象的apply() 方法将可以收到无限数的中间件(它使用了 Spread 操作符,所以传参的时候用逗号分隔就好了). apply() 这个函数返回的是一个对象,这个对象包含了两个方法:
1. forRoutes() –我们使用这个方法传入无限制的Controllers 或者是其他的对象,用逗号做分隔就好了
2. with – 我们可以用这个方法传入客户参数给 resolve()这个方法
更新一下:5的代码里面 好像改成了Middlewarecustomer,看了nest 代码 interface 下也没有builder 那个东西了。
When you pass UsersController in forRoutes method, Nest will setup middleware for each route in controller:
当我们把 UsersController 传入 forRoutes 这个方法的时候, Nest 将会为controller 中的每一个路由设置中间件
但是我们也可以直接定义带有路径的中间件,如下:
Chaining
在这里 apply 和 forRoutes 的组合 可以做无限制的叠加。
Ordering
中间件的调用顺序和他们的在数组中的顺序相同。
微服务:
微服务是通过消息来传递的。一般我们用TCP来传递消息。也可以自我配置 用Redis 也可以
正是由于Nest microservice 是通过TCP等协议来交互的。那就意味着 @RequestMapping()这一套Post ,Get 的解析机制就不可以再使用了。因为他们是基于HTTP的。那我们应该怎么识别消息内容呢? Pattern 样式 是我们唯一的选择。什么是Pattern呢? 它是一个对象,字符串,数字 只要定义好格式就好
创建 MathController:
好了今天就写到这里了。如果有拼写错误 或者写的不对的地方 请大家多多留言给我。
随着继续的学习 我们把剩下的一下东西 一点点的写出来。
晚安!
网友评论