【深入浅出 Node + React 的微服务项目】2.尝试做一个迷你微服务APP

【深入浅出 Node + React 的微服务项目】

尝试做一个迷你微服务APP

本文格式是针对 github 的 Markdown,所以目录链接 和 代码链接打不开

目录

APP-Preview

图2-01-创建 Post 和 Comments 的业务逻辑
  • 功能分别是发送 post 信息和发送 comment 信息
    在这里插入图片描述
图2-02-服务的拆分 分为 创建信息 和 展示信息

在这里插入图片描述

图2-03-React Client APP 的结构拆分,要对接上述两个服务

在这里插入图片描述

搭建项目

创建 react 客户端、post 服务、comments 服务

npx create-react-app client
mkdir posts && npm init -y && npm install express cors axios nodemon
mkdir comments && npm init -y && npm install express cors axios nodemon

创建 Post 服务

图2-04-Posts 服务架构

在这里插入图片描述

Code-2-01-Posts 结构
  • post 提供的 get post 服务
  • get 负责把 post 缓存的数据 send 出去
  • listen 监听端口
  • post id 的随机生成,const { randombytes } = require('crypto');
  • 对传来的 post 进行解析 body-parser 源码讲解
  • 储存 post 的数据结构
  • 设置返回状态码

最后设置 package.json

"scripts": {
    "test": "nodemon index.js"
 },

测试 Posts 服务

这里需要用到 postman 的使用

  • 新建一个 posts 的 tab
  • 在 header 里 配置 Content-Type application/json
  • body 中 传入参数 raw -> json 类型的参数
  • {"title": "First Post by Sirius"}
  • 返回 {"id": "685baefe", "title": "First Post by Sirius"}

创建 Comments 服务

图2-05-Comments 服务架构

在这里插入图片描述

图2-06-每个 post 下 都会有 一个 array 类型的 comments
  • 这里用 Object 存 post id 的键值对关系,值是 comments 的 array,而 comments 还是之前和 posts 类似的 Object 类型
    在这里插入图片描述
Code-2-02-Commnets 结构
  • 和 post 服务不同的是
  • 因为有个 postId 的键值对关系,所以需要创建一个上述的数据结构
  • 每次取出 req 的 comments 进行 push 或 create 操作,然后重新赋值给 postId 对应 id 的 comments
  • 其他操作和 post 一样

测试 Comments 服务

我们在 postman 上的 url 中 要传值

  • 两种方法
    • localhost:4001/posts/:id/comments params 传值
    • localhost:4001/posts/123/comments 直接 url 上传值
  • body 如下数据结构
[
    {
        "id": "7532c5f3",
        "content": "I am a comments by Sirius of posts 123"
    }
]

创建 React 前端 APP

图2-07-React 页面结构划分

在这里插入图片描述

  • 目前将会遇到的一些 bug
    • npx create-react-app client 我在这里遇到了很大的坑,花了6个小时+买了一个腾讯云服务器才解决
    • 你在npm install的时候报错,比如Npm Error - No matching version found for这种,只需要闭着眼睛npm cache clean --force再来即可
    • 另外,yarn之类的如果没加在全局执行,运行不动的情况下,vim ~/.bash_profile 在里面export PATH=$PATH:/usr/local/bin/node16/lib/node_modules/yarn/bin这是你yarn被下载的地方引入即可,或者export PATH="$PATH:yarn global bin",并source ~/.bash_profile
    • 还有问题建议npm i n && n latest

创建 React 的 Post Create 表单提交

Code-2-03-ClientPostCreate
  • PostCreate 模块主要是创建 posts 的 title
  • 功能拆分如下:
    • form + label + button
    • form 的 onSubmit 要阻止提交 并 异步提交给 post 服务

创建 React 的 Comment Create 表单提交

个人开发习惯:
这里我们从底层组件到外层组件开发
所以跳过 PostList,因为他对子组件有依赖性,子组件只有prop的依赖
在这里插入图片描述

Code-2-04-ClientCommentCreate.js
  • 和 Post create 几乎一样

创建 React 的 Comment List 表单展示

Code-2-05-ClientCommentList.js
  • useEffect,第二个参数为空数组,第一个函数里表示需要在 componentDidMount 执行的内容
  • 这里在组件挂载完执行 fetch 网络请求
  • 获取父组件 post 传进的 postId
  • 模板字符串拼接成参数传进 get 请求的 url 里

创建 React 的 Post List 表单展示

Code-2-06-ClientPostList
  • 这里用 Object.values(posts).map 返回渲染后的 posts 列表
  • 包含 Comment create 和 Comment list 组件 并 传 postId
  • flex 布局,让超出视口的每个 Post card 换行:display: 'flex', flexWrap: 'wrap'
