EnglishРусский Map

Porto SAP

title
Porto SAP
type
concept
summary
Слоёный паттерн для PHP/Laravel: Containers (бизнес-логика) + Ship (инфраструктура), Actions над Tasks с одним run(), каждый артефакт в отдельном классе
tags
software-architecture, php, laravel, ddd, clean-architecture
created
2026-05-13
updated
2026-09-14
lang
ru
translation_of
porto-sap
source_updated
2026-09-14
translated
2026-09-14
translator
lllm/antigravity/gemini-3.7-flash-medium

Porto - архитектурный паттерн, опубликованный Махмудом Зальтом (Mahmoud Zalt; MIT, ~1.6k★ на github.com/Mahmoudz/Porto) для организации серверного кода приложений. В первую очередь он ориентирован на PHP/Laravel, но в теории не привязан к конкретному фреймворку. Фреймворк, реализующий Porto для Laravel, называется Apiato (apiato.io); сам репозиторий Porto - это спецификация паттерна и документация, а не среда исполнения.

Текущий README - это лаконичная посадочная страница, всё основное содержимое находится на сайте с документацией. В основе паттерна лежат две идеи.

Два слоя: Containers и Ship

Код приложения делится на две части:

app/
├── Containers/
│   └── {Section}/
│       └── {Container}/
│           ├── Actions/
│           ├── Tasks/
│           ├── Models/
│           ├── Controllers/
│           ├── Requests/
│           ├── Routes/
│           ├── Transformers/
│           ├── Events/
│           ├── Listeners/
│           ├── Jobs/
│           ├── Commands/
│           ├── Migrations/
│           ├── Tests/
│           ├── Configs/
│           └── Exceptions/
└── Ship/
    ├── Parents/        # base classes (Controller, Model, Request, ...)
    ├── Providers/
    ├── Middleware/
    ├── Exceptions/
    └── Configs/
  • Containers содержат бизнес-логику одной ограниченной предметной области (User, Billing, Order, ...). Секции (Sections) группируют связанные Containers (например, секция Authentication может содержать User, Login, Roles).
  • Ship содержит всё сквозное: базовые классы фреймворка, от которых наследуются Containers, глобальные middleware, общие service provider'ы, конфигурации всего приложения.

Заявленная выгода - путь миграции из монолита в микросервисы: предполагается, что Container достаточно самодостаточен, поэтому его вынос в отдельный сервис сводится в основном к настройке сборки и развёртывания, а не к переписыванию кода. Насколько реальные кодовые базы на Porto сохраняют такую чистоту Containers - отдельный вопрос: это тот же разрыв между теорией и практикой, с которым сталкивается концепция "ограниченный контекст как микросервис" в литературе по DDD. В what-even-are-microservices утверждается, что разделение кода вообще не стоит считать сложной частью: извлечение Container - это лишь изменение в сборке и deploy'е, но организации делят сервисы по организационным причинам, и чёткая граница не снимает издержек на согласование между командами.

Actions и Tasks

Внутри Container'а бизнес-логика разделена на два типа компонентов:

  • Action - оркестрирует один сценарий использования (например, RegisterUserAction). Он выстраивает Tasks в последовательность; именно его вызывают Controllers, Jobs или Commands.
  • Task - делает ровно одну вещь, содержит единственный публичный метод run(), переиспользуется между разными Actions (например, CreateUserTask, SendWelcomeEmailTask, AssignRoleTask).
  • Sub-Action - именованная группа Tasks, используемая сразу несколькими Actions, чтобы не дублировать цепочки вызовов.

Это та же идея, что и классы сценариев использования (use cases) в Clean Architecture или интеракторы/сервисы в кодовых базах с элементами DDD, только с ужесточённым правилом: один класс - один метод - одна задача. Контроллеры остаются тонкими: разобрать запрос, вызвать Action, вернуть результат.

Свой класс для каждой сущности

Самая характерная черта паттерна - не слои, а требование раскладывать каждый концептуально обособленный артефакт в собственный класс в предсказуемой папке. Маршруты (web / api / cli) живут в отдельных файлах; валидация запросов - в классах Form Request; формирование ответов - в Transformers; сквозная функциональность - в Middleware; доменные события - в Events + Listeners; работа с очередями - в Jobs; CLI - в Commands; доступ к данным - в Repositories; персистентное состояние - в Models; DTO - для передачи данных между Containers; Exceptions - под каждую отдельную ошибку.

