Софт, на котором работает ваш бизнес

Правила вашего процесса, записанные так, что их нельзя нарушить незаметно: записи, состояния, переходы, роли — и тесты, которые падают, когда одно из правил изменилось.

Масштаб: от одной таблицы до системы доменов

Четыре масштаба по возрастанию: одна сущность с валидацией и ролями; состояния и переходы; конкурентный доступ с блокировками и журналом изменений; домены со своими таблицами, правами и тестами.

  1. Один экран вместо таблицы

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

  2. Процесс с состояниями и переходами

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

  3. Бэк-офис, в котором работают несколько человек сразу

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

  4. Много модулей, одна система

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

Трудоёмкость следует не за числом правил, а за тем, как они пересекаются. Одно правило на миллион строк — это дни. Четыре правила, которые в конце месяца противоречат друг другу, — это недели, сколько бы экранов над ними ни было.

  • Согласования

    Состояние-шлюз: запись не переходит дальше без подтверждения, а политика решает, чьё подтверждение требуется.

  • Роли и права

    Контроль доступа на уровне строк, а не пунктов меню. Скрытая ссылка — это не авторизация.

  • Расписание и брони

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

  • Правила цены

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

  • Документы

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

  • Системы, которые уже работают

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

  • Отчёты

    Агрегация чанками по индексированным колонкам: закрытие месяца не держит блокировок на таблицах, в которые идёт запись.

  • Склад как журнал

    Журнал движений append-only: каждая операция — неизменяемая запись, остаток — агрегат по журналу, а не перезаписываемое поле.

  • Кадровый учёт

    Приёмы, отсутствия и табели — события; итоги — производные агрегаты, пересчитываемые из журнала, а не хранимые вручную.

  • Воронка

    Лиды, сделки, обращения — конечный автомат; сроки — триггеры планировщика, а не поля для отображения.

  • API

    Версионирование, аутентификация на клиента, rate limiting. Спецификация генерируется из тех же определений, что и маршруты.

  • Унаследованный код

    Приёмка чужой кодовой базы на Laravel: характеризационные тесты фиксируют текущее поведение до любых изменений.

Система, построенная, чтобы найти предел

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

  1. Двенадцать доменов, одна кодовая база

    Контент, клиенты, документы продаж, омниканальный inbox, тендеры, склад, кадры и зарплата, найм, финансы, налоги, внутренние задачи и платформенное ядро. У каждого домена свои таблицы и свои права — эта граница и не даёт изменению в одном отозваться в другом.

  2. Админка, в которой можно работать

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

  3. Каждая долговечная таблица рассчитана на объём, которого ещё нет

    Индексы, пагинация, обработка чанками и путь архивации закладываются при создании таблицы, а не когда она замедлилась. Файлы раскладываются так, чтобы в одном каталоге не копились тысячи соседей; журнал движений сворачивается в месячные агрегаты и вычищает заменённое.

  4. Тестовый набор — вторая половина документации

    Почти каждая записанная ошибка называет тест, который не даёт ей вернуться. Набор гоняется на двух СУБД, включая ту, что стоит в продакшене, и оба прогона должны пройти до принятия изменения.

  5. Задокументировано по ходу сборки

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

В архиве 2 опубликованных проекта на Laravel, и на каждом из них указанное авторство разработки принадлежит другому человеку — я был там арт-директором, это написано на страницах проектов. Система выше — та, где код мой; на ней эта страница и стоит.

Проекты на Laravel2

Когда двое нажали одновременно

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

  1. Гонка за последнюю единицу

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

  2. У денег и склада ровно один вход

    Каждое движение денег или склада пишется через единственный путь, и записать его дважды невозможно, а не маловероятно: у записи есть отпечаток содержимого, уникальный индекс исключает дубль, а отказ базы перехватывается как страховка для гонки, пришедшей первой. Количества и суммы — decimal, а не float; исправление — зеркальная сторнирующая запись, а не правка задним числом.

  3. Долгая задача не должна запуститься дважды

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

  4. Пропущенная минута перепланируется, а не теряется

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

Пример помельче. Адрес Telegram-бота содержит его собственный токен, поэтому упавшее соединение цитирует живой секрет в тексте ошибки, а стандартный отчёт пишет его прямо в лог. Решение — редактор, который включается только тогда, когда сбой действительно несёт токен; всё остальное сохраняет исходный stack trace. Большая часть этой работы — такого размера.

На чём написано и что запускается на каждый пуш

Названо прямо, с версиями. Каждая строка прочитана из конфигурации референсной системы — composer.json, настроек очередей, CI-сценария — и любую можно проверить, попросив файл-источник.

  1. PHP 8.5 и Laravel 13

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

  2. Админка — Filament, и это решение, а не срезанный угол

    Экраны объявлены в PHP: их ревьюят по диффу и тестируют наравне с остальным кодом, а не хранят в конструкторе, который нельзя прочитать. Это покупает время на бизнес-правила вместо виджетов таблиц. Чего это не покупает — бокового меню с пунктом на каждую таблицу: панель разбита на девятнадцать рабочих пространств, двенадцать из них — настройки, а не один экран на сто полей.

  3. Девять именованных очередей на Redis, шесть групп воркеров под Horizon

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

  4. Живой push — Reverb там, где он нужен; опрос там, где нет

    Транспорт — настройка, по умолчанию polling: для панели, где отставание на пару секунд ничего не стоит, это правильный дефолт. Чат оператора — случай, которому нужен настоящий push, и там работает websocket-сервер самого фреймворка, а не платный брокер: минус один аккаунт, минус один счёт, минус один сбой, который отсюда не починить.

  5. Права, медиа, настройки, вебхуки и контроль расписания — пакеты

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

  6. Каждое изменение проверяется тремя способами на сборочной машине

    Стиль кода, статический анализ, тестовый набор — и набор гоняется дважды, на SQLite и на MySQL: запрос, который проходит на одном движке и падает на другом, — ошибка, которая иначе всплывёт на боевом сервере. Все три идут на раннере, а не на моём ноутбуке, поэтому результат можно попросить показать.

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

Сначала правила, потом код

  1. Разговор о процессе, а не о софте

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

  2. Записано и согласовано

    Записи, их состояния, кто вправе их двигать и что должно быть выполнено до перехода. По этому документу считается оценка, и он остаётся вашим независимо от того, начнётся ли сборка.

  3. Первый срез: одна операция от начала до конца

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

  4. Дальше — модулями, в порядке окупаемости

    Модули сдаются по одному, и каждый рабочий в момент сдачи. Ничто не ждёт всего остального.

  5. Передача — список, а не переговоры

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

Перед обращением

Возможно, и стоит принять это решение прежде всего. Страницы, которые правит ваша команда → WordPress. Процесс, на котором держится бизнес → Laravel. Если речь идёт о первом варианте, то описание этой работы находится в странице про WordPress.

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

Первый работающий срез — обычно недели. Система из нескольких модулей — месяцы, и приходит она по модулю, а не вся в конце. Тот, кто называет одно число до записанных правил, их не читал.

Это отдельная задача — серверы, резервное копирование, мониторинг и выпуск релизов — и она описана на страница DevOps. Приложение разработано таким образом, чтобы работа оставалась обычной.

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

Да, для всего крупного это обычная схема. Большие сборки идут к специалистам, с которыми я работаю годами, а кто какую часть сделал — написано на странице каждого проекта.

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

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

Заполнить бриф

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

    Где учёт живёт сейчас

    С чем стыковаться

    Код