前情回顾
乡党们早上好,上一篇从0开始轻松搭建Nestjs环境 是整个Nestjs系列的"开胃菜",今天将带领大家开始边缘计算平台的第一个业务开发,你将从中学到以下知识:
- 如何分析业务
- 轻松拿捏ER关系图、用例图、类图、数据流图
- 后端分层设计
- 第一个HTTP接口
- 调试
上面的内容就已经注定了本章节会相当特么的枯燥。但是!但是!楼主会尽可能的消除枯燥,你信我,看完这篇文章,你自己会在潜移默化中慢慢的形成标准化,江湖中流传的程序员的野路子、非正规军就会慢慢的从你身上褪去。什么?七天Nestjs从听说到放弃,根本不可能,吧!
楼主会从一个从未接触过后端开发人员的视角尽可能详细的讲解每一个知识点,整个边缘计算平台实战系列内容主要以Nestjs实战为主题,同时为了内容的完整性、真实性,你们还将额外学到以下知识:
自定义Nest模块
上一篇从0开始轻松搭建Nestjs环境文章有提过Nest内置的根模块 appModule ,在各类关于Nestjs的文章中大多数作者都是先给读者balala讲一堆枯燥的概念,而楼主恰恰与他们相反,人都是有惰性的,我们的思维模式大多都是出现问题、解决问题,我会用业务场景来驱动(出现问题),再应用这些概念知识点(解决问题)。
需求分析之边缘工程管理
所谓边缘工程,说白了就是一个一个的施工现场。边缘计算平台就是来管理这些施工现场的人以及人产生的数据,物以及物产生的数据。接下来,我将用标准的软件模块的概要设计流程来带大家一步步完成从设计、编码的过程,以后你们编写模块或者系统概要设计文档,完全可以参照楼主这个标准流程,这一套组合拳就等于专业。
01-ER关系图
概要设计第一步,构建系统顶层ER关系图,实体包含了虚拟实体和真实实体,描述系统内关键实体与实体之间的关系以及比例。关系常用菱形+动词,比如下图的管理、包含,用菱形包裹起来。比例就更好理解,1个工程现场包含许多人,许多用字母N来代表。
上图是一张非常经典的ER(Entity Relationship)关系顶层视图(顶层指的是最上层的视角看系统),我们平时做的每个软件或者系统都与之类似。可以看到 工程现场 这里用的是虚线,代表它是一个虚拟的组织,真正的核心是围绕它的人和物,但是这个虚拟组织还必须存在,它是切割业务的关键,人必须是某个工程下的人,物也类似。
02-用例图+原型图
上图是产品经理输出的工程管理的用例视图,图中描述了用户对工程有哪些比较典型的操作用例,为了让大家更有画面感,下图是原型图,大家看了应该更容易理解
03-细化实体
通过前面章节我们已经确定了系统中的一个关键实体-工程 ,接下来让我们继续补充ER关系图,根据需求来枚举工程实体包含了哪些属性,注意在ER关系图中,属性用椭圆形。
细心的同学肯定发现了原型图中用户只需要输入名称、描述 两个字段,而我们的工程实体却额外包含了 创建时间、创建人、更新时间、更新人4个字段。其实这个很好理解,你总不能让用户在表单里输入自己是什么时候创建这个工程的,这不是多此一举吗?另外,这4个字段基本上是所有软件里面涉及到实体的通用的,方便后期的统计和追溯。
04-实体定义
现在我们将ER关系中的实体转换成面向对象领域的实体类,工程实体是一个非常简单的实体类
现在的问题就很简单了,当用户在界面上创建工程后,我们系统需要创建一个对应的工程实体
请仔细看上图的流程,这里肯定有同学有疑问,绿色背景的申请接收人是不是多余?不能直接提交给工程实体的创建人吗? 这个问题问的特别好,这也是很多初学后端开发同学经常疑惑的地方,我来给大家解释一下,这种设计叫分层设计,功能内聚,它在我们生活中无处不在,比如下图
每一层只负责自己分内的事情,一个规范的饭馆是不会让服务员又负责招呼,同时又负责炒菜的。请大家从今天开始就建立起功能内聚的思想,只有功能内聚,才能任意组合出多种场景。
05-参与者
申请接收人与实体创建人 或者像川菜馆中的 服务员与厨师 都属于某个流程或者场景下的过程参与者,在系统内这些参与者同样需要体现,如下图
至此,我们的设计部分已经基本结束,通过我们的梳理,我们需要通过代码实现以下三个对象:
- ProjectApplyReceiver(申请接收人,对外负责接收HTTP请求,并且获取请求信息的工程名称、描述,对内负责调用ProjectCreator提供的create方法)
- ProjectCreator(工程创建人,提供create方法完成工程的创建)
- Project(工程实体)
设计与具体的框架或者语言无关,以上设计你用Springboot能实现,也能用Nestjs实现,接下来我们就需要到Nestjs框架里,看看通过哪种方式能实现我们上面设计的3个对象。
06-Module模块
Nest模块是把一组处理相关业务 的对象打包聚集在一起的一个盒子。盒子具备一定的封闭性,你可以选择性的把盒子里面的东西暴露出来让其他人使用。用Nest开发的系统,其实说白了,就是由许多个模块盒子组装起来的
现在,创建一个名叫modules的目录,这个目录会存放之后我们自己定义的所有module。
打开命令行或者vscode的终端,输入nest脚手架提供的创建module的命令,这个命令会自动帮我们创建好几个文件(你想手动创建文件也可以)
bash
# 下面命令翻译一下就是用 nest(命令) generate(生成) module(模块) 模块叫project,存放在modules目录下
nest g module modules/project
可以看到命令输出包含了创建一个project.module.ts文件,同时又更新了 app.module.ts 这个根模块,之后会讲到为什么会更新根模块,现在还不是时候。
我们来看一下nest帮我们创建的ProjectModule,非常简单的定义了一个类
唯一特殊的是类上面加了一个 @Module ,@符号的专业术语是注解 或者叫装饰器 ,楼主会在之后的章节用图解的方式讲解注解的原理,现在你只需要知道,它的作用就是告诉nest这个类充当模块的角色。
07-编写工程实体类
模块盒子已经有了,接下来就是往盒子里装东西了,我们需要逐步把之前定义的3个对象通过代码的形式编写出来,从底向上开始,先来编写工程实体类Project,nest框架建议所有的文件名都遵循以下规则:
js
${模块名称}.${文件内定义的类充当的角色,比如module代表模块,entity代表实体等}.ts
显然我们要创建的是实体这个角色,套用上面的规则,就有了project.entity.ts文件
08-编写工程创建者
继续创建一个叫project.provider.ts的文件,provider英文是提供某种能力的人。 该文件来编写我们的工程创建者ProjectCreator,它提供了create的能力
用过nest框架的同学看到这里肯定会疑惑,这里怎么没有依赖注入 ,兄弟,现在还不是时候,等我讲完了你就自动理解为啥要依赖注入了,现在直接讲,你只会雪崩。
09-编写申请接收者
我们之前定义的3个对象已经完成了2个,接下来我们编写工程申请接收者 ProjectApplyReceiver 。Nest框架约定了当某个对象(或者叫类也好)是负责接收外部输入信息 时,这个对象就充当了控制器controller的角色,看到了吧,Nest的规范现在是逐渐体现,它可以把系统内所有的对象按照职责划分的明明白白,按照约定我们创建一个project.controller.ts的文件
ProjectApplyReceiver 提供了receive方法接收浏览器发送的HTTP申请创建工程的信息,内部通过 new 关键字 实例化一个工程创建者并且调用它的create方法创建一个工程。没毛病吧兄弟们,上面写的这些代码是最基础的面向对象编程思维,我个人认为已经非常非常简单了,但凡学过一点点面向对象知识,应该都能看懂吧。
现在最关键的还剩下1个问题:
- 浏览器调用的接口地址是啥?我们定义的申请接收者如何与HTTP接口进行关联?
10-HTTP接口定义
现在我作为这个HTTP接口的开发者,我希望浏览器发送一个POST请求,POST请求体采用JSON格式,数据包含了此次要创建的工程名称、描述。这个接口的URI我采用最标准的Restful风格的HTTP接口(文末的码农集合章节会详细讲解什么是标准的Restful),当接口请求成功后,返回字符串"ok"
bash
POST http://${IP地址}:${端口}/projects
# body示例
Body
{
"name":"内蒙古工程",
"description":"xxxxxxx"
}
# 响应示例,Post接口默认返回的HTTP状态码是201
Response 201
"ok"
11-申请接收者与HTTP接口绑定
我们给申请接收者 添加一个 @controller 注解(或者叫装饰器),目的是告诉Nest,我这个类充当了控制器的角色,并且有能力接收 /projects开头 的所有HTTP请求.
同时,我们给receive方法上添加一个 @Post 注解,更进一步告诉Nest,我这个方法是处理Post请求的
12-debug调试
兄弟们,到了最激动人心的时候了,成败在此一举,我们把服务用debug模式启动一下
bash
npm run start:debug
如果你使用的是vscode,可以看看侧边栏的 NPM SCRIPTS,编辑器会自动识别到package.json里的命令
启动成功后,有2条日志引起了我的注意,后续Nest底层深入研究的时候你们会看到这个RoutesResolver
朋友们,别忘了我们服务是在本地电脑启动的,ip就是localhost,端口是我们之前设置的3000,我们用postman来发送一个post请求,看看receive方法是否会被调用,记得提前打个断点哦
注意看,我们使用POSTMAN发送请求必须在请求头(也就是http header)添加内容类型字段Content-Type。这个字段是告诉HTTP,我此次发送到数据是什么格式或者类型的,很多新人在这里很容易翻车....
点击发送,看看是否会进入到我们的断点
没毛病,显示高亮代表我们发送的HTTP接口已经与receive方法打通了,点击continue这个按钮让程序继续执行
现在我们已经成功返回了字符串"ok",可是控制台打印的project这个实体却出现异常,没有名称和描述!
再来看看我们刚才的receive方法,出现上面异常情况的原因就是参数name和description变成undefined
这是因为Nest并不会 自动 把HTTP接口中BODY体的数据转换成我们方法的入参!需要开发者通过 @Body() 这个注解来告诉Nest:伙计,看到没,我把Body体的name字段和方法的name参数绑定到一起了,一会有请求来了,记得赋值啊。
让我们再调用一次接口,看看数据是否已经正常
完美!至此我们用Nestjs成功开发了第一个模块以及第一个HTTP接口。Nest的概念和用法非常多,今天这篇属于真正的入门级,楼主十分尽力的在弱化Nest的内部概念,比如模块,提供者,控制器,更多的是从所有后端开发的标准流程出发来阐述,因为很多读者也许是初次接触后端开发,上来我就讲控制反转 给人一顿输出,那岂不是演绎了一出活生生的Nestjs从听说到放弃系列?
下一篇,我将会继续丰富Project这个模块,CRUD我们只完成了不到1%而已,还是延续楼主的讲课理念,通过需求来驱动设计,框架来更完美的实现设计,朋友们,下期见!
码农集合
欢迎大家来到码农集合,作为惯例,楼主会在每篇文章的末尾给各位补充一些软件行业的小知识,我会用图形化的方式将复杂的事情给你讲明白,本期讲解的是Restful风格的HTTP接口,let's get started!
HTTP接口进化史
早在15年前,楼主还在用JAVA的老三驾马车struts、hibernate、spring的年代(大概就是这种发型最流行的时候)
那个时候还没有什么Restful风格,HTTP接口几乎都是一个POST方法包揽所有,接口的URL大多都是下面这样
bash
POST /addUser
POST /deleteUser
POST /updateUser
POST /getAllUsers
......
后来随着分布式、集群、微服务这些概念的出现,国外有个老铁提出Restful(Representational State Transfer)风格,
注意看前面三个,资源、统一接口、无状态通信
我们本章讲述的边缘工程Projects(project后面加了一个s这个是Restful推荐的,也就是用复数来表达)就是资源,针对这个资源,我们如果设计了如下接口
- POST /projects 新增一个边缘工程
- PATCH或者PUT /projects/${projectId} 更新某个边缘工程
- DELETE /projects/${projectId} 删除某个边缘工程
- GET /projects 获取全部边缘工程
- GET /projects/${projectId} 获取某个边缘工程
那么这些接口就满足了前2个概念,也就是针对projects资源的管理我们用HTTPP内置的更语义化的Method,比如POST,PATCH,DELETE,GET来表达,以前那种比如
- POST /addProject 新增一个边缘工程
- POST /updateProjectById?projectId=xxx 更新某个边缘工程
这样的设计风格就不符合Restful。
至于第三个 无状态通信 ,各位前端人员都知道cookie吧,过去的系统当浏览器调用登录接口,后端会在HTTP的响应头里的cookie字段里写入一个会话id的东东。通过会话id,后续服务器就知道是谁在调用这个接口了。
也就是服务器存储了会话状态,没错吧,上图中的映射不就是会话状态吗?
但是Restful要求无状态,也就是服务端不能再有映射表了,然后你知道吗,token这个东西就出现了。可是,本节课不讲token哦。哈哈,各位,下期见,爱你们!