Выбор между модульным монолитом и микросервисами в PHP не определяется количеством модулей, возрастом кода или популярностью архитектуры. Корпоративное приложение может устойчиво расти в рамках единого развёртывания, если сохраняет чёткие границы. И наоборот, преждевременное разделение может превратить простые внутренние вызовы в сеть контрактов, очередей, повторных попыток и проблем координации.
Полезный вопрос состоит не в том, «нужно ли нам использовать микросервисы?», а в том, «какая бизнес-функция должна развиваться, отказывать, развёртываться или масштабироваться независимо и можем ли мы принять затраты на её эксплуатацию в таком виде?». Ответ должен исходить из домена и реальной эксплуатации, а не из целевой диаграммы.
Функциональный рост не требует отдельных сервисов

Добавление интеграций, асинхронных процессов или продуктовых областей не означает, что у каждой из них должен быть собственный сервис. Монолит может содержать чётко разграниченные модули, фоновые задачи, очереди и адаптеры для внешних систем, не теряя операционной согласованности.
Первым шагом обычно становится снижение внутренней связанности. Если модуль биллинга напрямую импортирует классы заказов, изменяет их таблицы или знает внутренние правила учёта запасов, перенос кода в другой репозиторий не исправит проблему автоматически. Она лишь превратится в сетевую связанность и связанность данных.
Модульный монолит предполагает, что каждая бизнес-функция имеет явный внутренний интерфейс, направленные зависимости и собственные правила. В PHP это может быть реализовано через пространства имён по доменам, контракты приложения, тонкие контроллеры, определённые сценарии использования и адаптеры для хранения данных или внешних API. То, что всё развёртывается вместе, по-прежнему совместимо с такими границами.
Что проанализировать перед изменением архитектуры
Прежде чем обсуждать технологии, определите бизнес-функции: например, управление заказами, каталог, идентификацию, биллинг, обработку документов или уведомления. Бизнес-функция не обязательно равна сущности или экрану; она объединяет правила и решения, которые должны меняться по схожим причинам.
- Владелец: определите, кто поддерживает правила, расставляет приоритеты изменений и отвечает за инциденты.
- Данные: выясните, какую информацию создаёт и контролирует каждая бизнес-функция, кто может её изменять и какие перекрёстные чтения ей нужны.
- Критические потоки: опишите прохождение значимой операции, включая проверки, внешние зависимости и асинхронные шаги.
- Темп изменений: отделите частые изменения от разовых. Одной частоты недостаточно; важно, вынуждает ли она координировать команды или релизы.
- Профиль нагрузки: отделите интерактивный трафик от задач, интенсивно использующих CPU, память, хранилище или вызовы сторонних систем.
- Влияние сбоя: установите, что происходит, если бизнес-функция деградирует на минуты или часы, и может ли основная часть бизнеса продолжать работу.
Этот перечень выявляет зависимости, которые часто остаются скрытыми: общие транзакции, прямые запросы к чужим таблицам, дублирующиеся правила в контроллерах и запланированные задачи, обновляющие несколько доменов. Выделение без их устранения создаёт формально разделённые, но функционально переплетённые сервисы.
Шесть признаков, что стоит сохранить модульный монолит
Эти признаки говорят в пользу укрепления внутреннего дизайна, а не распределения ответственности:
- Изменения обычно затрагивают несколько модулей. Если бизнес-функция требует согласованно изменить заказы, цены и биллинг, разделение может умножить количество развёртываний и контрактов.
- Немедленная согласованность имеет ключевое значение. Когда операции нужна одна транзакция базы данных для сохранения критических инвариантов, распределённая граница добавляет сложные решения по компенсации.
- Команда небольшая или разделяет владение. Несколько сервисов требуют эксплуатационной дисциплины, дежурств, конвейеров, версий и диагностики для каждой единицы.
- Нагрузка масштабируется схожим образом. Если компоненты растут в одном темпе и нет изолируемого узкого места, разделение не даёт явного преимущества.
- Границы домена пока нестабильны. Выделение бизнес-функции, пока её правила, словарь и ответственность постоянно меняются, закрепляет преждевременную границу.
- Наблюдаемость и автоматизация ограничены. Без структурированных логов, метрик, трассировок, алертов и повторяемых развёртываний каждый сетевой переход сделает расследование инцидента дороже.
Сохранить монолит не означает принять глобальный код. Цель в том, чтобы модуль мог развиваться с логической автономией, даже если он разделяет процесс, репозиторий и релиз с другими.
Шесть признаков, оправдывающих независимый сервис
Выделение более обоснованно, когда сочетается несколько из этих условий, а не появляется только одно:
- Есть ограниченная и понятная ответственность. У сервиса есть конкретная миссия, связные правила и собственный доменный язык.
- Он может владеть своими данными. Он управляет своим хранилищем и предоставляет согласованные операции, события или запросы, вместо того чтобы разрешать прямой доступ к своим таблицам.
- Ему действительно нужны независимые развёртывания. Его цикл изменений должен развиваться без координации каждого релиза с ядром.
- Его нагрузка отличается. Процесс конвертации, поиска, генерации файлов или ресурсоёмких вычислений может требовать иного масштабирования и ресурсов.
- Его сбой можно изолировать. Система может явно деградировать, если эта бизнес-функция не отвечает, используя повторные попытки, состояния ожидания или отложенную работу.
- Есть достаточное операционное владение. Команда или ответственный может поддерживать его жизненный цикл, алерты, инциденты, безопасность и совместимость.
API само по себе не превращает модуль в микросервис. Независимость также зависит от данных, развёртывания, эксплуатации и способности принимать решения, не завися от внутренних деталей другого приложения.
Затраты, возникающие при разделении ответственности
Вызов функции отказывает иначе, чем HTTP-вызов, сообщение в очереди или удалённый запрос. После выделения появляются задержки, тайм-ауты, аутентификация между сервисами, ограничения частоты запросов, частичная недоступность и несовместимые версии.
Также меняется модель согласованности. Если один сервис подтверждает операцию, а другой не получает или не обрабатывает событие, необходимо решить, как выявлять состояние, повторять попытки без дублирования эффектов и компенсировать при необходимости. Потребители событий должны быть идемпотентными: например, двукратная обработка сообщения не должна выпустить два документа или дважды списать средства.
Эксплуатация становится сложнее: корреляция запросов, распределённые трассировки, панели метрик, хранение логов, управление секретами, политики резервного копирования и тесты восстановления. Кроме того, каждому контракту нужны правила совместимости. Добавление необязательных полей обычно менее разрушительно, чем изменение семантики, удаление полей или повторное использование статуса с новым значением.
Переходная архитектура внутри PHP
Путь с наименьшим риском — модульная организация до выделения. Определите слой приложения для каждой бизнес-функции со сценариями использования, которые принимают команды или запросы и возвращают чётко определённые результаты. Скройте доступ к базе данных за репозиториями или портами, когда это представляет значимую зависимость; не превращайте каждый класс в абстракцию без цели.
Остальная часть монолита должна использовать модуль через его публичный внутренний интерфейс, а не через его сущности или таблицы. Если нужны асинхронные уведомления, публикуйте доменные или интеграционные события из контролируемой точки. Паттерн transactional outbox может помочь зафиксировать бизнес-изменение и ожидающее событие в одной транзакции, чтобы последующий процесс надёжно его доставил.
Этот этап позволяет проверить, реальна ли граница. Если внутренний интерфейс непрерывно растёт, требует приватных объектов других модулей или нуждается в общих транзакциях в каждом сценарии использования, это пока не сильный кандидат на разделение.
Как определить первую границу сервиса
Первый сервис должен иметь легко объяснимую ответственность и ограниченную зависимость от ядра. Задокументируйте четыре элемента до написания инфраструктуры:
- Ответственность: какие решения он принимает и что явно остаётся вне его границ.
- API или события: входы, выходы, ошибки, аутентификация, временные ограничения и идемпотентность.
- Владение данными: что он хранит, какие внешние идентификаторы сохраняет и какую информацию запрашивает через контракты.
- Совместимость: как производители и потребители будут сосуществовать при изменениях версий, включая задержанные сообщения.
Не проектируйте API как зеркало таблиц. Контракт должен выражать бизнес-операции или факты, а не раскрывать детали хранения, которые заблокируют последующие изменения.
Пример: обработка документов без фрагментации бэкофиса
Рассмотрим PHP-бэкофис, который управляет делами и должен генерировать, валидировать и хранить документы. Сначала обработка может существовать как внутренний модуль: он получает запрос на генерацию, регистрирует задачу, выполняет асинхронную задачу и обновляет видимое пользователю состояние.
Выделение становится обоснованным, если генерация потребляет существенно иные ресурсы, требует специфичных зависимостей для конвертации, получает собственные пики нагрузки и может работать с запросом на документ, содержащим минимально необходимые данные. Сервис документов не должен свободно обращаться к таблицам дела. Бэкофис может отправить команду с идентификатором, применимым шаблоном, версией данных и назначением; результат возвращается как событие или доступное для запроса состояние.
До этого стоит уточнить, что шаблон определяет структуру вывода, тогда как модель может относиться к доменным данным или к системе ИИ. Если бы ИИ использовался для классификации документов, потребовались бы ограниченный сценарий использования, оценка на репрезентативных данных, проверка человеком при чувствительных решениях, защита данных, контроль затрат и ручная либо основанная на правилах альтернатива на случай сбоя провайдера.
Проверки перед выделением

Не рассматривайте выделение как изолированное техническое развёртывание. Определите постепенную активацию для контролируемого подмножества операций, отличную от объявления изменения всем пользователям. Поддерживайте план сосуществования и отката, пока проверяется поведение.
- Контрактные тесты между производителем и потребителем в дополнение к модульным и интеграционным тестам.
- Метрики задержек, ошибок, повторных попыток, ожидающих очередей, дубликатов и времени до завершения процесса.
- Идентификаторы корреляции для отслеживания операции между монолитом, очередями и сервисом.
- Процедуры восстановления: безопасный повторный запуск, сверка состояний, резервное копирование и восстановление.
- Явные правила функциональной деградации, когда сервис недоступен.
- Критерий успешности: какие доказательства покажут, что выделение уменьшило конкретную проблему, а не только переместило сложность.
Лучшее архитектурное решение — то, которое защищает развитие продукта, не навязывая несоразмерную платформу. Модульный, измеримый и чётко разграниченный PHP-монолит обычно является правильным шагом, пока бизнес-функция не докажет проверяемую потребность в независимости.



