dmilyin.ru AI-разработка · Нижний Новгород · обновлено 2026-07-09


Кейсы Свой продукт

Fullstack CRM enterprise-класса в одиночку: Laravel + Vue, DDD-архитектура, real-time и 1338 автотестов

Собрал production-CRM с нуля один: 6 предметных областей, DDD, Octane, поиск на Meilisearch, real-time на WebSocket, 1338 зелёных тестов и два интерфейса из одного кода.

Обложка кейса: Fullstack CRM enterprise-класса в одиночку: Laravel + Vue, DDD-архитектура, real-time и 1338 автотестов

Задача

Коробочная CRM (Bitrix24, amoCRM) закрывает 60–70% типовых процессов, а на остальное упирается в ограничения платформы: свою логику воронки, нестандартные роли, глубокую интеграцию с учётом. Тогда бизнес идёт в заказную разработку — и получает счёт от студии на команду из 5–7 человек и полгода работы.

Apex Pulse — мой демонстрационный ответ на вопрос «а что реально закрывает один разработчик с AI-first процессом». Это не CRUD-заготовка для показа рекрутёру, а система, спроектированная как production: с чистой архитектурой, нагрузочными паттернами и полным тестовым покрытием. Задача была доказать не «умею писать код», а «веду сложный продукт целиком — от архитектуры до интерфейса — в одного».

Решение

В основе — прагматичный DDD: домен разбит на 6 ограниченных контекстов (Identity, Customer, Sales, Task, Analytics, Notification). Бизнес-логика живёт в rich-моделях Eloquent с Value Objects (Money, Email, PhoneNumber, PersonName), доступ к данным — через Repository (интерфейсы в Domain, реализации в Infrastructure), побочные эффекты (уведомления, индексация поиска, audit log) развязаны через Domain Events. Без ритуального event sourcing — стандартная персистентность плюс события там, где они реально нужны.

Рантайм — Laravel Octane на Swoole: приложение держится в памяти между запросами, 4 воркера + 6 task-воркеров. Это накладывает дисциплину — никаких синглтонов с мутабельным состоянием, иначе状ошибки утекают между запросами. Кэш многослойный: L1 в памяти Octane, L2 в Redis, инвалидация по тегам. Поиск — Meilisearch через Laravel Scout (мгновенные результаты, опечатки, асинхронная переиндексация очередями). Кастомные поля клиентов — на JSONB с GIN-индексом, чтобы не плодить миграции под каждый бизнес. Real-time (изменения сделок, задач, уведомления) — Laravel Reverb по WebSocket. Авторизация — OAuth2 через Passport, права — 5 ролей на Spatie Permission, soft delete и audit log на всех сущностях.

Фронтенд — Vue 3 + TypeScript: серверное состояние на TanStack Query, клиентское на Pinia, интерфейс на shadcn-vue + Tailwind. Kanban сделок с drag&drop, merge-wizard контактов, дашборды с ApexCharts и PDF-экспортом, импорт/экспорт CSV/XLSX с маппингом полей и дедупликацией, командная палитра Cmd+K, i18n (ru/en), тёмная тема. Весь стек в Docker, CI/CD на GitHub Actions.

Собрано это в AI-first процессе: Claude Code как исполнитель внутри выстроенного контекста (спецификация, правила проекта, TDD — тесты вперёд), а архитектурные решения, ревью и ответственность — за мной. Это ровно та граница, которая отличает инженерию от вайб-кодинга.

Архитектура Apex Pulse: Vue 3 + TypeScript, слои Laravel 11 на Octane (Interfaces, Domain из 6 контекстов, Infrastructure), PostgreSQL, Redis, Meilisearch и real-time через Reverb

Отдельный слой — интерфейс. Стартовый вид на дефолтном shadcn-vue технически чист, но визуально безымянен. Довёл его до продуктового не руками в CSS, а через AI-агента: связал студию дизайна (Open Design) с Claude Code по MCP и переписал визуальный слой по дизайн-системе. Причём дважды — нейтральный Linear и биржевой terminal — из одного и того же кода, без единой правки бизнес-логики, роутинга и тестов. Разбор этой связки вышел отдельной статьёй на vc.ru (ссылка ниже).

Дашборд CRM в дизайн-системе terminal: метрики, тренды выручки и win rate, real-time тикер

Kanban-доска сделок: стадии воронки от лида до закрытия, суммы и вероятности на карточках

Дизайн «до/после»: один и тот же дашборд в дефолтном светлом виде и после редизайна в системе terminal — без правок бизнес-логики

Стек

PHP 8.3 · Laravel 11 · Octane (Swoole) · Vue 3 · TypeScript · PostgreSQL 16 · Redis 7 · Meilisearch · Laravel Reverb · Passport (OAuth2) + Spatie Permission · shadcn-vue / Tailwind · ApexCharts · Docker · GitHub Actions · Pest / Vitest / Playwright

Результат

Что собрано в одиночку:

  • ~35 000 строк собственного кода (backend-приложение + фронтенд + тесты), 6 ограниченных контекстов, 28 миграций.
  • 1338 автотестов зелёные: 856 Pest (backend), 464 Vitest (frontend), 18 Playwright (E2E). Тесты писались вперёд кода (TDD).
  • Полный CRM-контур: контакты/компании, воронка сделок с Kanban, задачи с чеклистами и @упоминаниями, аналитика с дашбордами и PDF, импорт/экспорт, поиск, real-time, RBAC, audit log, две темы, два языка.
  • Редизайн через AI-агента — 13 и 15 изменённых файлов фронта, ноль правок логики, все тесты зелёные: два разных интерфейса из одной кодовой базы.

Отраслевой ориентир (2026, РФ): система такого класса на рынке — это заказная CRM. Стандарт с ролями, аналитикой и интеграциями студии оценивают в 700 000 – 1 500 000 ₽ и команду 3–5 человек на 3–5 месяцев; по архитектуре (DDD, Octane, real-time, полное тестовое покрытие) Apex Pulse тянет на enterprise-сегмент, который агентства считают от 6 млн ₽ и команду 5–7 человек. Здесь это закрыто одним разработчиком с AI-first процессом.

Что не сработало

  • Highload-цифры — это проектные цели, не боевые замеры. В PRD заложены P95 < 100 мс и 10 000+ одновременных пользователей, архитектура (Octane, многослойный кэш, очереди) под это и строилась. Но продукт демонстрационный и в проде под реальной нагрузкой не крутится — честно говорю про целевые ориентиры, а не про измеренный SLA.
  • Первый прогон дизайна дал слабый контраст. Нейтральная дизайн-система (Linear) по духу близка к дефолтному shadcn-vue — «до/после» вышло вялым. Заметная разница появилась только на характерной системе (terminal). Вывод, который забрал: итог задаёт выбор эталона, а не сам инструмент.
  • Грабли Tailwind v4 при редизайне. Голый класс border в v4 берёт цвет текста, а не токен границы — карточки обводились почти чёрным, как wireframe. Лечится одной строкой базового правила, но пока не поймёшь причину — выглядит как баг агента. Это особенность версии фреймворка, доводил руками.