Про що стаття?
Дисклеймер: основна частина цієї статті була написана ще у лютому 2023, але часу та натхнення довести її до кінця не вистачало. Забігаючии наперед скажу, що розробляв це добро наприкінці січня, з тих пір вийшло кілька десятків оновлень і зараз застосунок вже зовсім не такий, як тоді. Я знайшов старі скріншоти для автентичності, тож не дивуйтеся, що зараз все трохи інакше.
Якщо відкинути занудні скарги автора на недостатньо зелену траву та вже не такий смачний смузі, залишиться кілька слів про те, як дуже швидко зробити продуктивний веб-застосунок для проекту вихідного дня. Бажано красивий, зручний та приємний у підтримці. Приклад реальний, код у відкритому доступі, можна поклацати, та й взагалі все як у людей.
Завдання було дуже просте: створити зручний розклад київської міської електрички, яким можна легко скористатися навіть з мобільного браузеру. Знаю, не звучить як виклик для початку 2023:) Проте, ситуація на той момент була не дуже: цю потребу закривало приблизно 0 пошукових результатів з топ-10. На сайті Укрзалізниці взагалі відкривалася неправильна сторінка, на другому – WordPress та вставка з Google Sheets, що очевидно не дружить з мобільним. Якщо запастися бажанням, можна було отримати що треба з EasyWay, що йде третім. Він хороший, але коли мова йде про GPS трекінг, пошук розкладу потребує трошки терпіння та винахідливості. На додачу, розклад там оновлювався з великою затримкою, що інколи вводило користувачів в оману. Далі за результатами – взагалі без коментарів.
Електрички курсують регулярно, але не надто часто, а отже просто приходити на станцію та чекати потяга "як у метро" теж не найкраща ідея. Тож, після чергової сесії знущання зі здорового глузду у десятиградусний мороз, я вирішив зробити щось хороше для міста, людей, та й самого себе.
Оскільки на момент написання статті все вже давно зроблено та працює, лишу тут посилання на все те, про що йтиме мова.
- розклад міської електрички для людей
- REST API для тих, хто хоче зробити щось своє
- telegram-канал спільноти
- код проекту
- невеличкий блог розкладу з моїми короткими замітками із самого початку до нашого часу:)
First things first
Ідея зробити з наскоку ще одну незрозумілу таблицю одразу відправилася до кошика, людський значить людський! Все як у книжках: аналіз наявних рішень, опитування потенційних користувачів (звісно, friends, family, and fools), виділення ключових потреб. Виявилося, що в першу чергу наявних користувачів цього транспорту цікавить:
- о котрій відправляється наступний потяг із моєї станції?
- якщо не встигаю, коли чекати наступний?
- а як щодо відправлення завтра по обіді?
Є ще група тих, хто не знає, як цим всім користуватися і куди можна потрапити, тут все складніше:
- куди я взагалі можу доїхати?
- як довго це триватиме?
- де шукати та як платити за проїзд?
На щастя, другий набір питань розкривається у результатах пошуку трохи краще, тож вирішив зосередитися на тому, що дійсно заважає зручному користуванню. Ось перший прототип дизайну головної частини, швиденько накиданий у Figma:
Показуємо людям, виправляємо, знову показуємо, знову виправляємо – нарешті виходить щось схоже на приємний інструмент. Залишається заспокоїтися, пожити з ідеєю ще кілька днів, а там можна і за роботу.
Структура та інструменти
У сучасному світі веб розробки просто неймовірна кількість крутих інструментів – від одного лише різноманіття бібліотек та фреймворків на фронт-енді розбігаються очі. І все це добре, весело та драйвово, поки застосовується з розумом. Але є одне але.
Коли у тебе в руках лише молоток, усі проблеми здаються цвяхами @ Абрахам Маслоу
Постійне надмірне ускладнення всього підряд вже нагадує глобальну хворобу індустрії. Інструменти застосовуються не для розв'язання проблем чи полегшення життя, ні – просто бо так цікаво, модерново і взагалі - всі так роблять (not to say React). Та й хайпову галочку у CV можна поставити.
До чого це я? Завдання тут просте: написати веб-застосунок на кілька сторінок, щоб у ньому був красивеньикий майже статичний фронт, помірно швидкий бек, СЕО штучки, аналіз відвідуваності та невеличка інтеграція зі стороннім API. Що спадає на думку пересічному сучасному™ розробнику?
Ага, добре, значить тут ми візьмемо React, втягнемо Redux, Axios, ще 125 залежностей... Мало TypeScript не забули! Згрібемо це щастя вебпаком, мініфікуємо... О, ще ж SEO – най буде NextJS для SSR Такс, значить REST API, або ні - GraphQL чи навіть gRPC. Сюди Django REST (чи хоч Flask, але ж у мене такий милий шаблон на Джанго). FastAPI та Starlette в утиль – а раптом доведеться реалізувати приготування кави, то ж все руками робити... Все у кубер, про всяк прикрутити кешування, якусь базу (навіть якщо вона не потрібна, обовʼязково концептуально непідходящу, аби не напрягатися – наприклад, Mongo для реляційки).
І так далі, і тому подібне. Звісно, це все може бути дуже актуальним для якогось величезного проекту зі світлим майбутнім, але тут таке слоненятко явно недоречне, чи не так?
- Це буде просто важко підтримувати. Бо запускати локально для одного фікса довго, відлагоджувати складно, і взагалі наявні всі передумови для "а, байдуже, загалом працює". Розгортати на сервері теж непросто, купу рухомих частин ніхто не скасовував
- Все це швидко застаріватиме без постійної підтримки, а я ще не зʼїхав з глузду, щоб пожиттєво бампити реакт у розкладі на три сторінки
- Воно буде працювати значно повільніше, а пропускна здатність без додаткових часовитрат буде значно нижчою. Дорожчий сервер, більше підтримки, кешування...
Коротше кажучи: ні, дякую.
Шлях самурая — один, це прямий шлях, який не допускає зволікання, і тому головне — діяти відразу. Ямамото Цунетомо
Отже, у нас все буде дуже просто:
- Сервер на FastAPI (Python), що відмальовує HTML сторінки з допомогою Jinja2
- Фронт-енд без JS взагалі, що вже й казати про фреймворки/ліби. Може колись зʼявиться ванільний JS, але не більше.
- Тільки векторна графіка. А колись бажано ще й одним спрайтом.
- Nginx у якості reverse proxy, Docker, Gunicorn з воркерами Uvicorn для підтримки ASGI
Так, святотатство - ніякого SPA, ніякого фронтового роутингу та брудних маніпуляцій з історією та window.location
. Треба зробити вкладки для розкладів буднього та вихідного? Не питання, маємо посилання та окремі сторінки:
-
/stations/{station}/weekday
-
/stations/{station}/weekend
Навіть класичний dropdown зі списком станцій це просто окрема сторінка, ще й логічна: /stations
. CSS теж чистий, нічого ускладнювати, коли його всього триста рядків.
Переваги такої збірки:
- Локальне середовище піднімається за кілька хвилин майже без складних залежностей
- FastAPI з таким сетапом спокійно обслуговує 200-300 запитів на секунду на дешевій машині, чого, скоріш за все, для такого проекту вистачить назавжди
- SSR, надшвидке завантаження та відмальновка сторінок браузером, переваги для SEO
- Вкрай низька загальна складність розробки, відлагодження та підтримки
Недоліки, звісно, теж ніхто не скасовував, це не срібна куля:
- Наростити це все до великої аппки зі сповіщеннями, особистими повідомленнями та персональними блогами кондукторів було б важко, React дійсно допомагає там, де треба реактивність (sic!)
- Обране інфраструктурне рішення з певного моменту може мати проблеми із масштабуванням. Щоправда, мова про абсолютно неможливі для цього застосунку масштаби
- Є ризик бути закиданим камінням на співбесіді:)
Тож прикинув на око актуальність недоліків та переваг у розрізі маленького розкладу електричок, зрадів очевидності вибору та пішов працювати.
Настав час кодити
Отримуємо дані
Для того, щоб відобразити розклад, його потрібно десь взяти. Наприклад, з офіційного сайту. Запасаємося BeautifulSoup
та httpx
і йдемо розкручувати купи старого-доброго HTML.
Якщо вже завʼязався з парсингом сайтів, робитиму це відповідально. Розклад міняється вкрай рідко, а отже, достатньо простенького декоратору, що кешуватиме результат роботи функції та оновлюватиме лише якщо кеш старший за годину. Таким чином ми гарантуємо, що запит на сайт УЗ відбуватиметься не частіше за 24 рази на добу, що нехтовно мало.
А от табличка на сайті виявилася дуже непростою. Деталі цієї війни зі хтонічною сторінкою можна побачити у data.py
в репозиторії, але, мабуть, краще жити без цієї інформації. Також прибив цвяхами розклад відправлень Лівий Берег -> Видубичі, на сайті Укрзалізниці його так і не знайшов. Там взагалі жах, розклад був розмазаний пʼятьма різними таблицями, не уявляю, як нормальна людина могла у цьому розібратися. Зараз їх стало трохи менше, але все ще дуже незручно.
Відмальовуємо сторінки
Тут усе просто, з отриманих та трансформованих даних доволі легко відмалювати Jinja2 шаблони для списку станцій, сторінок із розкладами та головною. До речі, фантазії на пристойну головну сторінку в мене чесно не вистачило, тому вона досі виглядає не дуже.
Питання розкладу у будні та вихідні вирішується додаванням path параметрів, список сторінок це взагалі неймовірно просто, головна майже статична. Забігаючи наперед, мушу сказати, що mobile-first себе повністю виправдав – 82.5% користувачів сайту відкривали його з мобільного. Колись написав досить детальний пост про перші 4 міясці роботи розкладу електрички, там трохи більше цікавого.
Майже одразу вирішив додати темну тему, все ж багато хто нею користується, а отже, людський розклад має її підтримувати. До того ж, виявилося, що у багатьох користувачів є примусовий нічний режим у браузері, який автоматично інвертує кольори за відсутності стилів до темної теми. І в такому разі сайт виглядає мінімально жахливо. Звісно, це потім кілька разів вилазило боком – я адепт світлої (навіть в IDE, сильно не бийте), тож при тестуванні нових версій інколи пропускав неприємні речі.
Паралельно з цим довелося внести кілька покращень для відображень піктограм, тож сайт запрацював на 0.000001% швидше, але то таке.
Розгортаємо
Вже на другий день усе було готове до запуску, тож настав час розгортати розклад на сервері. У мене вже був найдешевший дроплет на DigitalOcean для невеличких особистих проектів, тож вирішив не вигадувати велосипед. Для початку просто docker, uvicorn навіть без gunicorn, nginx reverse proxy та безкоштовний сертифікат від Let's Encrypt. Залив, запустив, потестив – ніби непогано, швидкість пристойна, все літає. Переключення між сторінками (список станцій, розклад за станцією, будні та вихідні) просто миттєвий.
І тут лінь взяла своє: я все ж вирішив, що мабуть і так зійде, gunicorn, балансування навантаження і все таке почекає на світле майбутнє. Щоб переконатися у цьому, потестував сайт під навантаженням, витримав близько 300 запитів на секунду. Навіть якщо весь Київ накинеться на міську електричку, як це було під час Майдану у 2014, має вистачити.
Публічний запуск
Добре, з технічною частиною розібралися, час запускати. Останній штрих – аналітика, цікаво ж подивитися, як воно там живе і що роблять люди. Почав з Google Analytics, все ж доволі непоганий інструмент, хоч і традиційно зіпсований черговим оновленням. Зазираючи у майбутнє, скажу, що GA на невеликих обсягах відвідувачів неймовірно глючить, актуальна статистика нарешті перераховується десь через три дні. Ну якась вже занадто eventual consistency. Тож на додачу прикрутив ще старий-добрий Clicky, нічого зайвого, просто точна статистика у режимі реального часу. І це було рівно що треба, ні більше, ні менше, тож з тих пір майже забив на GA.
Для зворотнього звʼязку створив окремий ТГ-канал спільноти з коментарями, раптом знайдуться бажаючі щось покращити. Тож пристебнути реміні, злітаємо!
Почав, природньо, з тих самих FFF, на свій подив, отримав доволі теплі відгуки та цікаві пропозиції. Майже ніхто з них не користувався міською електричкою, але ініціативу оцінили. Щоб не втрачати момент, поширив сайтик спільнотами любителів громадського транспорту – так, вони існують і доволі немалі. І от тут понеслося... Я, чесно кажучи, очікував чогось на кшталт двох коментарів, трьох лайків та двадцяти відвідувачів, але натомість отримав близько 40 відгуків, 300 переглядів та 40 підписників на телеграм-канал. До речі, канал спільноти дійсно працює, небайдужі люди оперативно реагують на помилки, пропонують цікаві ідеї та покращення. За що я їм дуже вдячний, приємно бачити, що воно комусь треба.
Замість висновків
Десь так мій пет-проект вихідного дня перетворився на сервіс, яким користується близько тисячі людей на місяць. Від початкового плану закодити-захостити-забити довелося відійти: якщо вже людям подобається, треба розвивати. Ось і казочці кінець, якщо вам було цікаво, дайте знати, напишу продовження:)
P.S. Також ця стаття є у міні-блозі на Hugo, що зʼявився значно пізніше - але то вже окрема історія
Коментарі (2)