dasha_programmist
@dasha_programmist
ex Software Engineer at Reddit TS/React/GraphQL/Go

SSR: каков должен быть принцип кэширования состояния в angular universal?

Есть веб-сервер на express, на нём запилен angular universal. Работает он так что на каждый приходящий запрос серверного рендеринга он создаёт новый экземпляр ngModuleRef, бутстрапит его, ждёт когда приложение застабилизируется (isStable=true) и рендерит. Суть проблемы: сторы хранят наборы данных, запросы которые получаеют данные с АПИ по сетке ходят долго (объемы ~2-3 мб), но могли бы переиспользоваться как на клиенте, то есть 1 раз загрузить и потом брать из стора (выходит рендерится страница 5-6 сек). Начали копать ngExpressEngine, переделали вот этот кусок кода:
const moduleRefPromise = setupOptions.aot ?
        platformServer(extraProviders).bootstrapModuleFactory(moduleFactory as NgModuleFactory<{}>) :
        platformDynamicServer(extraProviders).bootstrapModule(moduleFactory as Type<{}>);

      moduleRefPromise.then((moduleRef: NgModuleRef<{}>) => {
        handleModuleRef(moduleRef, callback);
      });

на такой, чтобы переиспользовать модуль:
if (!moduleR) {
                const moduleRefPromise = setupOptions.aot ?
                    platformServer(extraProviders).bootstrapModuleFactory(<NgModuleFactory<{}>>moduleFactory) :
                    platformDynamicServer(extraProviders).bootstrapModule(<Type<{}>>moduleFactory);

                moduleRefPromise.then((moduleRef: NgModuleRef<{}>) => {
                    moduleR = moduleRef;
                    handleModuleRef(moduleR, callback, options.req, options.res);
                });
            } else {
                handleModuleRef(moduleR, callback, options.req, options.res);
            }

где moduleR прокидывается через замыкание из внешней функции. Убрали moduleRef.destroy() внутри handleModuleRef.
Но теперь при каждом последующем запросе отдаётся результат самого первого. Можно ли как-то "дестабилизировать" приложение или пнуть его с другим исходным url'ом? Или как нужно правильно реализовать кэширование уже существующего модуля? (на крайняк придется для тяжелых запросов поднимать локальный кэш-прокси к апи).
  • Вопрос задан
  • 399 просмотров
Решения вопроса 1
dasha_programmist
@dasha_programmist Автор вопроса
ex Software Engineer at Reddit TS/React/GraphQL/Go
Копание и отладка помогли прийти к такому коду handleModuleRef
function handleModuleRef(moduleRef: NgModuleRef<{}>, callback: Function, req, res) {
    const state = moduleRef.injector.get(PlatformState);
    const appRef = moduleRef.injector.get(ApplicationRef);
    const router = appRef.components[0].instance.router;
    const zone = appRef.components[0].instance.zone;
    zone.run(() => {
        router.navigateByUrl(req.originalUrl);
    });
    appRef.isStable
        .filter((isStable: boolean) => isStable)
        .first()
        .subscribe((stable) => {
            const bootstrap = moduleRef.instance['ngOnBootstrap'];
            bootstrap && bootstrap();

            if (!res || !res.finished) callback(null, state.renderToString());
        });
}

В главный компонент инжектим Router и NgZone чтобы потом можно было до них достучаться.
Мы зашли в метод handleModuleRef и если с первым запросом всё понятно - приложение нестабильно (isStable=false), то с последующими было неясно как его дестабилизировать чтобы запустить цикл ChangeDetection. Как мы знаем ChangeDetection работает сверху вниз по иерархии то есть от главного компонента на все низлежащие, то есть если у нас в приложении N компонент, то сложность такой операции O(N). Таким образом мы можем дестабилизировать приложение напрямую сообщив новый URL для роутера который мы получили из главного компонента. Фокус заключается в том, что isStable - это по сути свойство зоны NgZone (которая одна на всё приложение) и нам нужно дестабилизировать её. Поэтому код роутера следует запускать внутри zone.run(...). Приложение хавает новый маршрут, зоны дестабилизируется и мы ждем пока она станет стабильной (то есть в очереди внутри зоны не будет ни одной таски). Рендерим. Профит!
Последствия:
1) аналогичные браузеру - со временем течет память, спасает то что запущено всё в докере и всё само поднимается;
2) постоянный и не совсем предсказуемый state всего приложения.
Скорость рендеринга ускорилась в среднем 2-3-4 раза на разных компонентах по своему.
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 1
ozknemoy
@ozknemoy
яваскриптист
посмотри тут https://github.com/ozknemoy/a4 смысл в том что в хедер html сохраняется глобальная переменная(она набивается запросами к бд, все автоматом), потом она вычитывается сервисом на фронте. тоже используется ngExpressEngine. с тех пор когда еще не запилили его в отдельный модуль
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы