Use of microservices

Posted by efficacious on Tue, 08 Feb 2022 20:56:17 +0100

1. Install Qiankun

npm i qiankun -S

2. Modify main application

  • The main application is divided into two forms: routing loading and manual loading
  • Loading in the form of routing, adding the startup code in the startup file, which is suitable for the scenario of replacing sub applications based on a large area, such as switching the home application to directly replace the current application.
registerMicroApps([
    {
        name: 'stream-media', // app name registered
        entry: '//localhost:9001 ', / / load the application address
        container: '#Stream media ', / / load the application bound domi d
        activeRule: '/test/stream-media', // Load application's route
    }
]);
start();
  • Manual loading is suitable for dynamically creating a small page in other routes
import React, {useEffect, useRef} from 'react';
import {loadMicroApp} from 'qiankun';
import {withRouter} from "react-router-dom";
import GlobalShared from "@/share";

const StreamMedia: React.FC<any> = (props: any) => {
    const divRef: any = useRef<HTMLDivElement>(null);
    useEffect(() => {
        const videoStreamApp = loadMicroApp({
            name: 'video-stream', // app name registered
            // Deployment address
            entry: process.env.REACT_APP_VIDEO_STREAM as string,
            container: '#video-stream',
            props: Object.assign({
                token: sessionStorage.logintoken,
                height: divRef.current.offsetHeight, // margin-bottom
                GlobalShared
            }, props.location?.state)
        }, {
            // sandbox: {strictStyleIsolation: true} / / forced isolation
        });
        return () => {
            /**

             * Uninstall the micro app

             */
            videoStreamApp.unmount();
        }
    }, []);
    return (
        <div ref={divRef} id="video-stream" style={{position: 'relative', height: '100%'}}></div>
    )
};
export default withRouter(StreamMedia);

3. Modify micro application

  • config-overrides. There are two main modifications in JS: one is to specify the output, and the other is to allow cross domain
/**
 * Microservice parameter processing
 * @type {string}
 */
config.output.library = `stream-media`;  // Note that this is the sub application name registered by the main application
config.output.libraryTarget = "umd";
config.output.publicPath = 'http://localhost:9001/'; //  Note that this is the startup address of your sub application

devServer: overrideDevServer(
    // dev server plugin
    (config) => {
        /**
         * Allow cross domain configuration
         * @type {{"Access-Control-Allow-Origin": string}}
         */
        config.headers = {"Access-Control-Allow-Origin": "*",};
        return config;
    }
)
  • Three functions need to be exposed in the startup file index
export async function bootstrap() {
    console.log('[react16] react app bootstraped');
}

export async function mount(props: any) {
    console.log('[react16] props from main framework', props);
    startUp(props);
}


export async function unmount(props: any) {
    const {container} = props;
    ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root'));
}
  • Add public path under the root path JS is used to modify the publicPath path
