Перейти к содержимому
Olista
Войти

Как это сделано

Стек и решения

Не список модных слов, а состав этой сборки с объяснением, почему выбрано именно так. Версии заданы централизованно, одним файлом на всё решение.

Слои

Сверху вниз, от браузера к машине.

Браузер
Blazor: статическая отрисовка, серверные острова и WebAssembly там, где нужна отзывчивость
Приложения
ASP.NET Core на .NET 10 — три сервиса под одним оркестратором
Вход
OpenIddict: OAuth 2.0 и OpenID Connect, свои наборы учётных записей
Данные
MongoDB: документы, TTL-индексы, файлы в GridFS
Эксплуатация
Контейнеры, nginx с TLS, снимки базы по расписанию

Платформа и выполнение

  • .NET 10 и ASP.NET Core

    Один целевой фреймворк на все проекты решения, заданный в корневом файле свойств. Никакого «почти обновлено»: обновляется всё сразу или ничего.

  • .NET Aspire

    Оркестратор разработки: одной командой поднимает сервер идентификации, конструктор и этот сайт, раздаёт им строки подключения и адреса. В продакшене топология повторена вручную — compose написан как исходник, а не сгенерирован, потому что половины продакшена (nginx, TLS, снимки базы) в модели оркестратора нет и быть не может.

  • Blazor: сервер и WebAssembly

    Один компонентный слой на оба режима, и режим выбирается под задачу. Страницы, которые публикует конструктор, собираются на сервере и приезжают в браузер обычным HTML — без Blazor-рантайма вовсе. Редактор — WebAssembly, потому что перетаскивание блока и ползунок отступа меняют документ десятки раз в секунду, и на серверном соединении каждое такое изменение было бы обращением по сети.

  • OpenTelemetry

    Логи, метрики и трассировки собираются одинаково во всех приложениях и уходят по OTLP. Общий слой умолчаний ставит это вместе с проверками готовности и поиском сервисов — новый сервис получает наблюдаемость фактом подключения библиотеки.

Данные и вход

  • MongoDB

    Документная модель ложится на то, что здесь хранится: страница — дерево блоков, карточка товара — открытый набор характеристик, которые придумал администратор магазина. Сроком жизни входов занимается TTL-индекс самой базы, а не фоновая задача приложения, которая останавливается вместе с приложением.

  • OpenIddict и ASP.NET Core Identity

    Свой сервер OAuth 2.0 и OpenID Connect с учётными записями поверх MongoDB: вход, двухфакторная проверка, согласие, самообслуживание и админ-API. Всё остальное аутентифицируется через него, включая этот сайт.

  • Свои наборы учётных записей

    Те, кто делает сайты, и покупатели магазинов живут в разных наборах: один общий и свой у каждого сайта. Один и тот же адрес регистрируется в ста магазинах независимо, а покупатель одного магазина не существует для другого. Разделение проведено в ключах поиска — то есть невыразимо, а не проверяемо.

  • Ключи защиты данных переживают рестарт

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

Граница браузера

  • Backend-for-frontend вместо токена в браузере

    Страница ходит по своему же origin с одной cookie в несколько сотен байт, а тикет с токенами лежит в базе. Токен, отданный браузеру, лежит там, куда дотягивается любой внедрённый скрипт, и живёт ровно столько, сколько его никто не украл; вход в базе можно отозвать одной записью.

  • YARP

    Взят не как шлюз, а ради прямой пересылки запроса: потоковые тела, hop-by-hop заголовки, HTTP/2 и передача отмены там уже сделаны верно, и переписывать это руками означало бы сделать то же самое хуже.

  • Санитайзер на входе

    Авторские HTML и Markdown чистятся один раз — при сохранении, а не при каждом показе. Чистка при чтении означает, что о ней должен помнить каждый путь чтения: публичный рендерер, экспорт, предпросмотр, карта сайта. Один из них однажды не вспомнит, и именно он станет уязвимостью.

  • SkiaSharp для картинок

    Фотография приезжает из учётной системы такой, какой её сохранил товаровед, а в сетке показывается на 280 пикселях — уменьшенные копии делаются по запросу и кэшируются. Библиотека выбрана не по технической причине: раздельная лицензия ImageSharp превращает коммерческое использование выше порога выручки в денежное обязательство, а Skia — MIT.

Сборка и эксплуатация

  • Централизованные версии пакетов

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

  • Контейнеры, nginx и TLS

    Продакшен — одна машина: nginx с TLS и автопродлением сертификата, образы приложений и MongoDB со снимками по расписанию. Наружу открыты только 80 и 443; у базы порт на внешнем интерфейсе не «закрыт файрволом», а не существует.

  • Автотесты как условие сборки

    xUnit, Shouldly и bunit — последний потому, что две поломки разметки дошли до пользователя, не уронив ни одного теста логики: сломано было то, что рисуется. Тесты изоляции идут против настоящей MongoDB, а не против заглушки, — заглушка согласилась бы с любым ожиданием и не доказала бы ни уникального индекса, ни фильтра по сайту.

Восстанавливаем связь с сервером…

Не вышло. Следующая попытка через с.

Связь восстановить не удалось. Нажмите «Ещё раз» или обновите страницу.

Сервер приостановил работу страницы.

Продолжить не удалось. Нажмите «Продолжить» ещё раз или обновите страницу.