# 微架构

随着应用规模越来越大,把所有业务模块的功能,全部都打包到一个项目中将会导致打包的速度越来越慢,而且如果多个团队去维护的时候,就很容易出现冲突。所以我们才需要拆分成不同的服务,分解业务模块。下面将会以 Followme5.0布局来带大家实现一个微前端、微服务的架构。

# 架构图

服务流程图

  • 从前端布局上看,分为左导航和右边的内容区域,是一个典型的左右布局
  • 从服务架构上看,分为聚合服务和子服务,所有的请求都会进入到聚合服务,根据不同的url地址去请求对应的子服务

# 提供服务的渲染接口

    const renderModes = ['ssr-html', 'ssr-json', 'csr-html', 'csr-json'];
    /**
     * 提供一个API允许外部渲染
     */
    app.use('/api/render', (req, res, next) => {
        // 获取渲染的地址
        const url = decodeURIComponent(String(req.query.renderUrl));
        // 获取路由渲染的模式
        const routerMode =
            ['abstract', 'history'].indexOf(String(req.query.routerMode)) > -1
                ? req.query.routerMode
                : 'history';
        // 渲染的模式
        const mode: any =
            renderModes.indexOf(String(req.query.renderMode)) > -1
                ? String(req.query.renderMode)
                : 'ssr-json';

        renderer
            .render({
                url,
                mode,
                state: {
                    routerMode
                }
            })
            .then((r) => {
                res.send(r.data);
            })
            .catch(next);
    });

上面一共接收了三个参数,渲染地址、渲染模式和路由模式,它将会提供一个公共渲染出口,方便其他的服务调用。

# 远程组件调用

<template>
    <div>
        <remote-view
            v-for="name in names"
            v-show="ssrname === name"
            :key="name"
            :clientFetch="() => clientFetch(name)"
            :serverFetch="() => serverFetch(name)"
        ></remote-view>
    </div>
</template>
<script lang="ts">
import Vue from 'vue';
import { RemoteView } from '@fmfe/genesis-remote';
import axios from 'axios';

interface Data {
    names: string[];
}
interface Methods {
    clientFetch: (ssrname: string) => Promise<void>;
    serverFetch: (ssrname: string) => Promise<void>;
}
interface Computed {
    ssrname: string;
}

export default Vue.extend<Data, Methods, Computed>({
    name: 'container',
    components: {
        RemoteView
    },
    data() {
        return {
            names: []
        };
    },
    computed: {
        ssrname() {
            return this.$route.meta.ssrname;
        }
    },
    watch: {
        ssrname() {
            if (this.names.indexOf(this.ssrname) > -1) return;
            this.names.push(this.ssrname);
        }
    },
    created() {
        this.names.push(this.ssrname);
    },
    methods: {
        /**
         * 客户端远程调用时,走 CSR 渲染
         */
        async clientFetch(ssrname: string) {
            const renderUrl = encodeURIComponent(this.$route.fullPath);
            const res = await axios.get(
                `http://localhost:3000/api/${ssrname}/render`,
                {
                    params: {
                        routerMode: 'history',
                        renderMode: 'csr-json',
                        renderUrl
                    }
                }
            );
            if (res.status === 200) {
                return res.data;
            }
            return null;
        },
        /**
         * 服务端远程调用时,走 SSR渲染
         */
        async serverFetch(ssrname: string) {
            const renderUrl = encodeURIComponent(this.$route.fullPath);
            const res = await axios.get(
                `http://localhost:3000/api/${ssrname}/render`,
                {
                    params: {
                        routerMode: 'history',
                        renderMode: 'ssr-json',
                        renderUrl
                    }
                }
            );
            if (res.status === 200) {
                return res.data;
            }
            return null;
        }
    }
});
</script>

  • 用户首屏访问的时候,我们在服务端远程预取数据,走的是 SSR 渲染,同时也有利于 SEO
  • 在客户端远程调用时,走 CSR 渲染,这样能降低服务器的压力
  • 为了避免用户访问同样的服务,每次都需要重新请求,所以定义了一个数组,将所有的已访问服务用数组存储起来,只展示当前用户访问的服务,其它的服务则隐藏起来

关于远程组件更深入的了解,请点击这里

# 页面多路由实例

采用了微前端架构,意味着着每个服务内部都有自己的路由,为了保证路由同步,我们提供了一个对 vue-router 包装的库,你需要从这个库来创建路由,点击这里了解更多

# 外置化依赖

随着服务越来越多,你可能不想每个服务都打包一次 vue 库,你可能想对某些依赖进行外置,点击这里了解

# 完整的例子

因为文档编写篇幅有限,所以我们写了一个完整的微前端&微服务的demo,你可以通过这个 demo 进行更加深入的了解。