Laravel архитектура edplace: аудит кода и риски
Открываешь чужой репозиторий - и первые десять минут уходят не на код, а на попытку понять: с чем вообще имеешь дело. Именно так у меня началась работа с edplace: формулировка "надо разобраться и будем дорабатывать", незнакомая структура папок, bootstrap/app.php открыт в редакторе. Laravel архитектура этого проекта сразу задала вопросы - крепкий продукт или технический долг, замаскированный под рабочий код? Ниже честный разбор того, что внутри, какие риски реальны и что нужно учесть перед тем, как начинать что-то дорабатывать.
Контекст: что такое edplace
edplace — это образовательная платформа с несколькими доменами: авторы создают курсы и сообщества, ученики покупают доступ, платформа управляет выплатами, уведомлениями, геймификацией и приватностью. Это не учебный проект и не MVP на коленке — это живой продукт с реальными деньгами, реальными пользователями и требованиями 152-ФЗ.
Стек: Laravel (PHP), tymon/jwt-auth для аутентификации, Laravel Horizon для очередей, Laravel Reverb для WebSocket/realtime, PostgreSQL как основная БД, Redis для кешей и очередей. Фронтенд отдельно — в рамках этого аудита не рассматривался.
Первое впечатление: это не помойка
Первое, что бросается в глаза при чтении кода — дисциплина. Строгая типизация везде, где это возможно. Транзакции с блокировками на денежных операциях. Продуманные rate-limit'ы на чувствительных эндпоинтах. PII-редакция под 152-ФЗ реализована через .semgrep/edplace-152fz.yml и .gitleaks.toml — это не заглушки, а рабочие правила, которые реально проверяют код в CI.
Гит-история тоже говорит о многом. Из ~1476 коммитов (без merge) около 22% — это fix:-коммиты. Много? Зависит от контекста. Норма по индустрии — 10–15%, здесь чуть выше. Но важнее другое: команда пишет осмысленные сообщения коммитов (fix(auth): guide duplicate email recovery, fix(payment): return PayKeeper buyers to purchase context), разделяет домены, документирует намерения через docs:-коммиты. Это признак зрелой команды, а не хаоса.
Аутентификация и безопасность
Аутентификация построена на tymon/jwt-auth с несколькими guard'ами. Основной — api-product, плюс дополнительные под разные контексты. JWT настроен с разумными TTL и refresh_ttl, blacklist включён — это важно, потому что без blacklist отозвать токен невозможно.
Middleware-слой проработан: есть кастомный UserJwtMiddleware, политики (Policies) разложены по доменам (app/Domain/*/Policies/*), CORS настроен через config/cors.php. Сессии используются минимально — в основном для OAuth-флоу и некоторых административных маршрутов.
Что реально хорошо: на денежных операциях стоят транзакции с SELECT FOR UPDATE — это защита от race condition при параллельных запросах. PayKeeper-коллбэки проверяют подпись через PayKeeperValidator::isValid() — это не заглушка, это реальная валидация.
Что стоит проверить при доработке: политики авторизации покрывают не все эндпоинты равномерно. Есть зоны, где проверка прав делается вручную в контроллере, а не через Policy — это потенциальное место для IDOR при добавлении новых маршрутов. Не критично сейчас, но при расширении API нужно держать в голове.
Асинхронщина: очереди и realtime
Это самая интересная часть с точки зрения архитектуры. Проект использует Laravel Horizon для управления очередями на Redis и Laravel Reverb для WebSocket-соединений.
Очередей несколько, разделены по приоритетам и типам задач: уведомления, выплаты, геймификация, email-рассылки. Это правильный подход — смешивать критичные финансовые задачи с маркетинговыми письмами в одной очереди было бы плохой идеей.
По идемпотентности: большинство Job'ов написаны с учётом повторного запуска — проверяют состояние перед выполнением, используют уникальные ключи. Но есть исключения, особенно в геймификации — там несколько Job'ов могут дублировать начисление очков при retry. Это не катастрофа, но при высокой нагрузке или нестабильном Redis может дать неожиданные результаты.
Realtime через Reverb — относительно новый инструмент в экосистеме Laravel (появился в 2024). Это хорошо с точки зрения поддержки, но означает, что community-решений для нестандартных кейсов меньше, чем для Pusher или Socket.io.
Главная боль: «чиним одно — ломается другое»
Это была ключевая проблема, с которой меня подключили. И это не субъективное ощущение команды — это измеримое свойство кодовой базы.
Причина номер один: низкое покрытие тестами на контрактном уровне. При ~200 эндпоинтах в API — около 6 контрактных/регрессионных тестов. Это означает, что изменение в одном домене не имеет автоматической проверки того, что соседние домены не сломались.
Причина номер два: доменная связность выше, чем кажется. Домены (Auth, Payment, Subscription, Community, Gamification, Notification) выглядят изолированными по структуре папок, но на уровне событий (Events) и слушателей (Listeners) между ними много неявных зависимостей. Изменение в Subscription может триггернуть цепочку: Notification → Gamification → WebPush. Эта цепочка нигде не задокументирована явно.
Причина номер три: часть fix-коммитов меняет app/ без единого теста — около 16% от fix-коммитов. Это не вина команды, это системная проблема: когда тестов мало, писать их к каждому фиксу кажется избыточным. Но именно это и создаёт регрессии.
Что делать: перед любой доработкой — написать интеграционный тест на затрагиваемый флоу. Не unit, а именно интеграционный — от HTTP-запроса до ответа. Это единственный способ зафиксировать текущее поведение и не сломать его.
Telegram-интеграция: четыре разных проекта
Одна из обсуждаемых идей — сделать платформу «Telegram-first». Звучит как одна задача, но на самом деле это четыре принципиально разных архитектурных решения:
Уровень A: Telegram как канал уведомлений. Бот шлёт сообщения, deeplink ведёт в веб. Минимальные изменения в бэкенде — добавить Notification-канал, сохранить telegram_chat_id в профиле (поле telegram_link уже есть). Оценка сложности: 2–3 недели.
Уровень B: Telegram Mini App как клиент. Весь кабинет ученика живёт внутри Telegram WebApp. Веб-приложение адаптируется под Mini App SDK. Бэкенд почти не меняется, но фронтенд требует серьёзной переработки. Оценка: 1–2 месяца.
Уровень C: Двусторонняя работа. Действия в Telegram синхронизируются с веб-платформой в реальном времени. Здесь начинаются архитектурные вопросы: как синхронизировать состояние? Через Reverb? Через polling? Это уже нетривиально.
Уровень D: Платежи через Telegram. Конфликт с текущим PayKeeper-флоу. Telegram Payments работают иначе, требуют отдельного провайдера, и интеграция с существующей логикой выплат авторам — это отдельный проект.
Важно: в текущем коде Telegram-интеграции нет вообще. Только строковое поле telegram_link в профиле. Это гринфилд — с одной стороны, нет легаси, с другой — нет готовых паттернов, на которые можно опереться.
Для реализации бота рекомендую смотреть в сторону Nutgram — это современный PHP-фреймворк для Telegram-ботов, хорошо интегрируется с Laravel. Альтернатива — telegram-bot-sdk.
Где могут возникнуть трудности при доработке
Честный список рисков, которые я вижу:
1. Домен Notification. Это самый запутанный домен. Есть платформенные email, есть авторские email, есть WebPush, есть in-app уведомления. Логика «кому и что отправлять» размазана между несколькими классами. Недавний коммит fix(notification): stop platform email duplicates говорит о том, что дубли уже были проблемой. Любое изменение здесь требует особой осторожности.
2. Выплаты и ФНС. Домен Payout работает с реальными деньгами и интеграцией с ФНС (для самозанятых). Коммит fix(payout): отличать сбой ФНС от отсутствия НПД показывает, что граничные случаи здесь нетривиальны. Трогать этот домен без глубокого понимания бизнес-логики — опасно.
3. Геймификация и лидерборды. Логика «кто считается участником» менялась недавно (fix(gamification): лидерборд — только принятые и оплатившие участники). Это значит, что бизнес-правила здесь живые и могут меняться. Тесты на этот домен нужны в первую очередь.
4. Конкурентность при высокой нагрузке. Транзакции с блокировками есть, но не везде. При масштабировании (больше воркеров Horizon, больше параллельных запросов) могут вылезти race condition в местах, где блокировок нет.
Что сделано хорошо и на что опираться
Несмотря на риски, есть вещи, которые сделаны правильно и на которые можно опираться при доработке:
- Доменная структура (
app/Domain/*) — код организован логично, найти нужное место несложно - Строгая типизация — PHPStan настроен, это ловит целый класс ошибок на этапе CI
- Транзакции на деньгах — финансовая логика защищена от race condition
- Семантические коммиты — история читаемая, можно понять намерение каждого изменения
- Безопасность конфигов —
.gitleaks.tomlи semgrep-правила реально работают
Выводы
edplace — это зрелый продукт с понятной архитектурой и дисциплинированной командой. Это не тот случай, когда приходишь и первым делом думаешь «надо всё переписать». Здесь можно и нужно работать итеративно, опираясь на существующие паттерны.
Главный урок, который я вынес из этого аудита: метрики git-истории говорят больше, чем беглый просмотр кода. 22% fix-коммитов — это не катастрофа, но это сигнал. Сигнал о том, что тестовое покрытие нужно наращивать параллельно с фичами, а не после.
Второй урок: «Telegram-first» — это не одна задача. Прежде чем браться за интеграцию, нужно чётко договориться с командой и бизнесом: какой именно уровень интеграции нужен? Уведомления — это одна история. Полноценный Mini App — совсем другая. Смешивать их в одном спринте — верный путь к тому, что «чиним одно, ломается другое».
Третий урок: домен Notification — это первое место, куда нужно добавить тесты. Он самый запутанный, самый связный с другими доменами и самый часто меняющийся. Интеграционные тесты на основные флоу уведомлений дадут уверенность при любых последующих изменениях.
Четвёртый урок: аудит чужого проекта — это инвестиция, а не трата времени. Потратить неделю на понимание архитектуры перед тем, как начать писать код — это экономия месяцев на исправление регрессий. edplace в этом смысле оказался приятным сюрпризом: документация намерений через коммиты и доменная структура сильно ускорили погружение.
Дальше — писать тесты, договариваться о scope Telegram-интеграции и начинать с малого: бот как канал уведомлений, без переработки фронтенда. Это даст реальную ценность пользователям и позволит команде понять паттерны интеграции перед тем, как браться за более амбициозные уровни.
Полезные ссылки

AI-инженер, предприниматель, маркетолог. Основатель feberra.com и x10seo.ru. 13 лет в перфоманс-маркетинге, 3 года в системной интеграции AI в бизнес.