NestJS global-hook


前言

NestJs,我第一个使用的后端框架。从我开始工作的时候,我慢慢学会的。NestJS 不像是 express 或者 fastify 那样底层,更像是一个’中间件’,底层和上层部分都可以随意替换。如果你知道LLVM并基本了解LLVM的架构,这很像。

言归正传,我在公司的第一个项目的生产版本马上就要做完了,期间我遇到很多挑战。

其中就包含主题——循环依赖

这是什么?它是两个模块互相导入对方,就像是两条线头尾连了起来:没有出口了。

这个问题存在于一众编程语言中,并成为新手多个热门棘手问题中的其中一个。

问题

我一开始使用了回调,比如说你有两个模块,A和B。

A 依赖了 B,然后A会注册一个回调在B。

this.bService.registerOnDeleteCallback(() => {
    console.log("It is deleted");
    this.onDeleted();
  }
);

当某个事件发生时,模块 B 会通知模块 A,这保证了基本的内聚——每个模块只干自己的事情,我们不希望模块B直接对模块A的数据进行原始操作。

使用回调的办法很好,但是仅限于你只有少数几个。但是我编写这篇文章时已经有5个类似的实现了,绝对不行!

隐性耦合是一个你平时不易察觉的怪物,回调基于特定模块,意味着模块缺失了之后将会导致注册回调的模块出错。

你可能会说:这不是废话吗? 但是先别接,让我们接着往下看。

解决方案

于是我做了一个 ‘GlobalHook’ 模块专门处理回调,然后我把所有类似的代码都转为了对这个模块的引用,以一种 ‘注册事件,然后发出事件’ 的模式。

这听起来很像是上世纪的 ‘接线员’ 现在模块A需要在这个模块注册回调,然后模块B在这个模块发出事件。

接线员

现在其它模块可以做一样的事了:给出一个唯一标识符,注册这个标识符,在这个标识符上发出事件。

import { Injectable } from '@nestjs/common';
import { randomUUID } from 'crypto';

class EventFunction {
    callback: Function;
    unique: string;
}

class EventNode {
    event: string;
    callbacks: EventFunction[];
}

@Injectable()
export class GlobalHookService {

    constructor() {
        const un = this.registerEvent('npmtest', (payload) => {
            console.log("CB: ", payload);
        })
        console.log(un);

        this.raiseEvent('npmtest', { a: 'what' });
        this.removeEvent('npmtest', un as string);
        this.raiseEvent('npmtest', { a: 'what' });
    }

    private _hooks: EventNode[] = [];

    removeEvent(event: string, unique: string)
    {
        if(!this._hooks[event])
            return false;
  
        const callbacks = this._hooks[event].callbacks;
        for(let i = 0; i < callbacks.length; ++i)
        {
            if(callbacks[i].unique === unique)
            {   
                callbacks.splice(i, 1);
                break;
            }
        }
        return true;
    }

    registerEvent(event: string, callback: Function)
    {
        const ef: EventFunction = {
            callback,
            unique: randomUUID()
        };
        if(!this._hooks[event])
        {
            const en: EventNode = {
                event,
                callbacks: [ef]
            };
            this._hooks[event] = en;

            return ef.unique;
        }
        this._hooks[event].callbacks.push(ef);
        return ef.unique;
    }

    raiseEvent(event: string, payload: any)
    {
        if(!this._hooks[event])
            return;
  
        const callbacks = this._hooks[event].callbacks;
        for(const c of callbacks)
        {
            c.callback(payload);
        }
    }

}

真不错!

编辑:实际上,在我后面迁移博客的时候,我发现这个概念早就存在了!它叫 EventBus 。好吧,我不清楚为什么我更喜欢叫 Hook 一点,但是这两个的确有一点像