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

Разработка программного обеспечения

Пишем веб-платформы и доводим их до работающей установки

Olista Trade Enterprise — это разработка на .NET: модель данных, сервер входа, интерфейс, интеграции с учётными системами и всё, что нужно, чтобы это жило на сервере — контейнеры, TLS, резервные копии. Первый наш продукт — конструктор сайтов; он работает, и его можно открыть прямо отсюда.

Всё на этой странице взято из самого продукта: возможности — те, что работают сегодня, состав стека — из файлов сборки, которая её и отдаёт. Чего ещё нет, написано отдельной строкой.

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

Как мы работаем

Четыре правила, которые видно в коде, а не только в описании. Они дороже любого списка технологий: технологии у всех примерно одни.

Решение записано вместе с кодом

Рядом с каждым нетривиальным местом написано не только как сделано, но и почему так, и что было бы, сделай мы иначе. Через полгода это единственное, что отличает обдуманное решение от случайного, — и единственное, что мешает аккуратно вернуть уже исправленную ошибку.

Тесты пишутся как атаки

Не «счастливый путь», а попытки: выйти из объявления стиля, подсунуть javascript: в ссылку, отправить форму с чужой страницы, назвать свою цену в корзине, дотянуться до данных чужого клиента. У отказа изоляции нет симптома: запросы проходят, страницы рисуются, отличается только то, чьи данные на них.

Ничего не грузится со стороны

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

Незаконченное называется незаконченным

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

Наши проекты

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

Конструктор сайтов

Продукт платформы Lerro Center

Визуальный конструктор: владелец дела собирает страницы из блоков, настраивает тему и публикует — и получает обычный сайт, который открывается по адресу конструктора сразу после нажатия. Отдельно от страниц в нём живут интернет-магазин с каталогом из учётной системы, онлайн-запись и приём заявок.

  • Редактор блоков Страница собирается перетаскиванием, отмена сделана снимками документа, а панель настроек генерируется из описания блока — пятьдесят рукописных панелей означали бы пятьдесят разных форм и пятьдесят шансов принять значение, которое сервер отклонит.
  • Шаблоны вместо пустого холста Сайт начинается с готового набора страниц: салон, кофейня, студия услуг, мастерская, интернет-магазин, лендинг. Шаблоны проверяются по каталогу блоков при старте, поэтому переименованное свойство роняет запуск, а не страницу автора.
  • Публикация снимком Опубликованная страница — неизменяемая копия черновика вместе с темой, шапкой и подвалом. Правки не появляются на живом сайте, пока автор их не опубликовал, а откат — это восстановление предыдущего снимка, то есть одна запись.
  • Сайт без рантайма в браузере Посетитель получает обычный HTML, один файл стилей и один небольшой скрипт. Формы настоящие, фильтры — обычный GET, ссылки — ссылки: страница, которая без JavaScript выглядит сломанной, так же выглядит и для поискового робота.
  • Интернет-магазин Каталог, карточка с вариантами и галереей, фильтры, корзина и оформление заказа. Ни одна сумма не принимается от браузера: цену считает сервер, а при оформлении все строки переоцениваются заново — корзина может пролежать неделю.
  • Каталог из 1С Источник подключается по OData: номенклатура, цены, остатки и фотографии, обновление по расписанию. Правка, сделанная менеджером в консоли, накладывается поверх источника и переживает ночную синхронизацию — иначе складское название возвращается к утру.
  • Онлайн-запись Услуги, график работы и свободные окна. Два человека не займут одно окно, потому что это уникальный индекс в базе, а не проверка перед записью: проверка проигрывает гонку, индекс — нет.
  • Заявки и письма Формы складывают обращения в консоль сайта и присылают письмо владельцу; покупателю и гостю уходит своё подтверждение. Письма идут очередью с повторами, а не из середины оформления заказа.
  • Соавторы, роли и журнал Владелец сайта заводит соавторов. У платформы отдельный набор прав с областью «вся установка» или «один сайт», а заход сотрудника в чужой сайт остаётся в журнале — с повышением записи, если чтение перешло в правку.
  • Кабинеты покупателей Магазин может пускать покупателей в личный кабинет: у каждого сайта свой клиент входа и своя популяция учётных записей. Клиент магазина — это организация со своими людьми и ролями, а покупатель одного магазина не существует для другого.
  • .NET 10
  • Blazor WebAssembly
  • MongoDB
  • OpenIddict
  • OData 1С
  • SkiaSharp

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

Попробоватьbuilder.olista.ru

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

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

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

  • .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, а не против заглушки, — заглушка согласилась бы с любым ожиданием и не доказала бы ни уникального индекса, ни фильтра по сайту.

Посмотрите, что получилось

Соберите страницу из блоков и опубликуйте её — нужны только браузер и учётная запись. О том, что осталось доделать, там написано теми же словами, что и здесь.

Произошла необработанная ошибка. Перезагрузить 🗙

Восстанавливаем соединение с сервером…

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

Соединение восстановить не удалось.
Попробуйте ещё раз или перезагрузите страницу.

Сервер приостановил сессию.

Продолжить сессию не удалось.
Попробуйте ещё раз или перезагрузите страницу.