Как я разрабатывал приложение для тестирования когнитивных функций

—

от автора

Привет! На связи Никита Шелепен, менеджер поддержки в Selectel. Расскажу о том, как однажды подписался на легкое на первый взгляд задание, предвкушая потратить пару вечеров, а закончилось все собственным CI/CD, Docker’ом и PWA.

Предстояло сделать сервис, который позволит оценивать когнитивные способности пользователей, а также будет собирать и сохранять результаты для выгрузки и дальнейшего исследования.

За моими плечами тогда было около года опыта — я писал инструменты администрирования на Vue с Bootstrap для одной компании в качестве подработки. Затем самостоятельно познакомился с SvelteKit — мне понравились его философия и модульность, а также то, что «из коробки» он дает удобный роутер и простые инструменты для создания fullstack-приложения.

С этим багажом знаний я и приступил к работе.

Содержание
→ Версия 1.0 — быстрый старт и первые костыли
→ Переход в «почти прод» — инфраструктура взрослеет
→ DevOps Arc — укрощение зоопарка
→ Борьба с реальностью — нестабильный интернет
→ От дизайна «на коленке» к «Мягкому стеклу»
→ Coolify — full-circle
→ Планы на будущее и UI-эволюция
→ Анимированные иллюстрации
→ Заключение

Все началось с задания в универе — сделать заказчику сайтик для исследования. Это был директор на биофаке в моем универе, она проводила собственное исследование в качестве научного руководителя. В команде была студентка — и впоследствии штатный психолог. В какой‑то момент возникла идея создать инструмент для психологического тестирования.

Изначально «мини-игры» записывались на бумаге, а уже затем я их имплементировал. Например, это были тесты:

  • Струпа для анализа помехоустойчивости при соотнесении цветов на экране и их названий;

  • арифметический для анализа скорости счета;

  • Мюнстберга — анализ избирательного внимания при поиске слов в сетке случайных букв;

  • на запоминание слов для анализа кратковременной памяти;

  • «Кампиметрия» для проверки способности различать оттенки цветов;

  • «Ласточка» для измерения скорости реакции и принятия решений.

Несколько анимированных Gif-картинок того, что получилось в итоге — в конце статьи.

Звучит интересно и просто — мне дают описание миниигры, а я, хвастаясь своими скиллами во фронтенде, делаю демку и мы обсуждаем, что нужно улучшить. Понемногу сайт начал приобретать вид реального проекта с кучей частей и взаимосвязей между ними. И вот когда мы начали делать свой CI/CD, я по‑настоящему увяз. Понятно было только одно —  дальше будет только сложнее.

Версия 1.0 — быстрый старт и первые костыли

Для изначальной задачи с головой хватало SvelteKit с деплоем на Vercel, так как он там настраивается автоматически и сразу интегрируется с GitHub. Каждый коммит — новый деплой. Заказчик сразу же получал результат по публичной ссылке от Vercel (еще и с SSL!), а проект работал как часы из-за своей простоты.

Достаточного опыта в дизайне у меня не было, да и заказчик требовал просто показать, как можно собрать такое приложение с тестами. Поэтому первоначальная архитектура не выходила за рамки чистого CSS. Фон приложения был темно-серым, без современных градиентов, а элементы — примитивно блочные. Такой дизайн прожил всего месяц, и я вскоре перешел на Tailwind. Мне нравится его философия: из готовых блоков быстро собираешь нужные UI-компоненты.

Для всего нашего проекта базы данных в единственном файлике SQLite было достаточно. Однажды возникла первая проблема: попытка завести управление БД прямо в fullstack-приложении на Vercel провалилась, так как на нем нельзя хранить какие-либо файлы кроме ассетов сайта — при создании SQLite-файла он ругается и падает. В качестве костыля я сделал отдельный маленький сервис на Node.js на собственном сервере — он отвечал за управление SQLite-файлом и REST API. Да, у Vercel есть свой Postgres-сервис, но мне он был не нужен — для наших данных хватало SQLite.

Когда все шесть тестов были готовы, обнаружилась медленная работа самого сайта — даже в простом переключении между страницами и нажатии кнопок. Может мощности бесплатного тира Vercel и хватало для демо, но мы хотели заложить потенциал для расширений, вроде новых анимаций и более сложной логики. К тому же заказчику захотелось видеть собственный красивый адрес сайта, значит нужно было купить собственный домен.

Пришло время уходить с Vercel.

Первая версия приложения.

Первая версия приложения.
Панель управления Vercel с деталями успешного деплоя проекта приложения для тестирования.

Vercel.

Переход в «почти прод» — инфраструктура взрослеет

