学完了微服务和 monorepo 后,我们做了这个考试系统项目。
整体是模仿问卷星的流程,从创建考试、考试编辑器、答卷、自动判分、到排行榜等,流程比较完整。
我们通过 monorepo 的方式组织项目:
4 个微服务都是 apps 下的单独目录。
然后公共模块放在 libs 下,通过 @app/xxx 在项目里引入。
每个微服务都是独立跑的:
npm run start:dev user
npm run start:dev exam
npm run start:dev answer
npm run start:dev analyse
之间通过 tcp 来实现通信:
比如 answer 微服务调用 exam 微服务:
在 exam 微服务暴露 tcp 端口:
answer 微服务连接上它:
在 exam 写一个消息处理函数:
在 answer 里调用它:
但这个项目的多个模块比较独立,最终也没用到 tcp 通信。
同样,rabbitmq 的异步通信也没用到。
实际上大多数 node 项目我觉得都没必要用微服务架构,拆分会带来通信的复杂度,不如单体架构简单。
回顾下我们做这个项目的整个过程:
首先我们做了需求分析,分析了下有哪些功能:
直接用的问卷星的页面作为原型图:
这一步主要是明确做什么。
然后我们设计了下技术方案,做了技术选型:
当然,因为微服务之间比较独立,最终没用到微服务之间的同步(基于 tcp)和异步(基于消息队列)通信。
数据库设计:
分析了下接口:
接下来进入开发:
首先实现了 monorepo 架构:
创建了 user、exam、answer、analyse 这 4 个 app,还有 redis 这个公共 lib。
4 个微服务都单独暴露 http 接口在不同端口,之间还可以通过 TCP 来做通信。
libs 下的模块可以在每个 app 里引入,可以放一些公共代码。
然后实现了用户微服务的登录、注册、修改密码的功能。
过程中又创建了 prisma、email 的 lib。
通过 prisma 的 migrate 功能,生成迁移 sql 并同步到数据库。
之前用 TypeORM 也要做数据库迁移,不过需要自己准备这些 sql:
从数据库迁移方面来说,prisma 确实方便很多。
然后实现了考试微服务的接口,包括考试列表、考试创建、考试删除、发布考试、保存试卷内容的接口。
具体的试卷内容是用 JSON 存储的。
我们做了一个简单的低代码编辑器:
可以拖拽题型到画布区,然后在右侧编辑:
每个题目都设置分值、答案、答案解析
最终生成一个 json,把这个 json 保存到数据库。
其实一般的低代码编辑器都是编辑器拖拽,服务端存 JSON。
之后实现了这答卷微服务,包括创建答卷、答卷列表、答卷详情接口,导出答卷列表 excel 等接口。
然后实现了答题页面:
编辑完考试可以生成链接,打开链接答题后就会保存提交的答案。
渲染试卷 json 的逻辑和预览时一样。
表单 onChange 的时候修改 answers 状态,当点击提交的时候调用接口保存答卷。
这样从新建考试,编辑试卷,到答题提交答案的流程就完成了。
答题结果也是用 json 保存的。
格式是这样:
[
{
id: 1,
answer: 'xxx'
},
{
id: 2,
answer: 'yyy'
}
]
我们通过和试卷 json 的答案对比,实现了分数的计算:
之后又实现了排行榜。
排行榜的功能基于 redis 的 zset 实现,用 zadd 往其中添加元素,用 zrang 取排好序的前多少个元素,加上 REV 就是按照分数从大到小排序。
然后加了一个弹窗来展示排行榜。
这样,整个流程的功能就开发完了。
这个项目主要是熟悉了 monorepo 的架构,并且知道了低代码编辑器的存储方案。
如果你要开发微服务项目,也是基于这种 monorepo 的项目结构来开发。
整个项目流程都是对标问卷星来的,虽然有些简化,但功能是一样的。
不过这个项目比较简单,没用到微服务之间的通信。
实际上,单体架构的 node 项目占绝大多数,一般没必要用微服务的方式写,只会增加项目的复杂度。