Постановка и бизнес-процесс
Итак, проводится некое соревнование с раздельным стартом, и нам необходимо вести хронометраж. Для этого на старте и финише размещаются судьи, которые фиксируют время старта и финиша каждого участника. После завершения заезда все данные сводятся в итоговый протокол. Выглядит вполне просто и логично (пока что), но давайте погрузимся в детали.
Что необходимо для выполнения основной задачи
Список участников с номерами и планируемым временем старта (для формирования протокола).
Устройства с приложением для фиксации времени старта и финиша по номерам участников.
Роли в рамках задачи
Организатор — работа со списком участников: добавление, присвоение номеров, назначение времени старта.
Судья — фиксация старта, финиша, отказа от участия или происшествия.
Наблюдатель (зритель) — просмотр списка участников и их статуса.
Процесс и его основные этапы
-
До даты проведения
Добавить информацию о соревновании: условия, маршрут, категории.
Заполнить список участников (предварительная регистрация / импорт).
Указать планируемое время старта для каждого участника.
-
При проведении, до старта
Присвоить номера участникам, которые прибыли на место.
Добавлять новых участников и определять для них плановое время старта (если разрешена регистрация в день соревнования).
-
Во время проведения
Контролировать появление участников на старте в соответствии с плановым временем.
Отмечать фактическое время старта.
Отмечать фактическое время финиша.
Фиксировать отказ от участия после старта (вместе с временем события).
Присваивать номера опоздавшим участникам и назначать им новое плановое время старта.
При необходимости регистрировать новых участников прямо во время соревнования.
-
После проведения
Зафиксировать итоговые результаты в протоколе.
Детализация получилась заметно объёмнее первоначальной формулировки задачи, но она хорошо показывает, сколько функций необходимо обеспечить для реального использования. В дальнейшем информации станет ещё больше, когда мы начнём рассматривать отдельные функции и технические решения.
В дополнение к описанному процессу есть ряд важных требований.
Обязательные требования
На всех устройствах организаторов и судей должно быть синхронизировано время непосредственно перед началом соревнования.
Фиксация событий должна быть возможна при нестабильном соединении — данные не должны теряться.
Необходимо вести лог событий, поступающих от судей, и обеспечивать его просмотр.
Приложение должно одинаково хорошо работать на мобильных телефонах, планшетах и десктопах.
Дополнительные требования
Возможность заранее опубликовать информацию о соревновании для всех посетителей сайта.
Возможность наблюдать за ходом соревнования в реальном времени (любой посетитель видит обновления данных об участниках).
Возможность опубликовать итоговый протокол и другие материалы для всеобщего доступа.
Получился вполне приличный объём информации, а мы ещё ничего не начали реализовывать. Самое время ещё раз проверить все сформулированные требования, потому что вносить изменения имеет смысл именно на этом этапе, — когда начнётся проектирование и реализация, правки потребуют гораздо больших затрат.
Оценка работы «на местности»
После того как основная задача была сформулирована, мы решили подтвердить актуальность требований, понаблюдав за работой организаторов в реальных условиях.
Первое посещённое соревнование подтвердило все наши ожидания: роли и процесс полностью соответствовали описанному.
А вот второе соревнование проходило практически ночью, и тут возникли неожиданные нюансы:
На финише номера финиширующих участников не были видны из-за темноты. Судья сначала фиксировал время, а потом, когда участник подъезжал ближе, заносил номер. Если бы финиш освещался так, чтобы номера хорошо читались, свет слепил бы спортсменов. Значит, порядок фиксации финиша может быть разным: либо сначала номер, потом время (как предполагалось изначально), либо сначала время, а затем номер.
На старте тоже оказалось не всё просто: из-за темноты участники ориентировались на звук таймера, а не на время, отображаемое на экране.
Таким образом, мы должны дополнить обязательные требования:
На старте необходим обратный отсчёт с отображением таймера и звуковыми сигналами «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 и без контейнеризации — попробуем справиться пока без них.
В следующей статье мы перейдём к практической части: начнём с реализации пользовательского интерфейса. Создадим работающую в браузере заглушку с локальными данными, которая позволит «пощупать» внешний вид и поведение будущего приложения.
Комментарии (7)

Dhwtj
16.08.2026 18:39чем происшествие отличается от отказа в протоколе: оба DNF или происшествие отдельная строка?
обратный отсчёт на старте кто ведёт, приложение по плановому времени или судья вручную?
Непонятно как происходит синхронизация. Непосредственно перед стартом клиент запрашивает время сервера? А пинг?
Протокол вроде фиксирует судья. А опротестовать можно?
То есть мы фиксируем события, которые рождаются на клиенте и приезжают на сервер с учётом поправки на синхронизацию? Как долго ждём? Интернет пропал и всё, события не дошли. Значит, может быть состояние забег не состоялся? Или такого просто выкинут из забега?
Это пока всё про бизнес правила
На клиенте только рекомендуемое время старта и не влияет на отметку фактического времени старта? А если стартовал уже вне окна? Понятие окна старта вообще тут есть? Оно заранее известно или просто судья решает пустить или нет?

Dhwtj
16.08.2026 18:39Или фактическое время старта никто не меряет, главное чтобы не стартовал раньше планового времени?

ETCDema Автор
16.08.2026 18:39Я описываю любительские соревнования, поэтому часть требований намеренно упрощена. Про время: в статье прямо сказано, что все устройства организаторов и судей должны быть синхронизированы перед стартом. Для нашего уровня точности этого достаточно.
События, которые рождаются на клиенте, приезжают на сервер, и по мере их поступления сервер пересчитывает текущую картину.
Обратный отсчёт на старте ведёт приложение, запускает его организатор, когда участник выходит на старт. Плановое время нужно участникам, чтобы они сами подходили на старт в своём порядке. Фактическое время старта - когда счетчик дошел до нуля. Если пересек линию раньше, то судья возвращает участника на старт и аннулирует событие неудачного старта. Если поехал чуть позже - что ж, где-то придется ускоряться.
Пока организатор не зафиксировал протокол, события могут добавляться и вручную, и досылаться, если у судьи терялась сеть. На клиенте они сохраняются локально и отправляются повторно до получения подтверждения от сервера, так что потеря событий исключена. Естественно, это требует контроля повторного получения на стороне сервера.

PAL_habr
16.08.2026 18:39Если вопрос в том чтобы данные передать сначала на сервер, а потом сделать итоговые бюллетени, и точность нужна не на уровне сотых секунды, то никакая синхронизация времени устройств не нужна. Устройство просто фиксирует события в локальном времени устройства, а когда передает данные на сервер, одновременно передает и свое текущее локальное время. Сервер определяет разницу текущего времени устройства со своим временем и вносит корректировку для времени событий.

ETCDema Автор
16.08.2026 18:39На самом деле все проще: в событиях фиксируется локальное время устройства, никакой разницы с сервером считать не нужно. Синхронизация времени на устройствах нужно что бы разница во времени между устройствами была минимальной т.к. устройства организаторов являются в нашем случае доверенными. Для этого достаточно установить приложение для синхронизации времени.
Dhwtj
Ай ай
Оказалось, реальная ситуация не совпала с идеальной моделью. Пришлось усложнять, pity
Но зачем вы опять полезли в технологический стек, подробности реализации?
Рано ещё
ETCDema Автор
Собственно, для этого и нужен был разбор реальной ситуации до реализации - чтобы внести поправки, пока это недорого. Идеальная модель как раз и проверяется практикой, и хорошо, что это произошло на этапе постановки, а не во время соревнования.
Здесь ещё нет реализации, только подготовка к ней и перечисляю то, что уже явно потребуется.