html调用微服务,前端微服务简单实践

  • Post author:
  • Post category:其他


最近看了字节跳动技术团队写的《前端微服务在字节跳动的打磨与应用》这一篇文章,对其中的服务注册和动态加载模块比较感兴趣,再加上之前做过一些类似的东西,所以就花了点时间做了一些简单的实践。希望可以帮助到大家。

我对微服务的理解

我理解的微服务,本质上就是把一个大型的应用拆分为很多个独立的模块,每一个模块的可以单独的开发、调试并上线。这样的好处我理解主要有以下几个:

每个模块都是一个独立的个体,如果有某个模块出现问题了,不会导致整个应用挂掉。

由于每个模块可以单独上线,因此上线会更快,有利于更新迭代。

由于有了服务注册的功能,因此页面都可以通过配置化的方式来动态加载,对于功能的新增、回滚特别方便。

框架无关(这可能取决于具体实现)

本文主要是想简单讨论下服务发现以及动态加载模块的一些实践。当然这里只是给出一种简单的思路,仅供参考。

服务发现

首先,我们来思考一个问题。如果我们将一个大型应用拆分为多个模块的话,那主程序怎么知道有哪些模块,以及各个模块对应的配置信息(js / css 等配置信息)呢。其实,查找配置的模块信息的过程,就叫做服务发现。

那么我们怎么实现服务发现呢?

有一种很简单粗暴的做法,就是我们将这些配置信息直接硬编码在主程序里面,可是这样造成的问题是什么呢?每一次你要新增、修改和删除模块的话,你都需要发布一次主程序,这种做法肯定是不行的。

那么,有没有更好的办法呢?

这个时候比较聪明的同学可能就想到了,那我把配置信息通过接口的方式调用不就行了?我个人比较推荐的也是这种做法。因此有时候我们需要根据用户的身份、权限来返回不同的模块配置信息,通过接口的话,我们就可以很方便的做到这一点。我给一个简单的模块配置信息模块:

[{

name: ‘home’,

path: ‘/home’,

js: ‘https://unpkg.com/react@16/umd/react.development.js’,

css: ‘https://unpkg.com/react@16/umd/react.css’

}]

复制代码

配置信息主要分为四项,path 指的是该模块对应的路由地址,也就是说,当前端匹配到路由为 /home 的时候,就会加载对应的 js 文件和 css 文件,并执行对应的 js 文件,渲染模块内容。

动态加载模块

那么问题来了,假设我们匹配到 /home 这个路由,加载了对应的 js 文件后,我们如何渲染对应的模块呢?

动态加载模块的话,目前我想到的有两种方案,一种是字节技术团队文中使用的 new Function + CommonJs 的方案,还有一种是类似于 AMD 的方案,接下来我简单的介绍下两种方案实现。

new Function

基础知识

不清楚同学们有没有将文件打包为 CommonJs 格式,我先贴一段 CommonJs 打包后的样子

61e62edf01169a849d2a3ba9035ea199.png

从图中我们可以看到,其实 CommonJs 打包后,会将你导出模块的内容都挂在 exports 这个对象上,因此,我们就可以结合 new Function 使用。多说无益,我们来结合代码食用

首先我们先实现一个简单的模块功能

import React from ‘react’;

import ReactDom from ‘react-dom’;

function App() {

return React.createElement(‘div’, null, ‘hello world’);

}

export const render = container => {

ReactDom.render(React.createElement(App), container);

};

复制代码

这段代码特别简单,就是正常的实现了个 hello world 逻辑,并且导出了一个 render 方法。

我们接着来看下主程序是如何加载模块的

// 全局模块管理

const modules = {};

function loadModule() {

const currentConfig = {

name: ‘home’,

path: ‘/home’,

js: ‘./dist/main.js’,

};

const { name, path, js } = currentConfig;

modules[name] = {

exports: {},

};

const ajax = new XMLHttpRequest();

ajax.open(‘get’, js);

ajax.onload = function(event) {

new Function(‘module’, ‘exports’, this.responseText)(

modules[name],

modules[name].exports,

);

modules[name].exports.render(document.getElementById(‘app’));

};

ajax.send();

}

loadModule();

复制代码

实现步骤

看代码可能大家就比较容易理解了,主程序在加载模块的时候,主要分为以下步骤:

开发模块的时候,按照约定,导出一个 render 方法。

