Софт, на якому працює ваш бізнес

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

Масштаб: від однієї таблиці до системи доменів

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

  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. Додаток створено таким чином, щоб робота залишалася звичайною.

Так, і це частий спосіб почати. Спочатку — прочитати код і чесно сказати, в якому він стані. Іноді відповідь: він у порядку, і потрібні три правки, а не переписування.

Так, для всього великого це звичайна схема. Великі збірки йдуть до фахівців, з якими я працюю роками, а хто яку частину зробив — написано на сторінці кожного проєкту.

Ні. Це інструментарій, яким усе зібрано, — як несуча система будівлі, про яку ніхто всередині не думає. Важливо інше: чи є у вашого процесу правила, які справді ваші.

Одне чесне застереження, бо від нього залежить, чи стосується вас усе вище. Замовний софт — найдорожчий спосіб розвʼязати задачу, яку вже розвʼязує готове, і у двох найчастіших випадків відповідь в іншому місці цього сайту.

Заповнити бриф

Це перший прохід по правилах вашого процесу — кліками, а не твором. Обовʼязкові лише контакти; решту можна пропустити, і тоді я спитаю це голосом. Відповісте на все — дзвінок почнеться з середини розмови.

    Де облік живе зараз

    З чим стикуватися

    Код