
Как мы за 4 недели перенесли CRM проката на 318К строк на общую платформу
318К строк production-CRM аренды авто - за 4 недели на общую multi-tenant платформу. Операторы продолжали работать. Что реально шарится между SaaS-продуктами.
О чём этот кейс
Мы строим семейство SaaS-продуктов, которые шарят большую часть своих внутренностей. Вход, биллинг, общение с клиентами по каналам, аудит-лог, локализация, AI-ассистент, security-примитивы, rate-limiting - всё это generic. Мы написали это один раз как набор shared-пакетов и переиспользуем по продуктам. Каждый вертикальный продукт (CRM аренды авто, CRM недвижимости, e-commerce-бэкенд, ticketing мероприятий, операционка ресторана, платформа бронирования спортивных площадок и так далее) пишет только ту часть, которая действительно специфична для своей индустрии. Ставка в том, что большая часть SaaS-продукта - это substrate, который можно шарить, а небольшой, но реальный domain-специфичный слой - это то, что отличает одну вертикаль от другой.
Этот кейс про первый раз, когда мы доказали это на реальной кодовой базе. Мы взяли production-CRM аренды авто - 318 000 строк TypeScript, четыре года накопленной бизнес-логики, живые операторы, ведущие реальный прокатный бизнес - и за четыре календарные недели перенесли его на shared-платформу. Операторы продолжали пользоваться приложением всё это время. К концу 3-й недели они уже проводили свои реальные ежедневные бронирования через перестроенную версию.
Смысл этого письма не в самой engineering-работе. Смысл в ответе на реальный бизнес-вопрос: когда вы пытаетесь шарить код между очень разными продуктами, сколько вы реально экономите, и где шаринг разваливается?
Как выглядела миграция
До миграции: standalone CRM аренды авто. Годы кода, своя система входа, своя обёртка над биллингом, своя интеграция общения с клиентами через WhatsApp и Telegram, свой аудит-лог, свой rate-limiter. Всё это написано специально под продукт аренды, ничего из этого не переиспользуемо.
После миграции: те же 318 000 строк rental-специфичной бизнес-логики - машины, бронирования, тарифы, осмотры, штрафы, лояльность, отчётность для совладельцев парка - теперь сидят поверх shared-платформы. Вход, биллинг, общение с клиентами, аудит-лог, rate-limiter, локализация, AI-ассистент - всё это крутится на shared-пакетах, которые мы написали один раз и переиспользуем по продуктам.
Мы померили границу точно. Из 1872 строк схемы БД около 220 строк (12%) - generic multi-tenant substrate: таблицы пользователя, компании, роли, API-ключа; inbox общения с клиентами; аудит-лог. Около 1650 строк (88%) - действительно rental-специфичный домен: 67 разных таблиц для машин, бронирований, тарифов, осмотров и остального.
Это соотношение 12 vs 88 - ответ на бизнес-вопрос. 12% оказывается достаточно бойлерплейта, чтобы шаринг имел смысл - потому что каждый из наших 15 запланированных вертикальных продуктов иначе писал бы свою версию этих 12%. Написать один раз и переиспользовать по 15 продуктам - вот математика, которая оправдывает инвестицию в shared-платформу. 88% - часть, которая действительно отличается между арендой, недвижимостью и ресторанами, и пытаться её шарить было бы ошибкой.
Четыре недели по неделям
| Неделя | Что построено |
|---|---|
| 1 | Фундамент. Проект аренды зашёл в общий workspace. Пять обязательных multi-tenant таблиц БД выровнены под shared-пакет входа. Сам вход перепаян через shared-пакет. К концу недели валидатор подтвердил, что tenancy-слой соответствует спецификации платформы. |
| 2 | Shared-сервисы. Общение с клиентами через web, WhatsApp и Telegram через shared-пакет inbox. AI-ассистент-runner заменил локальную LLM-обёртку. Shared API-слой заменил локальный rate-limiter и error-reporter. К концу недели разговоры с клиентами текли через платформу end-to-end. |
| 3 | Операторские поверхности. Все 34 multi-tenant маршрута стабилизированы. Storefront-пресет, AEO-поверхность для видимости в AI-поиске, KPI-дашборд. Операторы пользуются перестроенным приложением для реальных ежедневных бронирований со среды. |
| 4 | Hardening. Payment-адаптеры интегрированы. API-поверхность для внешних AI-агентов (ChatGPT, Claude, кастомные) live для операций аренды. Персональные данные зашифрованы. Аудит-лог полностью подключён. |
Узким местом по всем четырём неделям был не engineering. Узким местом было то, что production-данные аренды постоянно противоречили нашим предположениям о том, как должна быть специфицирована shared-платформа. Трижды на 2-й неделе нам пришлось выбирать: переписать data-модель аренды под спецификацию платформы или переписать спецификацию платформы под данные аренды. Каждый раз мы переписывали спецификацию. Причина принципиальная: если спецификация платформы не выживает контакт с реальным, четырёхлетним production-SaaS - значит ошибается спецификация. Кодовая база аренды - эмпирический референс. Платформа, которая не несёт её чисто, ещё не платформа.
Что мы узнали про платформу
Три вещи изменились благодаря этой миграции. Одна из них оказалась самым важным архитектурным решением во всей платформе.
Проблема франшизы - и пятая multi-tenant таблица.
Мы специфицировали четыре обязательных multi-tenant таблицы: пользователи, компании, роли, связывающие их, API-ключи. Считали, что разрешения выводимы из назначения ролей. Кодовая база аренды имела франшизный паттерн, который ломал это допущение: конкретный Manager в конкретном филиале аренды нуждается в доступе к инвесторской отчётности. Роль Manager обычно его не даёт. Стандартная таблица ролей не могла это выразить.
Мы добавили пятую обязательную таблицу для per-company permission-override'ов. Каждый conforming-вертикальный продукт теперь её наследует. Триггер был rental-специфичным; решение - общее.
Общение с клиентами - выигрывает более богатая модель.
Наш shared-пакет общения с клиентами начался с более простой модели, которую использовали несколько других пилотов. Аренде нужна была более богатая семантика: несколько support-агентов, работающих с одним каналом одновременно, с правильным assignment'ом и handoff'ом. Мы оставили богатую модель. Другие пилоты мигрируют на неё в следующем цикле. Принцип: shared-пакет должен быть строгим супермножеством того, что нужно его потребителям. Если пилоту аренды нужна фича - shared-пакет её получает, и каждый другой пилот выигрывает.
Ценная случайность - сделать AI-агентов безопасными в использовании.
В спецификации нашей платформы есть секция, которую мы назвали obligations - бизнес-инварианты типа 'нет передачи машины, пока контракт не подписан и депозит не погашен' или 'нет возврата депозита, пока пост-арендный осмотр не записан'. Изначально мы относились к этому как к документации. Рантайм её не энфорсил; это должен был делать код приложения.
На 4-й неделе миграции мы вписали эти obligations прямо в API-слой, который вызывают внешние AI-агенты. Результат: когда ChatGPT или Claude вызывает наш API 'создать бронирование' от имени клиента, платформа автоматически отказывает агенту в пропуске контракта или депозита. Агенту даже не нужно знать, что правило существовало; рантайм возвращает структурированную ошибку, указывающую ровно на нарушенный бизнес-инвариант.
Эта часть работы, как оказалось, оказалась важнее всего. Выдача внешнему AI-агенту доступа к вашему API бронирований считается страшной частью agentic SaaS - агент перепутает правила, пропустит депозит, отдаст ключи без контракта, оставит вас с юридическими последствиями. Мы этого не позволяем. Правила энфорсятся слоем ниже агента, в коде, который агент не может обойти. Каждая вертикаль, которую мы теперь запускаем, получает этот safety-слой бесплатно. Мы не планировали так; миграция аренды нас заставила.
Где это сидит в более широком плане
inite.rent - один из 15 вертикальных SaaS-продуктов семейства Inite. Ещё две миграции крутятся в параллели прямо сейчас (недвижимость и e-commerce), четыре ещё в очереди (ticketing мероприятий, рестораны, путешествия, спортклубы). Каждая стартует из своей legacy-кодовой базы, но следует тому же миграционному паттерну, который установил кейс аренды. Шире про сам тезис «один движок, много обличий» — в материале «Один движок, много обличий», а про разделение спеки и рантайма — «Конституция vs рантайм».
Этот кейс - также worked example нашей 6-этапной методологии трансформации, применённой к собственной engineering-работе. Break аудит legacy-кода. Hold стабилизирует схему, чтобы у миграции были консистентные входы. Track инструментирует call-паттерны, чтобы поведение shared-платформы можно было сравнить с legacy. Cut удаляет локально-дублированный горизонтальный код, который теперь даёт shared-платформа. Cast выпускает rebuild к операторам, использующим его ежедневно. Form - три месяца тюнинга границ после, давшие форму платформы, что у нас сейчас.
Методология работает на engineering-работе так же, как на B2B-automation-engagement с платящими клиентами. Это само по себе полезное доказательство. Методология - не продажный документ; мы прогоняем её на самих себе.
Одним предложением
318 000-строчный rebuild CRM аренды авто доказал, что наша shared multi-tenant платформа может нести реальный, четырёхлетний, production-SaaS-продукт; миграция откалибровала, что в платформе было сделано правильно, что неправильно, и произвела случайный safety-слой для agentic API-вызовов, который, мы ожидаем, будет единственным самым ценным куском платформы в ближайшие два года.
Аренда настоящая. 318 000 строк настоящие. Миграция - референс для того, что идёт дальше.
01Чем это отличается от обычного pnpm workspace или Nx-монорепо?+
Workspace даёт code sharing. Нам нужен был контракт поверх code sharing. Shared-пакеты декларируют, что каждая потребляющая вертикаль обязана реализовать (пять tenancy-таблиц, модель inbox, форму API-обёртки). Валидатор крутится в CI и валит build, если вертикаль уплывает от контракта. Без него семья из 15 продуктов тихо форкает shared-пакеты за полгода, и вы получаете 15 maintenance-нош вместо одной. Контракт - то, что останавливает drift.
02Что делать, если вертикали нужно то, чего ещё нет в shared-платформе?+
Два пути. Если потребность явно generic (нужно нескольким вертикалям) - она уходит в shared-платформу в следующем релизе, а вертикаль ждёт. Если потребность действительно industry-специфична - остаётся в коде самой вертикали. Решение принимается против shared-спеки, а не тем, кто громче кричит. Сам пилот аренды дал три добавления в shared-платформу; будущие вертикали дадут другие. Платформа растёт от спроса из production-использования, не от спекуляций.
03Как вы делаете breaking-изменения в shared-платформе, не сломав 15 продуктов?+
Semantic versioning на каждый shared-пакет плюс contract-валидатор, сравнивающий декларированную версию зависимости вертикали с тем, что она реально использует. Breaking-изменения уезжают в мажорную версию, требующую явной миграции; вертикали на старой мажорке продолжают работать, пока не мигрируют. Валидатор ловит drift в CI. Самое сложное - не тулинг, а дисциплина отказывать в one-off патчах, которые помогли бы одной вертикали, но скосили контракт для остальных 14.
04Почему просто не использовать Stripe, Auth0, Twilio вместо построения собственного substrate?+
Мы используем внешних провайдеров там, где они явно лучшие: Stripe для card-процессинга, Twilio для SMS, third-party LLM для assistant-runner'а. Shared-платформа - слой над ними: multi-tenant data-модель, мапящая Stripe-клиента на одну из наших компаний, граф разрешений, решающий, кто видит какой inbox, аудит-лог, выживающий между провайдерами. Внешний SaaS решает проблемы одного вендора; платформа решает cross-cutting concerns, не принадлежащие ни одному вендору. Оба слоя реальны, и оба нужны.
05Могут ли другие компании использовать эту shared-платформу или это только Inite?+
Только Inite пока что. Платформа откалибрована под конкретную форму SaaS-продуктов, которые мы строим - multi-tenant B2B с AI-ассистентом, agentic API-поверхности, наша конкретная модель safety-правил. Универсальная shared SaaS-платформа потребовала бы других tradeoff'ов (более слабые контракты, больше конфигурации, более медленные релизы), и мы не строим её. Интересный переиспользуемый артефакт этой работы - не код, а методология: 6-этапный протокол трансформации и паттерн contract-then-share. Оба документированы и применимы вне семьи Inite.