图2-08-四个服务的最终交互效果

在这里插入图片描述

请求次数减少的策略

图2-09-最终实现的请求集合
  • 可以看出每一个 post 我们都需要请求一次他们对应的 comments
  • post 如果无限 请求次数就会无限

在这里插入图片描述

图2-10-方法一

对于单体同步服务的优化:一次请求,获得所有 postId 下的 comments 集合
在这里插入图片描述

图2-11-单体同步服务的弊端

在这里插入图片描述

异步的解决方式

图2-12-事件代理的原理
  • 比如我们的 Post 和 Comment 服务,每次创建都 emit 一个事件,事件代理负责接收事件并发送给对这个事件感兴趣(或者之前订阅过的)服务
  • 如果要想获取 Post 或 Comment 服务,就比如用 Query 服务进行获取,Query 负责收集每次服务的数据
    在这里插入图片描述

Event bus 介绍

图2-13-异步事件总线的步骤
  • Step1:Post 发起创建的事件的操作,负责 Query 的服务负责处理存储事件的操作
  • 这里并没有存 Comment
  • Step2:Comment 发起创建的事件的操作,负责 Query 的服务也负责处理
  • 这里根据之前的 Post 进行对应的 Comment 存储
  • Step3:用户想获取就从 Query 服务进行
    在这里插入图片描述
图2-14-异步事件代理、事件总线 的优点

在这里插入图片描述

搭建一个最基础的 Event bus

Code-2-07-EventBus 结构

最基础的 event-bus 只需要提供 post 服务将 event 转发给 其他 service
所以,需要接收(express) 和 发送(axios),发送给每一个订阅 events 的服务

实现发出创建 Post 的 Events

Code-2-08-Posts 结构

根据图2-13-异步事件总线的步骤
创建后,只需要再发给 EventBus 一份即可

实现发出创建 Comment 的 Events

Code-2-09-Comments 结构

根据图2-13-异步事件总线的步骤
创建后,只需要再发给 EventBus 一份即可

获取 Events

Code-2-10-Event-bus 结构

创建 data query 的服务

Code-2-11-Query 结构
  • 只需要提供 post 服务,把 Query 里的 post 送出去

对进来的 Events 进行解析

Code-2-12-Query 结构

根据图2-13-异步事件总线的步骤

  • event 的数据结构中,包含 type 和 data
  • 根据 type 就能进行解析
  • type 不同,处理方式不同

使用 data query 的服务

  1. 🚩【git commit】移除 CommentList 中 Comment 请求操作,转而接收 post.comment 的 props(之前是 postId)
  2. 🚩【git commit】PostList 换成 Query 接口,传递 post.comment 到 CommentList 里

再添加一个简单的服务

图2-15-评论审核服务的介绍

在这里插入图片描述

图2-16-加上评论审核服务的ReactAPP效果图
  • 发出去的 comment 有三个状态:等待、展示、拒绝。
    在这里插入图片描述

使用之前 query 服务的方式

方法一
图2-17-创建审核更新到查询一条龙
  • 每当 comment 服务发送 CommentCreated 类型的 events 的时候
  • 先送给负责审核的 moderation service
  • 审核(不确定的时间)过后,再添加 status 通过 event-bus返回给 query 和 comment service
    在这里插入图片描述

存在的问题:
在审核的过程中,用户并不知道发的 comment 的状态
因为必须审核好了过后才能知道

方法二
图2-18-创建时通知查询服务更新状态为pending

在这里插入图片描述

优化不同类型事件的传递

图2-19-修改的事件类型复杂
  • 会让 query service 的代码变得复杂
    在这里插入图片描述

    怎样处理更新的事件

    图2-20-复杂业务逻辑抽象成简单逻辑

    在这里插入图片描述

图2-21-对于query服务只需监听更新事件
  • 审核服务还是保持原有的事件类型,因为如果同样抽象事件类型的话,会对审核服务的具体模块受到影响
  • 对于审核功能:query 服务只需监听 comment 服务那边的更新事件,做到跟审核服务无直接交互
  • 同时 query 还要监听创建事件,如果有更新,就从之前创建的来更新
    在这里插入图片描述
图2-22-有关comment的三种事件类型
  1. CommentCreated:创建 comment 的时候,给审核服务query 服务
  2. CommentModerated:审核过后,给comment 服务
  3. CommentUpdated:comment更新后,给query 服务
    在这里插入图片描述

创建一个审核服务

Code-2-13-Moderation 结构
  • 接收 type 为 CommentCreated 的事件
  • 进行 judge 审核
  • 审核好过后返回 type 为 CommentModerated 的事件

增加评论审核的状态

Code-2-14-Comments 结构
  • comment 服务在创建的时候 就设置 status 为 pending