В тот период развития проекта я устроился в Selectel и начал близко знакомиться с тем, как работают облачные услуги провайдера IT-инфраструктуры изнутри. Чтобы переехать с Vercel, для нашего проекта я выбрал довольно простую облачную конфигурацию: ОС Debian, 1 vCPU, 2 GB RAM, 30 GB SSD. Даже такое железо, но на отдельной виртуальной машине, оказалось в разы быстрее бесплатного тира в Vercel.

Какой купить домен долго думать не пришлось. Название исходит из самого исследования: «когнитивное тестирование» — значит, «Когни-Тест». В Selectel очень удобно купить домен и настроить доменные записи — все в одном месте.

Решив прочитать документацию SvelteKit подробнее, я узнал, что они рекомендуют Drizzle ORM для менеджмента БД. Оказалось, что она позволяет делать все что угодно — хоть подключать внешний PostgreSQL-сервер, хоть работать с SQLite-файлом на диске. Это позволило нам уйти от отдельного сервера, который управлял одним файликом SQLite, к библиотеке внутри проекта. Теперь запуск — что в development, что в production — делается в один шаг, а проект работает без внешних зависимостей.

К тому же, мы собирали данные пользователей, поэтому пришлось задуматься о безопасности их хранения — собственный сервер же это полностью позволяет.

30+ бесплатных курсов на ИТ‑темы в Академии Selectel

Для начинающих и опытных специалистов.

Изучить →

DevOps Arc — укрощение зоопарка

В процессе развития проекта я понял, хочу научиться Docker’у. Он решает классические проблемы повторяемости, что позволяет легко разворачивать сервисы на любой машине. Плюс мне было очень интересно узнать, как собирать свои собственные образы. До внедрения Docker на сервере все приходилось делать руками, а при обновлении — останавливать, пересобирать, запускать.

В текущей имплементации мы собираем образы через GitHub Actions и пушим в GitHub Container Registry. Автоматически происходит только сборка образа, а запуск воркфлоу с деплоем — по одному клику, причем отдельно по веткам: dev-образ и prod-образ.

Сетевое окружение проекта эволюционировало в три этапа.

  1. Сначала сервисы выходили просто в интернет — это не особо безопасно.

  2. Затем я решил научиться основе основ — nginx. Но для него нужно писать конкретный конфиг. Мы попытались автоматизировать через docker-gen, но тот оказался довольно замысловатым инструментом, который использует шаблоны на Go для генерации конфигов nginx.

  3. В итоге мы перешли на Traefik — настройка там происходит с помощью лейблов прямо в Docker Compose, конфиг получается очень красивым и лаконичным. Traefik с помощью лейблов по сути создает его динамически как docker-gen с nginx, но без боли.

Раньше я создавал Let’s Encrypt-сертификаты вручную, но у нас часто возникали ситуации, когда сертификат истекал прямо перед конференцией. Мне приходилось в спешке перекидывать новый файлик серта на сервер вручную. Docker-gen умел генерить сертификаты автоматически — это очень круто, но удобнее всего это получилось с Traefik: там встроена такая возможность прямо в конфиг.

Пример лейблов Traefik в Docker Compose:

cogni-test:        build:            context: .        image: ghcr.io/multifacs/cogni-test/cogni-test:latest        profiles:            - 'prod'        container_name: cogni-test        restart: always        networks:            - cogni-network        environment:            - MODE=PROD        stdin_open: true        tty: true        labels:# Сами лейблы            - 'traefik.enable=true'            - 'traefik.http.routers.cogni-test.rule=Host(`cogni-test.ru`)'            - 'traefik.http.routers.cogni-test.entrypoints=websecure'            - 'traefik.http.routers.cogni-test.tls=true'            - 'traefik.http.routers.cogni-test.tls.certresolver=letsencrypt'            - 'traefik.http.services.cogni-test.loadbalancer.server.port=80'

Со временем я познакомился с двумя инструментами.

  • Lazydocker — классный опенсорсный TUI-инструмент для мониторинга сервисов, просмотра логов, переменных среды и, конечно, управлением самими контейнерами/образами. Его я использовал при разработке.

  • Очень удобный в продакшене Drizzle Gateway — заказчику нужно было скачивать полученные данные, а Gateway от создателей Drizzle ORM предоставляет удобный GUI для просмотра БД.

Таким образом мы собрали свой кастомный CI/CD-пайплайн — не такой удобный, как у Vercel, зато полностью под нашим контролем.

Интерфейс GitHub Actions, демонстрирующий процесс ручного запуска деплоя приложения Cogni-Test в ветках dev и main.

Ручной деплой через Github Actions.
Терминальный интерфейс утилиты Lazydocker для мониторинга логов и управления Docker-контейнерами на сервере.

Lazydocker.

Борьба с реальностью — нестабильный интернет

