Чесний realtime на React та Redux, як основа автоаукціону

Для тих, хто не знає, CarPrice створив аукціонний сервіс з продажу приватними особами своїх автодилерів. Власник авто приїжджає до однієї з наших численних філій, наш співробітник проводить інспекцію його машини, складає картку, що виставляється на дилерський аукціон. За цей автомобіль протягом півгодини торгуються дилери, поступово підвищуючи ціну. Після завершення аукціону власник вирішує, чи влаштовує його запропонована сума, і чи їде з грошима, чи спокійно відмовляється від продажу.
Нещодавно ми зарелізували нову версію сайту, де проходять аукціони для дилерів. Користувач може переглядати пропозиції, одночасно торгуватися за кілька автомобілів, роблячи ставки в різних лотах, ніби переміщаючись між ігровими столами, тому всередині нашої компанії за цим проектом міцно закріпилася неофіційна назва Poker Stars.
Що змінилося у новому релізі?
Раніше все було розкидано по окремих вкладках, користувачам доводилося постійно переходити між ними. Тепер це повноцінний односторінковий веб-додаток, всі блоки інформації розміщуються на одному екрані.
При натисканні на будь-який аукціон відкривається картка автомобіля:





Також ми використовуємо кілька сторонніх сервісів та баз даних, які допомагають дізнатися, чи використовувалася машина в таксі, який її реальний пробіг, та іншу інформацію, яка допоможе прийняти рішення при покупці.
Розкажи докладніше про технічний бік проекту.
Це веб-додаток, написаний на React/Redux і відповідний парадигмі цієї технології: він складається з окремих функціональних компонентів. Навіть інтерфейс користувача збирається зокремих карток-компонентів. По суті, кожна картка - це окремий додаток, що працює сам собою. Його можна безболісно витягти та вставити в інше місце загального застосування. Тобто щось на кшталт мікросервісної архітектури, тільки в клієнтській частині і на рівні інтерфейсу користувача.
Все це працює абсолютно незалежно від серверної частини. Тобто аукціонний додаток складається із статичних файлів, які збираються WebPack'ом і потім лунають Nginx.
Яка схема взаємодії фронтенду та бекенду програми? Які модулі з якими системами спілкуються, які дані передають?
Ми використовуємо комплексний підхід, у додатку ми маємо і спілкування по HTTP REST API, і дуплексне real time-спілкування по WebSocket (pub/sub шаблон). Це зумовлено як історичними факторами (додаток працював так спочатку), так і функціональними вимогами (у нас є місця, де real time та передача подій із сервера не потрібна, і навпаки). Нам дуже важливо було реалізувати саме чесний real-time, оскільки це аукціон, і ми бувають інтенсивні торги, особливо на останніх хвилинах. Ми відмовилися від схем, коли оновлення передаються з сервера раз у якийсь час (наприклад, у секунду, як це часто роблять у мессенжерах), у нас клієнт отримує оновлення з сервера так швидко, як це можливо. Дуже важливо було зберегти зручність інтерфейсу: коли за автомобіль починається активна торгівля, події летять дуже швидко і користувач не встигає вводити нову ставку вручну. Поля і кнопки (у нас є можливість торгів з різних місць у додатку, і вид ставки буває різний) самі оновлюють інформацію при отриманні подій з сервера, або, наприклад, можуть бути неактивними, якщо дилер не має права продовжувати далі торги. Це суттєвоспрощує роботу з інтерфейсом, але було не таким простим у реалізації, тому що за таких сценаріїв пов'язаність компонентів сильно зростає.
Розкажи докладніше про інструментарій, що використовується при розробці.
Ми свідомо відмовилися від «велосипедів» на користь найпопулярніших рішень для веб-застосунків, які зараз є на ринку. Як фреймворк, ідеологія та основа використовується React/Redux. Проект збирається за допомогою Webpack, ми використовуємо Yarn як менеджер залежностей, NodeJS та Express для dev/mock-сервера. У production статику роздає докеризований Nginx.
Не можна сказати, що цей стек абсолютно позбавлений недоліків, але без переваг, які він дає з коробки (реактивність, гнучкий дебаггінг, хороша інкапсуляція та перевикористання компонентів), важко побудувати додаток, який відповідає сучасним стандартам та вимогам (багатий інтерактивний інтерфейс з великою кількістю інформації на одному екрані та можливістю real-time оновлень).
Чи використовувалися компоненти попередньої програми, чи нову було створено з нуля?
Спочатку у нас був один великий монолітний додаток, ми поступово переписуємо з нього критичні модулі, що сильно розростаються. Зараз у production працюють і монолітний додаток, і нові сервіси та фронтенди у зв'язці. Що стосується інтерфейсу, то він сильно змінюється в процесі редизайну, і взаємодія старих і нових сторінок завжди складне завдання, що кожного разу вимагає унікального рішення.
А чи були «відсіяні» якісь технології чи інструменти, які були визнані непридатними для використання у додатку?
Наша архітектура дозволяє гнучко працювати з різними інструментами, не порушуючи головних принципів застосування. Наприклад, миекспериментували з CSS-модулями та БЕМ-найменуванням CSS-класів з використанням LESS. В результаті вибрали перше як більш зручний варіант. Те саме було з реалізацією гарячого перезавантаження на dev-сервері, технологія ще не до кінця устояла, і ми пробували кілька варіантів реалізації. Що було найважчим у реалізації цієї програми?
Додаток імплементував досить багато поведінкової логіки, часто нетривіальної. Наше завдання було грамотно розкласти її за модулями, передбачити всі взаємодії з користувачем, продумати всі сценарії використання. Коли всі компоненти сильно пов'язані між собою і дуже активно взаємодіють один з одним, важливо передбачити, до чого проводить кожну дію користувача (або інших користувачів), який впливає на програму. У клієнтській частині в цьому нам дуже допомогла Redux-архітектура. Наш аукціон - це real time-екосистема, і важливо було, щоб ті події, які прилітають від інших користувачів, не вступали в конфлікт із тими, що генерує сам користувач.
Наскільки я зрозумів, головна перевага нової програми — його модульний інтерфейс. Ти міг би розповісти докладніше про його розробку?
Сьогодні існує два популярні підходи до поділу програми на модулі:
У чому відмінність контейнера від компонента, чому ви проігнорували цей патерн?
Контейнер - це компонент, який працює зі stor'ом безпосередньо, робить запити на сервер і так далі. А просто компонент вміє лише приймати дані від батька та рендеруватись у певне місце. Часто їх поділяють за різними папками на рівні програми. Нам такий підхід видався надлишковим, я думаю, плоский список компонентів — це те, до чого йшла вся фронтенд-розробка останні п'ять років.Розробник сам розуміє, що компонент ускладнюється (стає контейнером), і це видно при відкритті вхідної точки компонента, тому що для цього потрібно імплементувати кілька методів Redux'а.
Я чув, що Redux використовує практики функціонального програмування для побудови програми, чи не так? А як же ООП?
Так, Redux писався під впливом функціональних мов, підходів та ідей. Наприклад, всі reducer'и – це чисті функції, а store – це незмінний об'єкт. Всі набори action'ів та reducer'ів — просто функції, які ми імпортуємо у вхідні точки компонента, жодних класів, інстансів практично немає (тільки в React'і та самописних модулях). ООП-підходи добре справляються з логікою, що постійно розбухає, але в даному випадку вдається зберегти додаток компактним, незважаючи на те, що він несе багато функціональності.
Перевикористання компонентів та ізоляція – одна з головних переваг такого підходу.
Який інструментарій використовується у роботі?
Redux DevTools та React Developer Tools додаються до Chrome до стандартних інструментів розробників. Перший — дуже потужний інструмент налагодження, що дозволяє переглядати store, відкочувати action'и і, відповідно, те, що робилося в додатку за часом (він так і називається — машина часу). Другий дозволяє працювати з XML-подібним деревом React-компонентів, схожим на те, що ми пишемо в JSX, а не зі звичним DOM'ом. Це дозволяє оперувати більшими частинами програми, ніж прості HTML-елементи. Також ми використовуємо ESLint зі стандартним, трохи зміненим AirBnb-конфігом, щоб привести код різних розробників приблизно до однакового вигляду.
Який шлях проходить додаток від розробки якоїсь нової функціональності до її релізу користувачам?
Розробник розгортає додаток у себе локально, запускає Webpack Dev-сервер та сервер з моками на різних портах (або налаштовує конфіги на production або staging-оточення) і починає роботу. Після проходження код-ревью, тестувальник у CI збирає собі Docker-контейнер із потрібною гілкою, та перевіряє. Далі запускається Drone (про нього ми скоро розповімо в окремому пості), CI збирає контейнер для production і запускає його на бойовому сервері. Надалі ми плануємо збирати один контейнер і для тестування, і для розгортання.
Як за останні 2-3 роки змінився підхід до розробки фронтенду та бекенду? Які ідеї/концепції, які раніше вважалися нормальною практикою, ти сьогодні оцінюєш як застарілі, і що прийшло на їхнє місце? Які методики ви взяли на озброєння, і, можливо, використовували при створенні цього додатка?
Я охопив би більший період. За останні 4-6 років вимоги до інтерфейсів в Інтернеті серйозно зросли, в більшості нових складних продуктів основну версію роблять для браузера, частина старих теж мігрувала. Програми стали значно складнішими, і монолітна архітектура, яка раніше превалювала, поступається місцем мікросервісам на бекенді та компонентам на фронтенді. Бекенд і фронтенд у типовому великому проекті спочатку відокремилися один від одного, зменшуючи складність та ентропію, а потім почали ділити зони відповідальності в собі. Якщо раніше, зайшовши навіть на популярний відвідуваний сайт, можна було зрозуміти, що всередині працює простий малий HTML-шаблонізатор, база даних, веб-сервер і трохи логіки, що все це пов'язує, то зараз «під капотом» найчастіше виявляється система з численних компонентів , з хитрою системою спілкування та стеком різних технологій.
І останнє питання. Купівля автомобіля - цеДосить відповідальний крок навіть для людей, які займаються цим професійно. Як дилери відреагували на новий інтерфейс?
Навіть вдалий редизайн — це завжди певною мірою біль для користувачів, який звикли до звичних патернів поведінки, навіть якщо їх можна сильно спростити і в них є баги. Ми поступово впроваджували нові сторінки і групами підключали користувачів до інтерфейсу, що оновився. У нас є система зворотного зв'язку, і наші менеджери розбирали побажання та відгуки користувачів. Вважаємо, що досить успішно впоралися із цим непростим завданням.