主程序加载的时候,根据配置信息,创建模块的 module 信息。

通过 xhr 加载拿到模块的 js 代码,并通过 new Function 的方式,将我们的模块 module 信息传进去执行 js 代码,这样 js 代码导出的内容就会挂载到 modules[name] 上。

调用模块导出的 render 方法来渲染模块内容。

这里面有两个关键信息

导出的模块必须选用 CommonJs 打包类型,否则无法将我们自己的 module 传进去。

加载模块的时候使用 xhr 请求,这样才能拿到代码的 source code。

以上就是通过 new Function 来实现动态加载模块的关键。 接下来我们来讲下类似 AMD 的实现思路。

AMD

AMD(Asynchronous Module Definition) 跟 CommonJs 一样,也是一种模块管理方案。它的特点在于,你每次要定义一个模块的时候,都需要使用如下类似的写法

define(‘myModule’, […deps], function () {

…some code

});

复制代码

通过这种方式定义模块的话,其他模块就可以通过依赖项注入的方式来使用该模块。当然,我们这里不涉及太深入的东西,只是简单做了个实现。还是用之前那个 hello world 例子,不过这次我们做了些修改:

import React from ‘react’;

import ReactDom from ‘react-dom’;

function App() {

return React.createElement(‘div’, null, ‘hello world’);

}

const render = container => {

ReactDom.render(React.createElement(App), container);

};

window.defineModule(‘home’, {

render,

});

复制代码

主要修改在于,我们不通过 export 将模块导出了,而是通过 window.defineModule 这个方法来定义自己的模块。而 window.defineModule 这个方法的实现,则是放在主程序下:

const namespace = Symbol(‘namespace’);

window[namespace] = {};

function defineModule(name, exports) {

window[namespace][name] = exports;

}

function getModule(name) {

return window[namespace][name];

}

window.defineModule = defineModule;

function loadModule() {

const currentConfig = {

name: ‘home’,

path: ‘/home’,

js: ‘./dist/main.js’,

};

const { name, path, js } = currentConfig;

const scriptEle = document.createElement(‘script’);

scriptEle.src = js;

scriptEle.onload = () => {

const module = getModule(name);

module.render(document.getElementById(‘app’));

};

document.body.appendChild(scriptEle);

}

loadModule();

复制代码

实现步骤

这里代码应该也比较容易理解,接下来我们来梳理下实现步骤

主程序通过定义一个 defineModule 方法,并将其挂载在 window 上来实现模块定义。

单独模块在开发的时候,通过 window.defineModule 方法来定义自己的模块,并将自己的 render 方法导出。

主程序在加载模块的时候,通过正常的创建 script 来加载。在加载完成后,根据模块的配置信息可以拿到模块的导出内容。

调用模块导出的 render 方法来渲染模块内容。

路由监听

当然,这里面其实还遗漏了最重要的一点,就是路由监听。因为我们每一个模块都是跟路由绑定在一起的,比如访问 /home 路由的时候才渲染 home 模块。对于路由监听的话,这里就不做展开了,有兴趣的同学可以看下 history 相关的接口以及 hashChange 事件。当然也可以看下 react-router-dom 的源码哈哈哈。

总结

本文只是简单的对前端微服务做了一些实践,并且讲了我对服务发现、动态加载模块的一些想法。只是个人的思考,希望能带给大家一些帮助。主要总结如下

服务发现可以通过配置化接口的方式来实现,一方面有利于模块动态增删改查,另一方面,可以根据用户的身份权限来返回不同的模块信息。

动态加载模块可以使用 new Function 和 类似于 AMD 的方式实现,具体使用哪种的话,取决于个人偏好吧。我个人觉得 window.defineModule 的方式可以更优雅点,调试起来可能更方便。

本文地址在->本人博客地址, 欢迎给个 start 或 follow。

内推

今日头条内推!!!!

d9ed4219e0c45e4bf7145ac5c00aad7d.png

b739ec46bb5c46d9c0aa4ce35ba1ea56.png

关于找一找教程网

本站文章仅代表作者观点,不代表本站立场,所有文章非营利性免费分享。

本站提供了软件编程、网站开发技术、服务器运维、人工智能等等IT技术文章,希望广大程序员努力学习,让我们用科技改变世界。

[前端微服务简单实践]http://www.zyiz.net/tech/detail-117740.html