На одной из конференций заказчик решил воспользоваться возможностью познакомиться с PWA и попросил участников пройти наш очередной тест — в рамках отдельного мини‑проекта «Когни-Ритм». Это испытание на попадание в ритм с тремя уровнями сложности. Перед нами встали сразу несколько задач: как при нестабильном интернете сохранять результаты, как уведомлять пользователей о новостях?

Заказчик настаивал, что нужно сделать именно приложение на телефон — чтобы пользователи скачивали его из магазина приложений. Проблема: для запуска в App Store нужна сертификация, аккаунт разработчика и соответствие требованиям платформы — это уже отдельный проект сам по себе. Я настоял, на использовании технологии PWA (Progressive Web App), которая позволяет сделать из сайта полноценное приложение без всех этих сложностей.

В основной сайт Cogni-Test сначала внедрили PWA просто чтобы тот определялся как приложение, а уже на «отпочкованном» Cogni-Rhythm обкатали полноценное кэширование.

Итоги тестов сохраняются локально через localForage — даже без интернета данные не теряются. Когда пользователь заходит на страницу результатов, приложение автоматически пытается загрузить их на сервер — если интернета нет, показывает ошибку и просит повторить.

Как человек, который до этого не сталкивался с PWA, я сначала думал, что нужно вручную настраивать Service Worker, модуль для управления фоновыми процессами в PWA. Документация по нему довольно сложная. Но оказалось, что в мире PWA уже давно существует библиотека Workbox, которая дает готовые стратегии: статику — CacheFirst, остальное — NetworkFirst с фоллбэком. Плюс: сервис-воркер автоматически определяет, что на сервере появилась новая версия, а мы для этого события настроили показ специального уведомления для пользователей.

И главное: при загрузке сайт предлагает установить себя на телефон — это дает пользователям привычную иконку на домашнем экране и запуск без браузерной рамки. Полноценное приложение из обычного сайта — и никаких сложностей с магазинами приложений.

Приложение Cogni-Rhythm с плашкой о том, что нужно обновиться. Экран установки PWA на телефон.

Приложение Cogni-Rhythm с плашкой о том, что нужно обновиться. Экран установки PWA на телефон.

От дизайна «на коленке» к «Мягкому стеклу»

Пока я писал эту статью, часть намеченных планов постепенно начала сбываться. Сайт уже вел себя как реальное приложение, и нам хотелось, чтобы он и выглядел соответственно. Также от заказчика поступило новое большое задание: агрегировать результаты множества участников, когда мы показываем наше приложение на конференциях — так называемые «сессии ментального ГТО».

С самого первого MVP все приложение было осознанно оформлено в темных тонах: такая тема нравится мне самому, и я решил не тратить силы на разработку двух вариантов. При переходе с простого CSS на Tailwind дизайн, наконец, стал адаптивным, а в стилях появились красивые темно-синие цвета. Тем не менее, опыта для создания полноценной системы мне все равно не хватало.

Вторая версия дизайна.

Вторая версия дизайна.

Недавно к нашей команде присоединился дизайнер — нам подыскали помощницу из студентов, чтобы она набралась опыта в разработке. Оказалось, что у нее также был опыт и в верстке. Мы наконец стали внедрять дизайн, как это полагается для серьезных проектов: макет в Figma, верстка элементов в соответствии с ним и общий файл app.css. Также дизайнер настояла на светлой теме, чтобы приложение стало дружелюбнее. Результатом стала светлая дизайн-система «Мягкое стекло»: пастельная гамма, полупрозрачные карточки, градиенты, воздух вокруг контента.

Третья версия дизайна — «Мягкое стекло».

Третья версия дизайна — «Мягкое стекло».

Параллельно с этим мы работали над «ГТО-М». Это дополнительная вкладка в интерфейсе нашего приложения, которая позволяет организовать совместные прохождения тестов на мероприятиях, научных конференциях, и ведет общую таблицу результатов по всем участникам. Так мы рекламируем сервис, набираем новых пользователей и получаем еще больше данных для нашего исследования. По этим результатам мы затем можем формировать сертификаты об успешном прохождении, чтобы участникам было приятно.

Админка ГТО-М.

Админка ГТО-М.

Coolify — full-circle

Экспериментировать с Traefik было интересно, но я быстро устал от ручного написания конфигов и отсутствия красивой обертки. Мне хотелось найти готовое решение, чтобы вернуть плюсы из опыта с Vercel. Такой оберткой стал open-source аналог Vercel — Coolify.

Решение перейти на Coolify закрыло мое желание сделать свою админку для управления инфраструктурой. Оно позволяет развертывать новые сервисы по клику, удобно управлять доменами c автоматическим выпуском SSL-сертификатов за счет Traefik в своей основе, настраивать уведомления на электронную почту и много других вещей, которые мне еще предстоит в нем изучить.

