Архитектура приложения
Wasaby / Изоморфное приложение. Application
Для того, чтобы приложение SSR (server-side rendering)
было проще поддерживать и быстрее создавать, необходимо, чтобы было как можно больше общего кода и меньше кода, который работает только лишь на сервере или на клиенте.
Основные причины в написании раздельного кода это то, что на сервере нет браузерного API
и нельзя использовать глобальные объекты-одиночки(singleton
). Так как построение страниц на сервере происходит в одном и том же процессе асинхронно, и несколько построений могут пересекаться по времени и использовать одни и те же глобальные объекты. И, соответственно, данные одной страницы могут затереть данные для другой страницы.
Чтобы разработка была так же удобной в браузере, где всегда строится только лишь одна страница, представлено API
из глобальных одиночек, которые знают о возможном асинхронном построении страниц на сервере.
Окружение. Application/Env
Окружение — это набор модулей (свойства окружения) для изоморфной работы с браузерным API
, которое доступно и на сервер. А также модули для хранения данных в рамках запроса построения.
Cookie
—Application/Env.ICookie
Location
— аналогwindow.location
- Логгером —
Application/Env.IConsole
Store
—Application/Env.IStore
StateReciever
—Application/Env.IStateReceiver
Как работать с окружением
require(['Application/Env'], ({ logger, cookie, location, getStore }) => {
logger.info('myCookie: ', cookie.get('myCookie'));
logger.info('hostname: ', location.hostname);
const myStore = getStore('myStore');
myStore.set('someKey', 'someValue');
logger.info('some store Key: ', myStore.get('someKey'));
});
Store
Это такие объекты, где можно потоко-безопасно хранить данные между асинхронными вызовами методов контрола (в том числе и контрола). Проблема с асинхронными вызовами функции встречается при построении страницы на сервере, где несколько запросов на построение могут выполняться асинхронно/параллельно в одной и той же, глобальной области видимости.
Для работы со Store
нужно всегда создавать контрол-одиночку обертку, где инициализируется начальное значение. Чтобы всегда была точка контроля изменения значений определенного хранилища.
/**
* NB! Со Store всегда работаем через свой синглтон.
*/
define('MySingletone', ['Application/Env', 'Types/entity'], (Env, Entity) => {
var cacheStore = Entity.Record();
const STORE_KEY = 'MySingletone';
// Инициализируем хранилище
Env.setStore(STORE_KEY, cacheStore);
// NB! Наружу отдаем объект, который всегда работает с данными через Env::getStore
return {
read(key) {
return Env.getStore(STORE_KEY).get(key);
},
write(key, value) {
return Env.getStore(STORE_KEY).set(key, value);
}
}
});
require(['MySingletone'], (single) => {
return function run() {
const ourValue = Math.random();
single.write("key", ourValue);
setTimeout(() => {
// Тут мы уверены, что в асинхронной операции получим ourValue.
console.log("store value:", single.read("key"));
}, 1000);
}
});
StateReciever: Восстановление состояния объектов построенных на сервере
Для восстановления состояния модулей используется объект StateReciever
, который берёт на себя ответственность о сериализации данных и размещении их в теле ответа на сервере и восстановления его на клиенте.
Момент сохранения состояния происходит в конце построения страницы, а восстановление в момент регистрации модуля.
Для того чтобы контрол мог восстанавливать свое состояние, он должен поддерживать интерфейс ISerializableState
и регистрироваться в StateReciever
.
Пример восстановления состояния в браузере для не визуальных модулей:
require(['Application/Env', 'Core'], ({ getStateReceiver }) => {
class MyComponent {
data = {};
getState() {
return this.data;
}
setState(data) {
this.data = data;
}
}
const component = new MyComponent();
// Регистрируем контрол, для восстановление состояния с сервера.
getStateReceiver().register("uuid", component);
console.log('data from server: ', component.getState().someKey); // "someValue"
});
Настройки приложения. Application/Config
Для получения системных настроек приложения используется модуль Application/Config
.
require(['Application/Config', 'Core'], (config) => {
console.log('LogLevel: ', config.get('Application/Env.LogLevel'));
});
<!-- [Список стандартных системных настроек приложения](https://online.sbis.ru/doc/3e7cd71c-3ed9-480a-a7a5-a42b4fd8e145). -->Wasaby / Продвинутое использование
Инициализация окружения. Application/Initializer
Для инициализации одиночек используется вызов метода init
из модуля Application/Initializer
. После синхронного вызова метода init
можно использовать все вызовы из области Application/*
.
require(['Application/Initializer', 'Application/Config'], ({ default: init }, config) => {
const error = 2;
const initConfigValue = {
'Application/Env.LogLevel': error
};
init(initConfigValue);
console.log('LogLevel: ', config.get('Application/Env.LogLevel'));
});
Фабрики окружения
При создании окружения используется фабрика, которая возвращает объекты, каждый из которых ответственен за API
каждого из свойства окружения. По умолчанию в браузере используется Application/Env.EnvBrowser
.
На сервисе представления свойства создает фабрика SbisEnv/PresentationService
.
require(['Application/Initializer', 'SbisEnv/PresentationService'],
({ default: init }, { default: PresentationService }) => {
const initConfigValue = {};
let environmentFactory;
if (typeof window === 'undefined') {
environmentFactory = PresentationService;
}
init(initConfigValue, environmentFactory);
});
Передача данных с сервера на клиент
Для передачи состояния модулей с сервера на клиент, необходимо их сериализовать и разместить в теле страницы, а затем в момент инициализации передать их в StateReceiver.deserialize(data)
.
// Значение пришедшее с сервера с состоянием контролов.
const serverValue = '{ \
"uuid": { "someKey": "someValue" } \
}';
define('Core', ['Application/Initializer', 'Application/Env'], ({ default: init }, { StateReceiver }) => {
const serverState = new StateReceiver();
serverState.deserialize(serverValue);
// Инициализируем приложение с состоянием с сервера.
init({}, undefined, serverState);
});
Настройки приложения. Application/Config
При инициализации приложения первым аргументом передается объект, который является настройками приложения и доступен через объект Application/Config
.
Для того, чтобы настройки приложения в любой точке времени были одинаковые, и не было проблем гонок состояния конфигурации, её можно изменять только целиком, и только на двух этапах жизненного цикла:
- при инициализации приложения — указываются настройки по умолчанию;
- при восстановлении конфигурации из
StateReciever
.