if (window.__POWERED_BY_QIANKUN__) {
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

4. The development environment deals with the cross domain problem of loading micro application js, json and other files

  • Micro applications need processing paths

In micro applications, the exposed parameters are extracted into env file management

#Note that this is the sub application name registered by the main application

REACT_APP_QIANKUN_library= video-stream

#Supported import forms

REACT_APP_QIANKUN_libraryTarget= umd

#Note that this is the startup address of your sub application

REACT_APP_QIANKUN_publicPath= http://localhost:9004/

When loading js, you need to handle the next path when starting as a micro application

// @If TS ignore is started as a micro application, modify the path
if (window.__POWERED_BY_QIANKUN__) {
    GlobalStaticFile = GlobalStaticFile.map((url: string) => {
        // @ts-ignore
        return Path.resolve(process.env.REACT_APP_QIANKUN_publicPath, url);
    });
}

It is suggested to use the prefix json when loading the main file, which is convenient for cross domain application and management

If you use fetch to report an error, please delete the 'content type': 'application / JSON' attribute of headers

Access to fetch at 'http://localhost:9004/bin/layout.json' from origin 'http://localhost:3000' has been blocked by CORS policy: Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response.

/**
 * Get resource path
 * @param url
 */
const getPathName = (url: string) => {
    // @ts-ignore
    if (!window.__POWERED_BY_QIANKUN__) {
        return  Path.resolve(window.location.pathname, url);
    }
    // return url;
    return Path.resolve(process.env.REACT_APP_QIANKUN_library as string, url);
};
  • The main application needs to handle cross domain
setupProxy Cross domain processing in

/**
 * Dealing with cross domain problems of micro applications
 */
app.use(
    createProxyMiddleware('/video-stream', {
        target: 'http://localhost:9004', //
        changeOrigin: true, // needed for virtual hosted sites
        ws: true, // proxy websockets
        secure: false, // Verify SSL certificate. Apply to https
        pathRewrite: {
            '^/video-stream': ''
        }
    })
);

5. Handling style isolation

Add prefix, '@ ant prefix': process env. REACT_ APP_ QIANKUN_ library,

addLessLoader({
    lessOptions: {
        javascriptEnabled: true,
        localIdentName: '[local]--[hash:base64:5]',
        modifyVars: Object.assign({}, getThemeVariables({
            // dark: true, / / enable dark mode
            compact: false, // Turn on compact mode
        }), {
            '@ant-prefix': process.env.REACT_APP_QIANKUN_library,
        }),
    }
}),

The ant message (creation method) cannot be destroyed and the style is unavailable

Idea: if the style is unavailable, download the style independently, maintain it independently, and add a prefix

/* stylelint-disable at-rule-empty-line-before,at-rule-name-space-after,at-rule-no-unknown */
/* stylelint-disable no-duplicate-selectors */
/* stylelint-disable */
/* stylelint-disable declaration-bang-space-before,no-duplicate-selectors,string-no-newline */
.video-stream-ant-message {

 ...

}

AntdMessage.config({
    prefixCls: `${process.env.REACT_APP_QIANKUN_library}-ant-message`,
});

// Destruction problem: manual destruction
AntdMessage[fnKey]({
    content: _msg,
    key: key,
    className: key,
    duration: 3,
}).then((t: any) => {

    /**
     * There is a bug in the ant component implementation when the micro component is destroyed
     * Keys cannot be destroyed directly
     */

    AntdMessage.destroy(key);
    const msg: any = document.getElementsByClassName(`${key}`);
    if (msg && msg[0] && msg[0].parentNode) {
        msg[0].parentNode.removeChild(msg[0])
    }
});

6. Parent-child communication problem

  • Pass props to micro applications (directly, bound to client sessionStorage localStorage)
  • Use Actions to communicate
// The api provided by qiankun uses observer mode to handle data flow

import { initGlobalState, MicroAppStateActions } from "qiankun";
const initialState = {};
const actions: MicroAppStateActions = initGlobalState(initialState);
export default actions;

//assignment

actions.setGlobalState({ token });
// Register observers to monitor changes
actions.onGlobalStateChange((state, prevState) => {
      // State: status after change; prevState: state before change
      console.log("Main application: the value before change is ", prevState.token);
      console.log("Main application: the login status changes, and the changed value is ", state.token);
    });

// The sub application needs to accept actions in the index
// The communication mode is also through setGlobalState
// When TODO communication service is complex, this scheme is difficult to maintain and the data flow is difficult to track
  • Pass it to micro application in shared mode

redux is used to manage the state, and the class of managing the state is passed to the micro application as a tool to achieve the purpose of data sharing and interaction

  • // src/share/Store.ts
/**
 * Type of exposure
 */

export enum SharedType {
    TOKEN = 'SET_TOKEN'
}

/**
 * The bound data can be added later
 */

type SharedState = {
    token: string,
}

/**
 * Action support
 */
type SharedAction = {
    type: string,
    value: any,
}

const reducer = (state: SharedState, action: SharedAction): SharedState => {
    switch (action.type) {
        case SharedType.TOKEN:
            return Object.assign({}, state, {token: action.value});
        default:
           return state;
    }
};


const SharedStore = createStore<SharedState, SharedAction, any, any>(reducer as any, {} as any);

export default SharedStore;
  • //Main application Src / share / index ts
import SharedStore, {SharedType} from './Store';

class Shared {
    /**
     * Get token
     */
    public getToken = (): string => {
        return SharedStore.getState()[SharedType.TOKEN];
    }

    /**
     * Set token
     * @param token
     */
    public setToken = (token: string): void => {
        SharedStore.dispatch({
           type: SharedType.TOKEN,
            value: token
        });
    }
}

const GlobalShared = new Shared();

export default GlobalShared;
// login is through GlobalShared Settoken is used to assign a value and pass the whole GlobalShared to the micro application
  • //Micro app / shared / index ts
    import GLOBALCONFIG from "@share/HttpClient/GLOBALCONFIG";
    
    class Shared {
        /**
         * Get token
         */
        public getToken(): string {
            return localStorage.getItem(GLOBALCONFIG.USERlocalStorage) || '';
        }
    
        /**
         * Set token
         * @param token
         */
        public setToken(token: string): void {
           localStorage.setItem('autoLogin', token);
        }
    
    }
    
    class SharedModlue {
        static shared = new Shared();
        /**
         * The shared passed in by the parent application is overwritten
         * @param shared
         */
        static overloadShared(shared: any) {
            SharedModlue.shared = shared;
        }
    
        /**
         * Get the token in shared
         */
        static getShared() {
            return SharedModlue.shared
        }
    }
    
    export default SharedModlue;
  • //Override in index render function
if (GlobalShared) SharedModlue.overloadShared(GlobalShared);

7. After deployment, static resource requests must support cross domain and nginx configuration

location /video-stream  {  
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
        add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';

        if ($request_method = 'OPTIONS') {
                return 204;
        }
              
        try_files $uri /video-stream/index.html;
        index  /video-stream/index.html;
    }

Topics: Javascript IDE