Зачем всё так сложно: постановка и архитектура
- понедельник, 17 августа 2026 г. в 00:00:04
Итак, проводится некое соревнование с раздельным стартом, и нам необходимо вести хронометраж. Для этого на старте и финише размещаются судьи, которые фиксируют время старта и финиша каждого участника. После завершения заезда все данные сводятся в итоговый протокол. Выглядит вполне просто и логично (пока что), но давайте погрузимся в детали.
Список участников с номерами и планируемым временем старта (для формирования протокола).
Устройства с приложением для фиксации времени старта и финиша по номерам участников.
Организатор — работа со списком участников: добавление, присвоение номеров, назначение времени старта.
Судья — фиксация старта, финиша, отказа от участия или происшествия.
Наблюдатель (зритель) — просмотр списка участников и их статуса.
До даты проведения
Добавить информацию о соревновании: условия, маршрут, категории.
Заполнить список участников (предварительная регистрация / импорт).
Указать планируемое время старта для каждого участника.
При проведении, до старта
Присвоить номера участникам, которые прибыли на место.
Добавлять новых участников и определять для них плановое время старта (если разрешена регистрация в день соревнования).
Во время проведения
Контролировать появление участников на старте в соответствии с плановым временем.
Отмечать фактическое время старта.
Отмечать фактическое время финиша.
Фиксировать отказ от участия после старта (вместе с временем события).
Присваивать номера опоздавшим участникам и назначать им новое плановое время старта.
При необходимости регистрировать новых участников прямо во время соревнования.
После проведения
Зафиксировать итоговые результаты в протоколе.
Детализация получилась заметно объёмнее первоначальной формулировки задачи, но она хорошо показывает, сколько функций необходимо обеспечить для реального использования. В дальнейшем информации станет ещё больше, когда мы начнём рассматривать отдельные функции и технические решения.
В дополнение к описанному процессу есть ряд важных требований.
На всех устройствах организаторов и судей должно быть синхронизировано время непосредственно перед началом соревнования.
Фиксация событий должна быть возможна при нестабильном соединении — данные не должны теряться.
Необходимо вести лог событий, поступающих от судей, и обеспечивать его просмотр.
Приложение должно одинаково хорошо работать на мобильных телефонах, планшетах и десктопах.
Возможность заранее опубликовать информацию о соревновании для всех посетителей сайта.
Возможность наблюдать за ходом соревнования в реальном времени (любой посетитель видит обновления данных об участниках).
Возможность опубликовать итоговый протокол и другие материалы для всеобщего доступа.
Получился вполне приличный объём информации, а мы ещё ничего не начали реализовывать. Самое время ещё раз проверить все сформулированные требования, потому что вносить изменения имеет смысл именно на этом этапе, — когда начнётся проектирование и реализация, правки потребуют гораздо больших затрат.
После того как основная задача была сформулирована, мы решили подтвердить актуальность требований, понаблюдав за работой организаторов в реальных условиях.
Первое посещённое соревнование подтвердило все наши ожидания: роли и процесс полностью соответствовали описанному.
А вот второе соревнование проходило практически ночью, и тут возникли неожиданные нюансы:
На финише номера финиширующих участников не были видны из-за темноты. Судья сначала фиксировал время, а потом, когда участник подъезжал ближе, заносил номер. Если бы финиш освещался так, чтобы номера хорошо читались, свет слепил бы спортсменов. Значит, порядок фиксации финиша может быть разным: либо сначала номер, потом время (как предполагалось изначально), либо сначала время, а затем номер.
На старте тоже оказалось не всё просто: из-за темноты участники ориентировались на звук таймера, а не на время, отображаемое на экране.
Таким образом, мы должны дополнить обязательные требования:
На старте необходим обратный отсчёт с отображением таймера и звуковыми сигналами «3… 2… 1… Старт».
На финише нужно предусмотреть возможность сначала зафиксировать время, а затем ввести номер участника и отправить данные на сервер.
Связь на местности была ожидаемо неустойчивой, поэтому нам потребуется минимизировать объём передаваемых данных и обеспечить работу в офлайн-режиме.
В качестве основы мы выбираем ASP.NET Core MVC на языке C#. Приставка «MVC» не должна вводить в заблуждение: SPA может обмениваться с сервером не только JSON-пакетами, но и готовыми HTML-фрагментами, которые вставляются прямо в контейнеры на странице. Контроллеры у нас будут и те, что отдают HTML, и те, что возвращают JSON, — в зависимости от потребностей конкретной функции.
База данных — PostgreSQL, она пользуется заслуженной популярностью, особенно в последнее время.
Возможные варианты реализации UI:
Классический MVC — генерация HTML на стороне сервера с помощью Razor-шаблонов и обработка форм через POST-запросы.
SPA с генерацией HTML в браузере — обмен данными с сервером в формате JSON и использование одного из современных фреймворков (Angular, React, Vue.js).
Исходя из требования работы приложения при отсутствии сети и на мобильных устройствах, второй вариант подходит лучше по следующим причинам:
Логика отображения UI будет иметь единую реализацию и работать независимо от доступности сервера: навигация по уже полученным данным возможна и в офлайне, а при наличии связи данные синхронизируются.
Для кэширования статических компонентов UI можно использовать Service Worker.
Компоненты UI будут работать с хранилищем в браузере (например, localStorage), которое в свою очередь будет синхронизироваться с сервером.
Всю клиентскую часть логично оформить как SPA/PWA-приложение для более комфортной работы на мобильных устройствах.
Технология PWA значительно упростила разработку приложений, работающих на разных платформах: нам не нужно писать «родные» приложения под каждую ОС, достаточно сделать адаптивную вёрстку и убрать элементы интерфейса браузера. С развитием Web API можно делать то, что раньше казалось невозможным (например, работать с USB/Bluetooth-устройствами или даже обновлять прошивки прямо из браузера). А если возможностей стандартных HTML-элементов не хватает, всегда можно использовать Inline SVG, Canvas или даже 3D-рендеринг.
UI-компоненты обеспечивают навигацию, отображение и редактирование данных, обращаясь к хранилищу данных. Это всё, что нужно для SPA.
Хранилище данных работает следующим образом:
При запросе данных оно пытается обратиться к серверу (если есть связь) для получения актуальной информации, сохраняет ответ в localStorage и возвращает данные компонентам.
Если связи нет, хранилище возвращает данные из localStorage.
При получении команды на изменение данных хранилище сначала сохраняет изменения в localStorage, а затем инициирует отправку на сервер. Если сервер недоступен, отправка откладывается и повторяется при появлении соединения. Конфликты (одновременные правки от разных судей) — вопрос, который будем решать уже на уровне сервера; на этапе клиентского прототипа мы отложим эту тему.
Service Worker загружает с сервера компоненты, код хранилища, стили и другой статический контент, сохраняет их в кэше, а также реализует логику обновления кэшированного контента — это необходимо для работы PWA.
Серверная MVC-часть занимается сериализацией и десериализацией данных и вызывает соответствующие методы сервисов для получения или изменения данных.
Сервисы реализуют бизнес-логику: чтение и сохранение моделей, расчёт показателей участников (время, место и т. д.).
Все основные данные хранятся в PostgreSQL.
Определим базовый набор готовых компонентов, которые будем применять. Список будет дополняться по мере реализации, но на старте он выглядит так:
ASP.NET Core MVC — платформа для веб-приложения.
Npgsql — провайдер для доступа к PostgreSQL из .NET.
Vue.js — UI-фреймворк для построения компонентов. Он выбран как наиболее простой для встраивания, с отличной документацией и экосистемой. Эксперимент начинался около трёх лет назад, поэтому используется Vue 2 и соответствующее ему официальное хранилище Vuex; для современных проектов можно рассмотреть Pinia, но в рамках эксперимента это не принципиально.
Vue Router — официальный маршрутизатор для построения SPA.
RequireJS — библиотека для асинхронной загрузки скриптов и компонентов. Возможно, сегодня она не так актуальна (появилась нативная модульность в JS, сборщики типа Webpack), однако её применение в нашем эксперименте — осознанный шаг. Мы посвятим RequireJS отдельный разговор, в котором разберём механизмы работы модулей и альтернативные подходы.
System.Text.Json — встроенный в .NET механизм работы с JSON. Раньше безоговорочным стандартом был Newtonsoft.Json, но теперь платформенный инструмент вполне успешно конкурирует с ним.
Да, в этом списке нет Entity Framework Core и Docker. Сознательно оставляем их «за бортом», чтобы сфокусироваться на минимально необходимом наборе технологий. Работать будем напрямую с ADO.NET и без контейнеризации — попробуем справиться пока без них.
В следующей статье мы перейдём к практической части: начнём с реализации пользовательского интерфейса. Создадим работающую в браузере заглушку с локальными данными, которая позволит «пощупать» внешний вид и поведение будущего приложения.