Главный дашборд платформы Coolify для управления серверной инфраструктурой и развертывания веб-сервисов.

Общий вид Coolify.

Чтобы разместить Coolify, а также иметь больший запас производительности на одной машине для потенциального развертывания других сервисов, мы перешли на более производительный сервер — 4 vCPU, 8 ГБ RAM и 80 ГБ SSD.

Теперь через Coolify развернут прод cogni-test.ru и dev-билды для тестирования нововведений в отдельных ветках. Рядом лежит Drizzle Gateway для быстрой выгрузки из базы данных, а под капотом Coolify остался все тот же Traefik — я даже не менял его конфиг. Вдобавок встроенными возможностями Coolify мы удобно подключили S3 для автоматического бекапа файла базы данных. Получился тот же гибкий стек, но с другим уровнем абстракции.

Настройка SSL-сертификатов и привязка доменного имени проекта в панели управления Coolify.

Удобная настройка доменов.
Конфигурация параметров автоматического резервного копирования базы данных в объектное S3-хранилище через Coolify.

Бэкап в S3.
Интерфейс настройки предпросмотра тестовых сборок (Preview deployments) для проверки нововведений в Coolify.

Предпросмотр тестовых сборок.

GitHub остался как билдер образов по каждому запушенному (push) коммиту, и они хранятся в GHCR. Это избавляет от нужды в отдельном билд-сервере и нагрузки на прод, но Coolify также позволяет подключить свой билд-сервер и собирать образы полностью на своей инфраструктуре.

Получается, что мое путешествие начиналось с «каждый коммит — новый деплой» на Vercel, и я вернулся к той же концепции, но уже под полным своим контролем. Правда, настроить этот автодеплой еще предстоит, но он остался в паре кликов от выполнения.

Планы на будущее и UI-эволюция

С тех пор как мы начинали с небольшой демки из шести тестов, проект сильно вырос. Теперь у нас шесть изначальных тестов для определения когнитивного возраста пользователя и его подсчета с помощью небольшого классификатора, тринадцать упражнений для оттачивания когнитивных навыков и отдельный раздел для потокового тестирования с сессиями по пользователям и экспортом данных. Проект уже давно перестал быть «сайтиком для чьего-то диплома».

Сейчас авторизация сделана через cookies, которые мы проставляем вручную, без управления сессиями. Хочу, чтобы в будущем этим управляла специализированная библиотека, такая как Better Auth (тоже из документации к SvelteKit) — она дает сессии, авторизацию и все остальное.

Для удобной и наглядной разработки новых компонентов есть «влажная мечта» внедрить Storybook — фронтенд-воркшоп, который позволяет изолировать компоненты и удобно менять их параметры, сразу получая результат. В нем долго разбираться и он тяжелый, но сама идея прототипировать компоненты в песочницах щекочет хотелку.

Пользовательский интерфейс фронтенд-воркшопа Storybook для изоляции, тестирования доступности и прототипирования UI-компонентов.

Иллюстрация из доки Storybook.

Также планируем внедрить WebSockets, чтобы тестовую сессию могли проходить одновременно условные врач и пациент — первый в реальном времени видит результаты второго. Это также позволит организаторам отслеживать поток участников в сессиях «ГТО-М»на конференции.

Помимо внешних изменений, которые будут доступны пользователям, мы хотим довести до совершенства и админку для нас самих. Здесь философия проста: в первую очередь — все для пользователей, и только во вторую админка для нас самих.

Анимированные иллюстрации

Быстрый счет
Слова и повторы
Поиск слов
Цвет и смысл
Полет птицы
Скрытая фигура

Заключение

«Пара вечеров» на демо приложения обернулась целым годом пути: Vercel → VPS → Docker → Traefik → Coolify. Этот путь дал мне новые полезные знания, веселый опыт работы в команде, и я уверен, что он закончится еще не скоро.

Первая половина шагов в инфраструктуре делалась на чистом энтузиазме изучения новых инструментов. Вторая же стала ответом на конкретную боль:

  • SQLite не заработал на внешней площадке — подняли собственный сервер;

  • сертификат истек перед конференцией подключили Traefik;

  • тяжело работать с конфигурациями вручную — внедрили Coolify.

Можно сказать, что шаг за шагом рабочая инфраструктура формировалась сама.

Не обязательно строить космолет на старте, но не удивляйтесь, когда новые фичи и инструменты сами попросятся в проект. И это хороший знак: значит, проект живой.

Получается, что главное правило — «лучше сделать плохо, чем не сделать».

ссылка на оригинал статьи https://habr.com/ru/articles/1101540/