Одной из основных задач программы ЕФС является трансформация всех фронтальных систем к единому технологическому стеку. Фронтальная система в нашем контексте это интерфейс, через который любой пользователь взаимодействует с банком. Это может быть интернет-банк — многим известны приложения Сбербанк Онлайн и Сбербанк Онлайн для бизнеса, — банкоматы, терминалы, интерфейсы операторов в отделениях и call-центрах и другие системы, которыми пользуются многие тысячи клиентов и сотрудников банка в России.
Основная проблема, которую мы решаем, это замена устаревшего кода: фронтальных систем в банке много, у каждого своя архитектура, свой дизайн.
Очевидно, что это неудобно для всех: для клиентов, сотрудников и банка в целом.
Поэтому главная миссия фронтенда ЕФС – заменить существующую сборную солянку и привести все к единой кодовой базе, к единому технологическому стеку с удобным и понятным пользовательским сценарием.
Какие задачи стоят перед разработкой?
- Надежность и безопасность, ведь мы говорим о банковском ПО.
- Отказоустойчивость. Здесь фигурирует такая цифра, как 99,99%, это примерно 52 минуты в год, когда мы можем приостановить работу системы.
- Удобство для разных социально-демографических категорий пользователей. Интерфейс должен быть понятен как продвинутому пользователю, так и бабушке, как опытному сотруднику банка, так и новичку. Добавим, что единообразие и стандартизация позволяют нам сэкономить значительное время на обучении многих тысяч сотрудников по всей России.
- Быстрота работы и внедрения, так называемый time-to-market.
До эры ЕФС время вывода нового технического продукта на рынок могло составлять до 1 года. С ЕФС мы ставим себе задачу сократить time-to-market до 1 месяца.
И это только начало!
Как устроен фронтенд в ЕФС?
У нас есть команда разработчиков платформы ЕФС, а также прикладные разработчики, задача которых – реализовать бизнес-логику.
Команда платформы разрабатывает библиотеку UI-компонентов для внутреннего использования. Примеров подобной разработки довольно много — у таких компаний, как Google, Yandex, Avito, Mail.ru и др. также есть библиотеки компонентов. Команды же прикладных проектов используют эту библиотеку для реализации своих проектов, предоставляя фидбек в случае проблем.
В команде платформы сейчас 8 человек. Мы работаем двухнедельными спринтами, в конце каждого из них выпускаем новую версию библиотеки, в которой содержатся фиксы и, возможно, новые компоненты. У нас, разумеется, есть code review, свой code style – мы взяли лучшие практики программирования и адаптировали их под себя.
В качестве инструментария мы используем набор инструментов от компании Atlassian: JIRA для постановки задач, BitBucket для git-репозиториев и Confluence для документации.
Из чего состоит библиотека?
- Различные UI-элементы: всевозможные селекты, кнопки, большой набор полей с различными типами данных, чекбоксы, радиокнопки и другие элементы формы, с которыми взаимодействует пользователь.
- Способы вывода информации, например, таблицы или панели с кнопками и заголовками
- Сетка. Мы посмотрели, как ее реализовал Bootstrap, а затем создали подобное для своих целей. По нашей сетке компоненты строятся на формах.
- Компонент просмотра вложений. Служит для того чтобы оператор мог взаимодействовать с приложенным файлом, например, приблизить или повернуть квитанцию.
- Компонент загрузки файлов
- Компонент для асинхронной загрузки React-приложений: бизнес-процессов много, мы разбили их на отдельные React-приложения, которые подгружаются при переходах из одного процесса в другой.
Поддержка браузеров
Целевыми браузерами являются IE8+. Сейчас IE8, если кто-то помнит, это как в свое время был IE6: ужасное API и ужасная отладка. Конечно, время, проведенное за дебагом в IE8, бесценно. Были случаи, когда разработчики проводили несколько дней в попытках найти, в каком месте возникала ошибка, потому что в IE8 очень скудный инструментарий для дебага и он показывает порой, что ошибка возникла совсем в другом месте.
Поддержка IE не случайна, нам приходится работать с железом из браузера: RFID-таблетки, различные принтеры, сканеры и т.д. В вебе нет единого стандарта по работе с железом: в далеком прошлом технологией для работы с ним был выбран ActiveX. Количество ПО, написанного с использованием ActiveX, колоссально, и это не дает нам в одночасье отказаться от поддержки IE и перейти в сторону современных браузеров. В планах — перевод устаревшего ActiveX на Java-апплеты и отказ от IE8.
Стек технологий
Мы своего рода стартап внутри крупной организации и наш стек технологий фронтенда не сильно отличается от большинства мировых стартапов: react, redux и PostCSS. Все эти технологии зарекомендовали себя с лучшей стороны, к тому же, они позволяют нам поддерживать IE8. Однако, мы не можем резко менять стек технологий, т.к. вокруг него завязана определенная архитектура приложений, например, именно React позволил нам разбить одно огромное приложение на сотни маленьких и подгружать их по требованию, используя SystemJS.
React
Это первая технология, которую мы выбрали по следующим причинам:
- Позволяет разбить приложение на компоненты, тем самым организовав компонентный подход к разработке.
- Легок в изучении — мы не требуем от нового коллеги, чтобы он знал React, мы просто даем ему пару дней разобраться в проекте.
- Поддержка IE8. С этого, на самом деле, можно было начинать, т.к., если посмотреть, какие фрэймворки и библиотеки поддерживают IE8, окажется, что таких крайне мало. Самые популярные — Angular, Ember, Vue.js — его не поддерживают, поэтому с React нам повезло.
Также в процессе разработки мы начали экспериментировать с React Native, что позволило нам выпустить кросс-платформенную библиотеку UI-компонентов. Прочитать об этом вы сможете в нашей первой статье.
Как мы подружили React и IE8 ?
Во-первых, мы не обновляем версию React, потому что с какого-то момента они тоже отказались от поддержки IE8. Во-вторых, мы используем es3ify — это loader для webpack, который берет наш ES5 код и перегоняет в ES3. Он просто заменяет некоторые вещи, которые в IE8 не работают.
TypeScript
Второй технологией, которую мы выбрали, был TypeScript, вот почему:
- Строгая типизация
По нашим стандартам тип any запрещен, однако, бывают исключения из правил, т.к. далеко не всё можно покрыть generic’ами.
- Защита от дурака
Все мы люди и иногда используем не те типы, передаем не те данные, и компилятор выводит простые и понятные ошибки.
- Удобство работы с прикладными разработчиками
Как описано выше, мы передаем свою библиотеку прикладным разработчикам. Многие из них также используют TypeScript, и на этапе компиляции им понятно, как правильно работать с компонентами.
Сейчас в библиотеке порядка 70 компонентов. Когда мы только начинали разработку, у нас все компоненты были «умные», то есть содержали state и вся логика была внутри. Особых проблем мы не испытывали. Но, как только компоненты стали сложнее, они начали вкладываться друг в друга, их стало много, мы поняли, что все-таки у нас есть проблемы с производительностью, расскажем, как мы их решали.
Производительность
Во-первых, мы сделали компоненты «глупыми», то есть избавились от state, вынося его на прикладной уровень. Теперь прикладные разработчики решают, как менять state, а в наши компоненты только пробрасываются нужные props.
Возьмем пример:
export default class Input extends React.Component<Props, State> {
static defaultProps: Props = {
value: ''
}
state: State = {
value: this.props.value
}
handleChange = event => {
const value = event.nativeEvent.target.value;
this.setState(prevState => ({value}))
this.props.onChange(value);
};
render() {
return <input onChange={this.handleChange}
value={this.state.value} />;
}
}
Есть библиотечный компонент Input, у него в state хранится value, в render он возвращает input и в onChange он меняет state. Не много ли кода для такого компонента? Однозначно много, давайте отрефакторим этот пример:
export default class Input extends React.Component<Props, {}> {
static defaultProps: Props = {
value: ''
}
handleChange = event =>
this.props.onChange(event.nativeEvent.target.value);
render() {
return <input onChange={this.handleChange}
value={this.props.value} />;
}
}
Код компонента стал короче, а на уровне выше есть компонент Form, который сам решает, как управлять состоянием компонента: через redux или через простейший setState. Input стал проще, и, соответственно, производительнее.
Второе, мы придерживаемся архитектуры чистых компонентов (PureComponent), т.е. все внешние свойства, внутренний state и контекст проходят проверку соответствия предыдущему состояния. Если состояние не изменилось, то нет смысла вызывать render лишний раз. Эту проверку мы осуществляем в методе shouldComponentUpdate, который добавили во все наши компоненты.
И третье, мы избавились от утечек памяти в коллбеках.
export default class Button extends React.Component<Props, {}> {
render() {
return (
<button onClick={event => this.props.onClick(event)}>
{this.props.children}
</button>
);
}
}
В данном примере у компонента есть коллбек onClick и в него передается стрелочная функция.
Если ее так задать, то здесь возникает утечка. В IE8 ее особенно видно, потому что при каждом повторном вызове render эта функция создается, она накапливается и возникают тормоза в компоненте. Немного изменим наш пример:
export default class Button extends React.Component<Props, {}> {
handleClick = event => this.props.onClick(event);
render() {
return (
<button onClick={this.handleClick}>
{this.props.children}
</button>
);
}
}
Сам код стал лаконичнее и к тому же, мы избавились от утечки, поскольку callback-функция больше не создается при каждом вызове render.
В планах на будущее — прекращение поддержки IЕ8, что позволит использовать более прогрессивные фронтенд-технологии. Кроме того, мы уже приступили к работе над масштабным проектом интеграции с мобильной платформой ЕФС, приступили к разработке гибридной библиотеки, позволяющая один и тот же код использовать и для web, и для мобильных устройств, используя React Native.
В следующий статье про фронтенд программы ЕФС мы расскажем про то, как мы используем Redux и как он стал сердцем нашей архитектуры, подписывайтесь на наш блог, чтобы не пропустить!
Идеи, предложения и пожелания – пишите, будем рады пообщаться с вами в комментариях к статье.
Комментарии (20)
unsafePtr
08.04.2017 00:10Если состояние не изменилось, то нет смысла вызывать render лишний раз. Эту проверку мы осуществляем в методе shouldComponentUpdate, который добавили во все наши компоненты.
Метод shouldComponentUpdate ведь вызывается неявно, или я что то путаю. Тогда зачем вы добавили его во все методы?Luanre
10.04.2017 11:41Неявно он вызывается только у компонентов, наследующихся от класса React.PureComponent, который появился в версии 15.3.0
vintage
08.04.2017 11:34Компонент, для показа и ввода даты у вас тоже стейтлесс и требует для своей работы следующие пропсы?
value : string currentFormattedString : string selectionPosition : [ number , number ] onValueChange : ( value : string )=> void onYearChange : ( year : number )=> void onMonthChange : ( month : Month )=> void onResetToCurrentDate : ()=> void
olegshilov
08.04.2017 21:06меня сломала фраза
По нашей сетке компоненты строятся на формах
не поясните?Luanre
10.04.2017 15:17стандартный подход к раскладке форм по Grid'у: задаются колонки, строки, внутри них вставляются компоненты
PFight77
09.04.2017 08:29Очень интересно, у нас похожий стек технологий, и тоже движемся к созданию внутренней библиотеки компонент. Вот только делать все компоненты полностью stateless не всегда разумно, т.к. порой это приводит к дублированию кода в бизнес-логике. Redux вовсе необязательно знать, открыта менюшка или нет...
Luanre
10.04.2017 15:17Согласен, об этом я писал выше: главное подходить к stateless-компонентам без фанатизма
PQR
10.04.2017 16:12В планах — перевод устаревшего ActiveX на Java-апплеты
а Java-апплеты не устаревшая технология?Luanre
11.04.2017 16:18в отсутствии ActiveX это пока единственный способ подружить браузер с железом, если есть предложения, как это сделать, будем рады услышать
Co0l3r
12.04.2017 01:35Electron/NW.js — ЭТО единственный нормальный способ подружить браузер с железом, это если речь идет о терминале или интерфейсе для внутреннего использования. А если речь идёт о веб-приложении для пользователей, то не нужно требовать их устанавливать/запускать какие-то убогие плагины.
scramble
То, что вы называете утечкой, больше похоже на выделение мелких мусорных объектов. Жаль, если сборщик мусора не может с ними справиться. Фрагментация хипа всякой мелочью — тоже не особо хорошо. Вы правильно сделали, что заменили создание функции на лету на передачу ссылки на метод инстанса.
По возможности метод render() не должен выделять память в результате своей работы.
Luanre
В идеальном мире мы всегда рассчитываем, что GC сделает свою работу, однако, это не всегда правда. Надо помогать ему в этом :)
Miraage
Тут даже дело не в утечке, которая маловероятна. React увидит, что изменился аттрибут onClick и переназначит обработчик. Лишняя ненужная работа.
А если так передавать коллбэки не в DOM элементы, а в дочерние компоненты — у них будет еще бонусом холостой render вызываться.