Схема контура
Что с чем разговаривает. Боевые адреса, порты и имена таблиц здесь обезличены — показана механика, а не карта.
Выкладка на прод идёт только через сборочный конвейер репозитория, адресно по путям: прямая заливка отключена сознательно — два канала выкладки означают два разных прода. Оранжевая стрелка снизу вверх — то самое перевёрнутое направление связи.
Состав
| Слой | Чем сделан | Каким ограничением продиктовано |
|---|---|---|
| Сайт | статический HTML, собирается из отдельных источников контента через слоты и рендер | тысячи страниц, из них большинство типовые; шаблонный движок на сервере дал бы то же самое, но с сервером, который может лечь. Статика не падает и не требует обслуживания |
| Выкладка | только через сборочный конвейер репозитория, адресно по путям | единственный канал выкладки — иначе прод и репозиторий расходятся. Прямая заливка по SFTP отключена сознательно: два канала выкладки означают два разных прода |
| Инструменты и проверки | ~490 Python-скриптов: генераторы, чекеры страниц, гейты цен и персональных данных, сборщики отчётов | ручная операция, повторённая трижды, становится скриптом. Реестр инструментов ведётся отдельно, чтобы не заводить второй такой же |
| Админка и её бэкенд | PHP-API на том же хостинге, что и сайт | работает там, где уже есть хостинг, без второго сервера. Цена решения названа ниже |
| Бот, мини-приложение, счета | три Node-сервиса под менеджером процессов, три порта, три поддомена | разные контуры доступа: внутренний, пользовательский и публичный. Разделены сервисами, а не флагами в одном приложении |
| Подпись документов и обмен с госсистемой | агент на Java 11, локальный HTTP-порт на машине владельца, 105 библиотек | криптография по национальному стандарту существует под эту платформу. Ключ, ПИН и пароль кабинета не должны покидать компьютер владельца — значит агент локальный, а не облачный |
| Распознавание речи | дообученная модель, воркер сам забирает задания с прода | у машины с моделью нет белого адреса. Не пробрасывать порт наружу, а перевернуть направление связи — воркер ходит за работой сам |
| Навигация по коду | граф кода с ручными наложениями поверх автоматического разбора | вопрос «что сломается, если тронуть это» дешевле задать графу, чем поиску по тексту |
Три решения, которые стоит объяснить отдельно
- Направление связи перевёрнуто. Классический ответ на «нет белого адреса» — проброс порта или туннель. Мы вместо этого сделали очередь на стороне прода: воркер сам приходит за заданием, забирает, возвращает результат. Наружу не открыто ничего. Побочный выигрыш — машина может быть выключена, задания просто копятся.
- Секреты не централизованы намеренно. Ключ электронной подписи лежит на машине владельца и никуда не уезжает. Это неудобно (агент надо запустить) и это осознанный размен: централизованное хранилище ключей — новая точка отказа и новый предмет разговора с проверяющим.
- Разграничение видимости — в коде, а не в интерфейсе. Один и тот же заказ отдаётся разным ролям через фильтр полей: клиент не получает ни телефона водителя, ни ставок, ни служебных флагов. Прятать на фронте бесполезно — данные всё равно уходят в браузер.
Разграничение видимости — на сервере. Имена полей обезличены, механика настоящая
// Один заказ — три представления. Роль решает, что вообще уйдёт в ответ,
// а не то, что потом спрячет интерфейс.
function view_for(array $order, string $role): array {
$common = ['id', 'status', 'from', 'to', 'when'];
if ($role === 'client') {
// ни телефона водителя, ни ставки, ни служебных флагов
return pick($order, [...$common, 'price_client']);
}
if ($role === 'driver') {
// своя ставка видна, клиентская цена — нет
return pick($order, [...$common, 'rate_driver', 'client_name']);
}
return $order; // диспетчер видит всё
}Три строки, но именно они отвечают на вопрос «а данные точно не утекут в браузер». Фильтр стоит в одной функции, и все каналы — сайт, бот, мини-приложение — ходят через неё. Пока представление собирается в каждом обработчике отдельно, рано или поздно один из них отдаст лишнее поле, и заметит это клиент, а не мы.
Где эта сборка упирается — список без смягчений
| Предел | Что это значит на практике |
|---|---|
| Хранилище — файлы JSON без схемы и блокировок | гонки записи и стихийная эволюция полей. Разобрано отдельно: один запрос уже обнулял базу заявок |
| У PHP-API нет автотестов | а он ворочает заявками и деньгами. Провал ловится в проде человеком. Ближайшая работа — локальный прогон с конкурентными запросами и невалидным телом в том же гейте, что и проверка синтаксиса |
| Нет мониторинга доступности | сервер умер — узнали при попытке им воспользоваться; автосбор данных стоял 10 дней незамеченным. Лечится дешёвой проверкой живости и внешним пингом, а не переписыванием архитектуры |
| Резервных копий репозиториев вне одного диска нет | самый неприятный пункт списка: единственная копия — вопрос «когда», а не «если» |
| Часть инструментов живёт вне основного контура | по нашему же правилу инструмент, оставленный на невлитой ветке, — это инструмент, которого нет. Правило есть, дисциплина неполная |
Чего в стеке нет и не будет в ближайшее время
- Микросервисов. Три сервиса — это разделение контуров доступа, а не архитектурный стиль. Дробить дальше на нашем масштабе — добавить сетевых отказов там, где сейчас вызов функции.
- Очередей и распределённых хранилищ. Масштаб не тот; порог, при котором они понадобятся, считается на салфетке и до него далеко.
- 1С. Не наш стек. Мы делаем контур вокруг него — обмен, документы, отчётность, — но расширения внутри 1С не пишем и не беремся оценивать. Это отдельная профессия.
- Собственной модели с нуля. Мы дообучаем готовые под свою задачу; обучение с нуля и классическое машинное обучение — не наш контур, и это записано в границах услуг.