В итоге получается широкое и неглубокое дерево: множество мелких файлов, каждый из которых назван строго по выполняемой функции. Авторы классических кодовых баз на Laravel иногда возражают, что это over-engineering. Ответ Porto: накладные расходы на ещё один файл малы, а цена поиска того, "где именно происходит X", в монолитном Controller'е - высока. Паттерн делает ставку на то, что инструменты (переход к символу в IDE, поиск) работают дёшево, а значит, предельно подробное дробление на классы даёт структурный выигрыш.

Аргумент об удобстве для ИИ

Текущий README начинается со следующего тезиса:

Porto's strict adherence to the single responsibility principle enhances its compatibility with AI tools like GitHub Copilot, which thrive on clear, well-defined classes.

Этот довод перекликается с clean-code-coding-agents (и лежащими в его основе рассуждениями из книги Дядюшки Боба Clean Architecture): контекстные окна LLM ограничены, и кодовая база, где каждый файл решает одну именованную задачу, обходится дешевле по токенам при навигации, чем код, где нужно прочитать 600 строк Controller'а, чтобы понять происходящее при POST /users. Структура Porto с небольшими классами в предсказуемых папках идеально подходит для цикла grep-and-read у агентов: файл с именем RegisterUserAction.php в app/Containers/Authentication/User/Actions/ сразу сообщает агенту, что он делает и где искать следующий шаг, ещё до открытия файла.

В реалиях 2026 года это самый убедительный аргумент в пользу паттерна. Заявление о переходе от монолита к микросервисам проверить сложнее (обычно это работает в первый день, а затем незаметно деградирует по мере размытия границ Containers), но тезис об удобстве для агентов проверяется на каждом конкретном файле: откройте файл и проверьте, говорит ли его имя о том, что он делает. В Porto ответ положителен по определению структуры; в традиционном MVC - "может быть, в зависимости от разработчика".

Родословная

Porto объединяет в себе:

  • MVC - сохраняет Models/Views/Controllers в привычном для Laravel виде, но низводит Controllers до роли тонких адаптеров.
  • DDD-lite - Containers по сути являются ограниченными контекстами (bounded contexts). Sections служат для более крупной группировки.
  • Clean Architecture - Actions выступают сценариями использования (use cases); Tasks - атомарными операциями внутри них.
  • Гексагональная архитектура / порты и адаптеры - разделение на Ship/Container повторяет правило "ядро домена не зависит от инфраструктуры", хотя Porto обеспечивает его через соглашение о структуре папок, а не линтингом правил зависимостей.

Чего паттерн не берёт из DDD: агрегаты, value objects, доменные события как инварианты первого уровня, проработку единого языка (ubiquitous language). Porto - структурный паттерн, а не философия моделирования.

Когда подходит

  • Приложениям на Laravel, переросшим плоскую структуру по умолчанию app/Http/Controllers/ и требующим места для сквозных механизмов.
  • Командам, желающим закрепить структуру соглашениями, а не проверками на code review.
  • Кодовым базам, над которыми наряду с людьми работают coding agents (см. clean-code-coding-agents).

Когда не подходит

  • Небольшим приложениям, где накладные расходы на обилие файлов создают реальные издержки, а не окупаются в будущем.
  • Командам, не готовым придерживаться принципа "один класс - одна задача": без этой дисциплины Porto деградирует в обычный Laravel с увеличенным числом папок.
  • Языкам без хуков жизненного цикла в стиле Laravel (Providers, Middleware, Form Requests, Events) - концепцию паттерна можно перенести, но детали реализации не совпадут.

Место в вики

  • clean-code-coding-agents - более общий тезис о том, что при участии агентов структура становится важнее, а не второстепеннее.
  • programming-as-theory-building - концепция Наура о том, почему фиксация структуры через соглашения о папках - слабая замена общей ментальной модели команды, но лучшее, что может предложить паттерн.
  • no-silver-bullet - Брукс о случайной и сущностной сложности: Porto борется со случайной сложностью ("куда положить X?"), оставляя сущностную ("что должен делать X?") предметной области.
  • metapatterns - компендиум Дениса Полторака, классифицирующий сотни архитектурных паттернов (включая Layers и Hexagonal Architecture) менее чем в 20 метапаттернов; Porto относится к тем конкретным паттернам, которые он описывает.
  • scotty - сторона развёртывания того же мира Laravel: SSH task runner в традициях Envoy для момента, когда приложение уже структурировано и его нужно доставить на сервер.