Code-2-15-Query 结构
  • 同时 query 服务在 comment 创建过后,也同步 comment 的 status

处理审核

Code-2-16-Comments 结构
  • 给 comment 服务增加一个监听 event 的接口
  • 一旦 event-bus 有 event 是 CommentModerated,就更新自己的 comment,并发出 CommentUpdated事件

更新评论内容

Code-2-17-Query.js
  • query 服务收到 comment 发的 CommentModerated事件,取出对应的 comment,并替换内容直接替换 无需修改原因之前讲过

通过status渲染评论

Code-2-18-CommentList
  • 在 CommentList 每次渲染 comment 的时候加一个 status 判断即可

处理错过了的事件

图2-23-moderation服务某一段时间挂了
  • moderation服务某一段时间挂了
  • moderation 服务会错过 Event 2 3
  • 此时 2 和 3 会一直阻塞住,处于 pending 的状态
    在这里插入图片描述
图2-24-query服务某一段时间挂了或者还没开放
  • query 服务将不知道前几个服务发生了什么事件
  • query 服务将会错过之前的 PostCreated CommentCreated CommentUpdated
    在这里插入图片描述
图2-25-通过请求相关服务进行同步
  • 这样做等于还是进行了几个服务间的依赖关系
  • 且要在每个服务中开相关接口,很烦

图2-26-直接定位到相关数据库取数据
  • 但如果数据库格式(sql,nosql)不一样,同样就很复杂
    在这里插入图片描述
图2-27-用event-bus存储事件
  • 存储每一次的事件
  1. query 服务开放过后,直接获取全部事件,复原即可
  2. moderation 服务挂了又修复了之后,拉取挂之后的事件,复原即可
  • 虽然会占空间,但花费不了多少
    在这里插入图片描述
    在这里插入图片描述

实现事件存储修复query服务启动问题

Code-2-19-EventBus
  • 存储每一个 event,并开个 get 接口返回所有 event
Code-2-20-Query
  • 整合对 n 个 type 的反应成 handleEvent方法
  • 在自身接收 event 的服务中,每次调用该方法即可
  • 在开始服务的时候,同步所有 event,并同样调用该方法
图2-28-最终实现的效果图

在这里插入图片描述

图2-29-最终的Query服务发生了什么
  • 如果重启Query服务,将会把这张图下的所有 event handle
  • 再打印 Sync Event Finished!
    在这里插入图片描述
附:EventEmitter 的手写实现
class EventEmitter {
 
    constructor() {
        //回调中心
        this.listenerMap = {};
        //离线事件列表
        this.offlineListenerList = [];
    }
    /**
     * 添加事件监听
     * @param type 事件类型
     * @param fn 回调函数
     * @param flag 是否是离线事件
     */
    on(type, fn, flag) {
        if (!this.listenerMap[type]) {
            this.listenerMap[type] = [];
        }
        this.listenerMap[type].push(fn);
        //如果注册了离线事件,则在监听事件时,需要检测是否有离线事件缓存
        if (flag) {
            let index = this.offlineListenerList.findIndex(vv => vv.type === type);
            if (index !== -1) {
                fn.call(this, this.offlineListenerList[index].params);
                //清空该条离线事件记录
                this.offlineListenerList.splice(index, 1);
            }
        }
    }
    /**
     * 触发事件监听
     * @param type 事件类型
     * @param params 载荷参数
     * @param flag
     */
    emit(type, params, flag) {
        let fnList = this.listenerMap[type];
        if (fnList && Array.isArray(fnList)) {
            fnList.forEach(fn => {
                fn.apply(this, params);
            });
        }
        //如果注册的是离线事件,则吧
        if (flag) {
            this.offlineListenerList.push({
                type,
                params
            });
        }
    }
    /**
     * 移除事件监听
     */
    off(type, fn) {
        if (!this.listenerMap[type]) {
            return false;
        } else {
            let index = this.listenerMap[type].findIndex(vv => vv === fn);
            if (index === -1) {
                return false;
            } else {
                this.listenerMap[type].splice(index, 1);
            }
        }
    }
 
    /**
     * 只触发一次
     * @param type
     * @param fn
     */
    once(type, fn) {
        let fnList = this.listenerMap[type];
        let params = Array.from(arguments);
        if (!fnList || fnList.length === 0) {
            return false;
        } else {
            let index = fnList.findIndex( vv => vv === fn);
            fnList[index].apply(this, params.slice(1));
            fnList.splice(index, 1);
        }
    }
}
 
let event = new EventEmitter();
 
event.emit('test', 1, true);
 
event.on('test', params => {
   console.log('offline', params)
}, true);
 
event.on('cc', () => {
    console.log('normal', 22222);
});
 
event.emit('cc');
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值