bugover / ship
Core package for Porto architecture.
Requires
- php: <=8.2.3
- php-64bit: <=8.2.3
- composer-plugin-api: ^2.0.0
- ext-gettext: *
- ext-json: *
- ext-redis: *
- ext-swoole: *
- bugover/laravel-repository: ^1.0.0
- composer/class-map-generator: ^1.0.0
- darkaonline/l5-swagger: ^8.0.0
- guzzlehttp/guzzle: ^7.0.0
- hashids/hashids: ^5.0
- laravel/framework: ^11.0.0
- laravel/octane: ^2.0.0
- laravel/passport: ^12.0.0
- laravel/reverb: ^1.0.0
- laravel/tinker: ^2.0.0
- swoole/ide-helper: ^5.0.0
Requires (Dev)
- composer/composer: ^2.0.0
- friendsofphp/php-cs-fixer: ^3.0.0
- mockery/mockery: ^1.6.12
- nunomaduro/collision: ^8.4
- phpmd/phpmd: ^2.0.0
- roave/security-advisories: dev-latest
- spatie/laravel-ignition: ^2.8
- squizlabs/php_codesniffer: ^3.0.0
README
Добро пожаловать в Porto
- Вступление
- Приступая к работе
- Компоненты
- Типичная структура контейнера
- Атрибуты Качества Porto
- Реализации (Построены на базе Porto)
- Обратная связь и вопросы
Вступление
Porto это современный архитектурный шаблон программного обеспечения, состоящий из методических рекомендаций, принципов и шаблонов, которые помогают разработчикам организовать свой код максимально удобным и многократно используемым способом.
Porto - отличный вариант для средних и крупных веб-проектов, поскольку со временем они, как правило, становятся более сложными.
С помощью Porto разработчики могут создавать супер масштабируемые монолитные системы, которые при необходимости можно легко разделить на несколько микросервисов. При этом обеспечивается возможность повторного использования бизнес-логики (функций приложения) в нескольких проектах.
Porto унаследовал концепции из DDD (Domain Driven Design), Modular, Micro Kernel, MVC (Model View
Controller), Layered and ADR (Action Domain Responder) архитектур.
И он придерживается списка удобных принципов дизайна, таких как SOLID, OOP, LIFT, DRY, CoC, **GRASP
**, Generalization, High Cohesion и Low Coupling.
Это начиналось как экспериментальная архитектура, направленная на решение общих проблем, с которыми сталкиваются веб-разработчики при создании крупных проектов.
Мы очень ценим отзывы и предложения.
"Простота является необходимым условием надежности.” — Эдсгер Дейкстра
Приступая к работе
Обзор слоев
По своей сути Порто состоит из 2-х слоев "папок" Контейнеры
& Корабль
.
Эти слои могут быть созданы в любом месте в любой выбранной структуре.
(Пример: в Laravel или Rails они могут быть созданы в app/
директории или в новой src/
директории в корне.)
Визуальный обзор
Прежде чем объяснить, где должен размещаться код каждого типа, давайте разберемся в различных уровнях кода, которые у нас будут:
Уровни кода
- Низкоуровневый код: код фреймворка (реализует основные операции, такие как чтение файлов с диска или взаимодействие с базой данных). Обычно живет в каталоге поставщиков (vendors).
- Среднеуровневый код: общий код приложения (реализует функциональность, которая обслуживает код высокого уровня. И он полагается на низкоуровневый код для работы). Должен быть в слое "Корабль".
- Высокоуровневый код: код бизнес-логики (инкапсулирует сложную логику и для функционирования полагается на код среднего уровня). Должен быть в слое "Контейнеры".
Схема Слоев
Слой Контейнеров (грузовые контейнеры) >> опирается >> на слой Корабля (грузовой корабль) >> опирается >> на Фреймворк ( море).
От Монолита к Микро-сервисам
Porto создан, чтобы масштабироваться вместе с вами! В то время как большинство компаний переходят от монолита к микро-сервисам (и в последнее время без серверов) по мере их расширения. Porto предлагает гибкость, позволяющую превратить ваш монолитный продукт в микроуслуги (или SOA) в любое время с наименьшими возможными усилиями.
В терминах Porto Монолит это эквивалент одного грузового корабля с контейнерами, в то время как Микро-сервисы это эквивалент нескольких контейнерных кораблей. (Независимо от их размеров)
Porto предлагает гибкость, позволяющую начинать с малого, с одной хорошо организованной монолитной службы и расти всякий раз, когда вам это нужно, путем разделения контейнеров на несколько служб по мере роста вашей команды.
Как вы можете себе представить, эксплуатация двух или более судов в море, а не одного, приведет к увеличению затрат на техническое обслуживание (два репозитория, два CI пайплайна,...), но также даст вам гибкость, когда каждое судно может работать с разной скоростью и направлением. Это технически приводит к тому, что каждая служба масштабируется по-разному в зависимости от ожидаемого трафика.
То, как сервисы взаимодействуют друг с другом, полностью зависит от разработчиков.
1) Слой Корабля
Слой "Корабль" содержит родительские "Базовые" классы (классы, расширяемые каждым отдельным компонентом) и некоторый Служебный Код.
Родительские классы «Базовые классы» слоя «Корабль» предоставляют полный контроль над Компонентами Контейнера (например, добавление функции к классу Базовой Модели делает его доступным в каждой Модели в ваших Контейнерах).
Слой «Корабль» также играет важную роль в отделении кода приложения от кода фреймворка. Это облегчает обновление Фреймворка, не затрагивая код приложения.
В Porto уровень «Корабль» очень тонкий, он НЕ содержит общих многоразовых функций, таких как Аутентификация или Авторизация, поскольку все эти функции предоставляются контейнерами, которые можно заменять по мере необходимости. Предоставляя разработчикам большей гибкости.
Структура Корабля
Уровень корабля содержит следующие типы кода:
- Код Ядра: это двигатель корабля, который автоматически регистрирует и автоматически загружает все компоненты вашего Контейнера для загрузки вашего Приложения. Он содержит большую часть магического кода, который обрабатывает все, что не является частью вашей бизнес-логики. И в основном содержит код, который облегчает разработку за счет расширения возможностей платформы.
- Общий код Контейнеров
- Родительские классы: базовые классы каждого компонента в вашем контейнере. (Добавление функций в родительские классы делает их доступными в каждом контейнере). Родители созданы, чтобы содержать общий код для ваших Контейнеров.
- Универсальные Классы: многоразовые функции и классы, которые могут использоваться каждым Контейнером. Например, глобальные исключения, миддлвары приложения, файлы глобальной конфигурации и т.д.
Примечание: Все компоненты контейнера ДОЛЖНЫ расширяться или наследоваться от уровня «Корабль» (в частности, от родительской папки).
При отделении Ядра в отдельный пакет Корабль-Родители должны расширять Ядро-Родителей (могут быть названы Абстрактными, поскольку большинство из них должны быть Абстрактными Классами). Корабль-Родители содержат общую бизнес-логику вашего пользовательского приложения, в то время как Ядро-Родители ( Абстрактные) содержат общий код вашей платформы, в основном все, что не является бизнес-логикой, должно быть скрыто от разрабатываемого приложения.
2) Слой Контейнеров
Porto управляет сложностью проблемы, разбивая ее на более мелкие управляемые контейнеры.
Слой Контейнеров - это место, где живет бизнес-логика приложения (Особенности приложения / функциональность). Вы будете проводить 90% своего времени на этом слое, создавая компоненты.
Вот пример того, как создаются Контейнеры:
"В приложении объекты "Задача", "Пользователь" и "Календарь" будут находиться в разных контейнерах, каждый из которых имеет свои собственные Маршруты (Routes), Контроллеры (Controllers), Модели (Models), Исключения ( Exceptions) и т.д. И каждый контейнер отвечает за получение запросов и возврат ответов от любого поддерживаемого пользовательского интерфейса (Web, API..)."
Рекомендуется использовать одну Модель для каждого Контейнера, однако в некоторых случаях вам может потребоваться больше, чем одна Модель, и это совершенно нормально. (Даже если у вас есть одна Модель, у вас также могут быть значения (Values) "также известные как Объекты-Значения (Value Objects)" (Values аналогичны моделям, но не представлены в базе данных в их собственных таблицах, а как данные в Моделях), эти объекты создаются автоматически после того как их данные извлекаются из БД, такие как Цена, Местоположение, Время ...)
Просто имейте в виду, что две Модели означают два Репозитория, два Преобразователя (Transformers) и т.д. Если вы не хотите всегда использовать обе модели вместе, разделите их на 2 контейнера.
Примечание: если у вас есть сильные зависимости между двумя контейнерами по своей природе, то размещение их в одной Секции упростит их повторное использование в других проектах.
Базовая структура Контейнеров
Container 1
├── Actions
├── Tasks
├── Models
└── UI
├── WEB
│ ├── Routes
│ ├── Controllers
│ └── Views
├── API
│ ├── Routes
│ ├── Controllers
│ └── Transformers
└── CLI
├── Routes
└── Commands
Container 2
├── Actions
├── Tasks
├── Models
└── UI
├── WEB
│ ├── Routes
│ ├── Controllers
│ └── Views
├── API
│ ├── Routes
│ ├── Controllers
│ └── Transformers
└── CLI
├── Routes
└── Commands
Взаимодействие Контейнеров и Связи
- Контейнер МОЖЕТ зависеть от одного или многих других контейнеров. (Желательно в том же разделе.)
- Контроллер МОЖЕТ вызывать задачи из другого контейнера.
- Модель МОЖЕТ иметь связь с моделью из другого контейнера.
- Возможны также другие формы связи, такие как Прослушивание Событий, запускаемых другими Контейнерами.
Если вы используете обмен данными между контейнерами на основе событий, вы можете использовать тот же механизм после разделения кодовой базы на несколько служб (сервисов).
Примечание. Если вы не знакомы с разделением кода на модули / домены или по какой-то причине не предпочитаете такой подход. Вы можете создать все свое приложение в одном контейнере. (Не рекомендуется, но абсолютно возможно).
Секции Контейнеров
Контейнерные "Секции" - еще один очень важный аспект в архитектуре Porto.
Думайте о секции как о рядах контейнеров на грузовом корабле. Хорошо организованные контейнеры в рядах, ускоряют погрузку и разгрузку связанных контейнеров для конкретного клиента.
Чтобы избежать наличия десятков контейнеров в корне вашей папки с контейнерами, вы можете сгруппировать связанные контейнеры в Секции.
Секция - это по сути папка, содержащая связанные контейнеры. Тем не менее преимущества огромны. Думайте о Секциях как о ограниченном контексте, где каждый раздел представляет собой часть вашей системы.
Пример: если вы создаете гоночную игру, такую как Need for Speed, у вас могут быть следующие два раздела: Секция Гонки и Секция Lobby, где каждая секция содержит Контейнер Автомобиля и Модель Автомобиля внутри, но с разными свойствами и функциями. В этом примере Модель Автомобиля в Секции Гонки может содержать бизнес-логику для ускорения и управления автомобилем, в то время как Модель Автомобиля в Секции Lobby содержит бизнес-логику для настройки автомобиля перед гонкой.
Разделы позволяют разделить большую Модель на более мелкие. И они могут предоставить границы для различных Моделей в вашей системе.
Если вы предпочитаете простоту или у вас только одна команда, работающая над проектом, вы можете вообще не иметь Секций (где все контейнеры находятся в папке контейнеров), что означает, что ваш проект представляет собой одну Секцию. В этом случае, если проект быстро растет и вы решили, что вам нужно начать использовать Секции, вы можете создать новый проект также с одним разделом, это называется Микросервисами. В Микросервисах каждая секция «часть проекта» находится в своем собственном проекте (репозитории), и они могут обмениваться данными по сети, как правило, с использованием протокола HTTP.
Компоненты
На уровне Контейнера есть набор Компонентов
"Классы" с предопределенными обязанностями.
Каждый отдельный фрагмент кода, который вы пишете, должен находиться в Компоненте (функции класса). Porto определяет для вас огромный список этих Компонентов с набором рекомендаций, которым следует следовать при их использовании, чтобы процесс разработки проходил гладко.
Компоненты обеспечивают согласованность и упрощают обслуживание вашего кода, поскольку вы уже знаете, где должен быть найден каждый фрагмент кода.
Типы Компонентов
Каждый Контейнер состоит из нескольких компонентов, в Porto Компоненты разделены на два типа:
Основные Компоненты
и Дополнительные Компоненты
.
1) Основные Компоненты
Вы должны использовать эти компоненты, поскольку они необходимы практически для всех типов веб-приложений:
Маршруты (Routes) - Контроллеры (Controllers) - Запросы (Requests) - Действия (Actions) - Задачи (Tasks) - Модели ( Models) - Представления (Views) - Преобразователи (Transformers).
Шаблоны: следует использовать в случае, если приложение обслуживает HTML-страницы.
Преобразователи (Transformers): следует использовать в случае, если приложение обслуживает данные JSON или XML.
1.1) Схема взаимодействия Основных Компонентов
1.2) Жизненный цикл Запроса
Базовый сценарий вызова API, навигация по основным компонентам:
- Пользователь вызывает
Endpoint
в файлеМаршрута
. Endpoint
вызываетПосредника (Middleware)
для обработки Аутентификации.Endpoint
вызывает соответствующийКонтроллер
.Запрос
внедренный вКонтроллер
автоматически применяет валидацию и правила авторизации.Контроллер
вызываетДействие (Action)
передает ему данныеЗавпроса
.Действие (Action)
выполняет бизнес-логику, *ИЛИ может вызывать столько Задач (Tasks), сколько необходимо для повторного использования подмножеств бизнес-логики.Задачи (Tasks)
выполняют многократно используемые подмножества бизнес-логики (Задача (Task)
может выполнять единственную часть основного Действия (Action).Действие (Action)
подготавливает данные для возврата в Контроллер, некоторые данные могут быть собраны из Задач ( Tasks).Контроллер
создает ответ используяПредставление (View)
(илиПреобразователь (Transformer)
) и отправляет его назад Пользователю
1.3) Основные Компоненты, определения и принципы
Кликайте по стрелкам ниже, чтобы узнать о каждом компоненте.
Маршруты (Routes)
Маршруты являются первыми получателями HTTP-запросов.
Маршруты отвечают за сопоставление всех входящих HTTP-запросов со своим контроллером.
Файлы Маршрутов содержат конечные точки (Endpoints) (шаблоны URL - адресов, идентифицирующие входящий запрос).
Когда в ваше приложение поступает HTTP-запрос, конечные точки (Endpoints) совпадают с шаблоном URL-адреса и вызывают соответствующую функцию контроллера.
Принципы:
- Существует три типа маршрутов: Маршруты API, Web-Маршруты и Маршруты CLI.
- Файлы Маршрутов API ДОЛЖНЫ быть отделены от файлов Web-маршрутов, каждый в своей отдельной папке.
- Папка Web-маршрутов будет содержать только Web Endpoints (доступные веб-браузерам); А папка API-Маршрутов будет содержать только API Endpoints (доступные любому пользовательскому приложению).
- У каждого Контейнера ДОЛЖНЫ быть свои Маршруты.
- Каждый файл Маршрута ДОЛЖЕН содержать одну конечную точку (Endpoint).
- Задача конечной точки (Endpoint) состоит в том, чтобы вызвать функцию в соответствующем Контроллере после выполнения запроса любого типа. (НЕ ДОЛЖЕНО делаться ничего другого).
Контроллеры
Контроллеры несут ответственность за валидацию запроса, обслуживание данных запроса и построение ответа. Проверка и ответ происходят в отдельных классах, но запускаются контроллером.
Концепция контроллеров такая же, как и в MVC (это C в MVC), но с ограниченными и предопределенными обязанностями.
Принципы:
- Контроллеры НЕ МОГУТ ничего знать о бизнес-логике или о каких-либо бизнес-объектах.
- Контроллер ДОЛЖЕН выполнять только следующие работы:
- Чтение данных Запроса (Request) (ввод пользователя)
- Вызов Действия (Action) (и передача ему данных запроса)
- Создание Ответа (Response) (обычно ответ создается на основе данных, собранных в результате вызова Действия ( Action))
- Контроллеры НЕ МОГУТ иметь какую-либо бизнес-логику. (НУЖНО вызывать Действие (Action) для выполнения бизнес-логики).
- Контроллеры НЕ МОГУТ вызывать Задачи (Tasks) Контейнера. Они МОГУТ вызывать только Действия (Actions). (И потом Действия (Actions) могут вызывать Задачи (Tasks) Контейнера).
- Контроллеры МОГУТ быть вызваны только конечными точками (Endpoints) Маршрутов.
- Каждая папка пользовательского интерфейса (UI) Контейнера (Web, API, интерфейс командной строки (CLI)) будет иметь свои собственные контроллеры.
Вы можете спросить, зачем нам Контроллер! когда мы можем напрямую вызвать действие из маршрута. Слой контроллера помогает сделать действие повторно используемым в нескольких пользовательских интерфейсах ( Web-интерфейс и API), поскольку он не создает ответа, и это уменьшает количество дублирования кода в разных пользовательских интерфейсах.
Вот пример ниже:
- UI (Web): Route
W-R1
-> ControllerW-C1
-> ActionA1
. - UI (API): Route
A-R1
-> ControllerA-C1
-> ActionA1
.
Как вы можете видеть в приведенном выше примере, действие A1 использовалось обоими маршрутами W-R1 и A-R1 с помощью слоя Контроллера, который находится в каждом пользовательском интерфейсе.
Запросы (Requests)
Запросы в основном служат для ввода данных пользователем в приложение. И они очень полезны для автоматического применения правил проверки и авторизации.
Запросы - лучшее место для применения валидации, поскольку правила валидации будут связаны с каждым запросом. Запросы также могут проверять Авторизацию, например проверить, есть ли у этого пользователя доступ к этой функции контроллера. (Пример: проверить, владеет ли конкретный пользователь продуктом перед его удалением, или проверить, является ли этот пользователь администратором, чтобы что-то редактировать).
Принципы:
- Запрос МОЖЕТ содержать правила Валидации / Авторизации.
- Запросы ДОЛЖНЫ внедряться только в Контроллеры. После внедрения они автоматически проверяют, соответствуют ли данные запроса правилам проверки, и если ввод запроса не валидный, будет выброшено Исключение.
- Запросы МОГУТ также использоваться для авторизации, они могут проверять, авторизован ли пользователь для выполнения запроса.
Действия (Actions)
Действия (Actions) представляют собой сценарии использования (Use Cases) Приложения (действия, которые могут быть предприняты Пользователем или Программным обеспечением в Приложении).
Действия МОГУТ содержать бизнес-логику или / и организуют Задачи (Tasks) для выполнения бизнес-логики.
Действия (Actions) принимают структуры данных в качестве входных данных, манипулируют ими в соответствии с бизнес-правилами внутри компании или с помощью некоторых Задач (Tasks), а затем выводят новые структуры данных.
Действия (Actions) НЕ ДОЛЖНЫ заботиться о том, как собираются Данные или как они будут представлены.
Просто взглянув на папку «Действия» (Actions) Контейнера, вы можете определить, какие сценарии использования (функции) предоставляет ваш Контейнер. И глядя на все Действия (Actions), вы можете сказать, что может делать Приложение.
Принципы:
- Каждое Действие (Action) ДОЛЖНО отвечать за выполнение одного варианта использования в Приложении.
- Действие МОЖЕТ извлекать данные из Задач (Tasks) и передавать данные другой Задаче (Task).
- Действие МОЖЕТ вызывать несколько Задач (Tasks). (Они даже могут вызывать Задачи (Tasks) из других Контейнеров!).
- Действия МОГУТ возвращать данные Контроллеру.
- Действия НЕ ДОЛЖНЫ возвращать ответ. (Задача Контроллера - вернуть ответ).
- Действие НЕ ДОЛЖНО вызывать другое Действие (если вам нужно повторно использовать большой кусок бизнес-логики из нескольких Действий, и этот фрагмент вызывает несколько Задач, вы можете создать ПодДействие (SubAction). См. Раздел SubAction ниже.
- Действия в основном используются из Контроллеров. Однако их можно использовать из Слушивателей событий (Listeners), Команд и / или других Классов. Но их НЕ СЛЕДУЕТ использовать из Задач.
- Каждое действие ДОЛЖНО иметь только одну функцию с именем run ().
- Основная функция Действия
run ()
может принимать объект Запроса в параметре. (что странно, прим. перев.) - Действия (Actions) несут ответственность за обработку всех ожидаемых Исключений (Exceptions).
Задачи (Tasks)
Задачи (Tasks) - это классы, которые содержат общую бизнес-логику для нескольких Действий (Actions) в разных Контейнерах.
Каждая Задача (Task) отвечает за небольшую часть логики.
Задачи (Tasks) не являются обязательными, но в большинстве случаев они вам нужны.
Пример: если у вас есть Действие (Action) 1, которому нужно найти запись по ее идентификатору в БД, потом запускается Событие. И у вас есть Действие 2, которому необходимо найти ту же запись по ее идентификатору, а затем выполнить вызов внешнего API. Поскольку оба Действия выполняют логику «найти запись по идентификатору», мы можем взять эту бизнес-логику и поместить ее в собственный класс, этот класс является Задачей (Task). Эту Задачу (Task) теперь можно использовать в обоих Действиях (Actions), а также и в любых других Действиях, которые вы можете создать в будущем.
Правило состоит в том, что всякий раз, когда вы видите возможность повторного использования фрагмента кода из действия, вы должны поместить этот фрагмент кода в Задачу (Task). Не создавайте Задачи вслепую для всего, вы всегда можете начать с написания всей бизнес-логики в Действии (Action), и только когда вам нужно повторно использовать ее, создайте для нее специальную Задачу. (Рефакторинг необходим для адаптации к росту кода).
Принципы:
- Каждая Задача (Task) ДОЛЖНА иметь единственную ответственность (работу).
- Задача (Task) МОЖЕТ получать и возвращать данные. (Задача НЕ ДОЛЖНА возвращать ответ (response), задача Контроллера - вернуть ответ).
- Задача НЕ ДОЛЖНА вызывать другую задачу. Потому что это вернет нас к архитектуре Служб (Services Architecture), а это большой беспорядок.
- Задача НЕ ДОЛЖНА вызывать Действие (Action). Потому что тогда ваш код не будет иметь никакого логического смысла!
- Задачи (Tasks) ДОЛЖНЫ вызываться только из Действий (Actions). (Они также могут было вызваны из Действий других Контейнеров!).
- Задачи (Tasks) обычно имеют одну функцию
run ()
. Однако при необходимости у них может быть больше функций с явными именами. Заставить класс Task заменить уродливую концепцию флагов функций. Пример:FindUserTask
может иметь 2 функцииbyId
иbyEmail
, все внутренние функции ДОЛЖНЫ вызывать функциюrun
. В этом примереrun
может быть вызван в конце обеих функций после добавления Критериев в репозиторий. - Задача НЕ МОЖЕТ вызываться из Контроллера. Потому что это приводит к появлению недокументированных функций в вашем
коде.
Совершенно нормально иметь много Действий (Actions), "например:
FindUserByIdAction
иFindUserByEmailAction
, где оба действия вызывают одну и ту же задачу", а также совершенно нормально иметь одно действиеFindUserAction
, принимающее решение, какая Задача (Task) должна быть вызывана. - Задача (Task) НЕ ДОЛЖНА принимать объект Запроса (Request) ни в одной из своих функций. Она может принимать любые параметры функций, но не объект Request. Она (Задача) может свободно использоваться везде, и её можно будет протестировать независимо.
Модели (Models)
Модели (Models) обеспечивают абстракцию данных, они представляют данные в базе данных. (Это M в MVC).
Модели несут ответственность за то, как следует обрабатывать данные. Они следят за тем, чтобы данные поступали правильно в внутреннее хранилище (например, в базу данных).
Принципы:
- Модель НЕ ДОЛЖНА содержать бизнес-логику, она может содержать только код и данные, которые представляют себя. (это отношения с другими Моделями, скрытые поля, имя таблицы, заполняемые атрибуты, ...)
- Один Контейнер МОЖЕТ содержать несколько моделей.
- Модель МОЖЕТ определять Отношения между собой и любыми другими Моделями (в случае, если связь существует).
Представления (Views)
Представления (Views) содержат HTML-код, обслуживаемый вашим приложением.
Их основная цель - отделить логику приложения от логики представления. (Это V в MVC).
Принципы:
- Представления можно использовать только из Web Контроллеров (Web Controllers).
- Представления ДОЛЖНЫ быть разделены на несколько файлов и папок в зависимости от того, что они отображают.
- Один Контейнер МОЖЕТ содержать несколько файлов Представлений.
Преобразователи (Transformers)
Преобразователи (Transformers) (это сокращенное название от Responses Transformers).
Они эквивалентны Представлениям, но для ответов JSON. В то время как Views принимает данные и представляет их в HTML, Transformers принимает данные и представляет их в JSON.
Преобразователи (Transformers) - это классы, отвечающие за преобразование Моделей в массивы.
Преобразователи (Transformers) берут модель или группу моделей «коллекцию» и преобразует ее в форматированный сериализуемый массив.
Принципы:
- Все ответы API ДОЛЖНЫ быть отформатированы с помощью Преобразователей.
- Каждая Модель (которая возвращается вызовом API) ДОЛЖНА иметь Преобразователь.
- В одном Контейнере МОЖЕТ быть несколько Преобразователей.
- Обычно у каждой Модели есть Преобразователь.
Исключения (Exceptions)
Исключения (Exceptions) - это также форма вывода, которую следует ожидать (например, исключение API) и которая должна быть четко определена.
Принципы:
- Существуют Исключения Контейнеров (находятся в Контейнерах) и Общие Исключения (находятся в Корабле).
- Задачи (Tasks), Подзадачи (Sub-Tasks), Модели и любой класс в целом могут вызывать очень конкретное Исключение.
- Вызывающий ДОЛЖЕН обрабатывать все ожидаемые Исключения от вызываемого класса.
- Действия (Actions) ДОЛЖНЫ обрабатывать все Исключения и следить за тем, чтобы они не просачивались в Компоненты верхнего уровня и не вызывали неожиданное поведение.
- Имена исключений ДОЛЖНЫ быть как можно более конкретными и ДОЛЖНЫ иметь четкие описательные сообщения.
Под-действия (Sub-Actions)
ПодДействия (SubActions) предназначены для устранения дублирования кода в Действиях (Actions). Не запутайтесь! ПодДействия (SubActions) не заменяют Задачи (Tasks).
В то время как Задачи (Tasks) позволяют Действиям (Actions) совместно использовать часть функциональности. ПодДействия (SubActions) позволяют Действиям (Actions) совместно использовать серию Задач.
ПодДействия (SubActions) созданы для решения проблемы. Проблемой является: Иногда вам нужно повторно использовать большой кусок бизнес-логики в нескольких Действиях. Этот кусок кода уже вызывает некоторые Задачи (Tasks). (Помните, что Задача НЕ ДОЛЖНА вызывать другие Задачи), так как же вам повторно использовать этот фрагмент кода без создания Задачи! Решение - создать ПодДействие (SubAction).
Подробный пример: предполагается, что действие A1 вызывает Task1, Task2 и Task3. И еще одно действие A2 вызывает Task2, Task3, Task4 и Task5. Обратите внимание, что оба Действия вызывают Задачи (Tasks) 2 и 3. Чтобы исключить дублирование кода, мы можем создать ПодДействие, которое содержит весь общий код между обоими Действиями.
Принципы:
- ПодДействия (Sub-Actions) ДОЛЖНЫ вызывать Задачи (Tasks). Если ПодДействия выполняют всю бизнес-логику без помощи хотя бы одной Задачи (Task), вероятно, это должно быть не вспомогательное действие, а Задача (Task).
- ПодДействие (Sub-Action) МОЖЕТ извлекать данные из Задач (Tasks) и передавать данные в другую Задачу (Task).
- ПодДействие МОЖЕТ вызывать несколько Задач (Tasks). (Они даже могут вызывать Задачи из других Контейнеров!).
- ПодДействия МОГУТ возвращать данные в Действие (Action).
- ПодДействие НЕ ДОЛЖНО возвращать ответ (response). (задача Контроллера - вернуть ответ).
- ПодДействие НЕ ДОЛЖНО вызывать другое ПодДействие. (старайтесь избегать этого насколько возможно).
- ПодДействие ДОЛЖНО использоваться из Действия (Action). Однако их можно использовать из Событий (Events), Команд ( Commands) и / или других классов. Но их НЕ СЛЕДУЕТ использовать из Контроллеров или Задач (Tasks).
- Каждое ПодДействие (Sub-Action) ДОЛЖНО иметь только одну функцию с именем run ().
2) Дополнительные Компоненты
Вы можете добавлять эти Компоненты, когда они вам нужны, в зависимости от потребностей вашего приложения, однако некоторые из них настоятельно рекомендуются:
Tests - Events - Listeners - Commands - Migrations - Seeders - Factories - Middlewares - Repositories - Criteria - Policies - Service Providers - Contracts - Traits - Jobs - Values - Transporters - Mails - Notifications...
Типичная Структура Контейнера
Контейнер со списком основных и дополнительных Компонентов.
Container
├── Actions
├── Tasks
├── Models
├── Values
├── Events
├── Listeners
├── Policies
├── Exceptions
├── Contracts
├── Traits
├── Jobs
├── Notifications
├── Providers
├── Configs
├── Mails
│ ├── Templates
├── Data
│ ├── Migrations
│ ├── Seeders
│ ├── Factories
│ ├── Criteria
│ ├── Repositories
│ ├── Validators
│ ├── Transporters
│ └── Rules
├── Tests
│ ├── Unit
│ └── Traits
└── UI
├── API
│ ├── Routes
│ ├── Controllers
│ ├── Requests
│ ├── Transformers
│ └── Tests
│ └── Functional
├── WEB
│ ├── Routes
│ ├── Controllers
│ ├── Requests
│ ├── Views
│ └── Tests
│ └── Acceptance
└── CLI
├── Routes
├── Commands
└── Tests
└── Functional
Атрибуты качества Porto
Преимущества использования Porto.
Модульность и Возможность повторного использования
В Porto бизнес-логика вашего приложения живет в Контейнерах. Контейнеры Porto похожи по своей природе на модули (из модульной архитектуры) и домены (из архитектуры DDD).
Контейнеры могут зависеть от других Контейнеров, подобно тому, как слой может зависеть от других слоев в многоуровневой архитектуре.
Правила и рекомендации Porto минимизируют и определяют направления зависимостей между контейнерами, чтобы избежать циклических ссылок между ними.
И это позволяет группировать связанные Контейнеры в разделы, чтобы повторно использовать их вместе в разных проектах. ( каждый раздел содержит многократно используемую часть бизнес-логики вашего приложения).
Что касается управления зависимостями, разработчик может свободно перемещать каждый Контейнер в свой собственный репозиторий или хранить все Контейнеры вместе в одном репозитории.
Удобство сопровождения и Масштабируемость
Porto стремится снизить затраты на обслуживание за счет экономии времени разработчиков. Он структурирован таким образом, чтобы обеспечить разделение кода и обеспечить согласованность, что способствует удобству его сопровождения.
Наличие единственной функции для каждого класса для описания функциональности упрощает процесс добавления и удаления функций.
Porto имеет очень организованную кодовую базу и нулевую развязку кода. В дополнение к чистому рабочему процессу разработки с предопределенными направлениями потока данных и зависимостей. Все это способствует его масштабируемости.
Тестируемость и отлаживаемость
Чрезвычайное соблюдение принципа единой ответственности за счет наличия единственной функции для каждого класса приводит к появлению сверхтонких классов, что упрощает тестируемость.
В Porto каждый компонент ожидает одного и того же типа ввода и вывода, что упрощает тестирование, имитирование и подделку (stabbing).
Сама структура Porto делает написание автоматических тестов плавным процессом. Поскольку у него есть папка tests
в
корне каждого Контейнера для размещения там модульных тестов ваших Задач.
И папка tests
в каждой папке пользовательского интерфейса для размещения там функциональных тестов (для тестирования
каждого пользовательского интерфейса отдельно).
Секрет упрощения тестирования и отладки заключается не только в организации тестов и заранее определенной ответственности компонентов, но и в разделении вашего кода.
Адаптивность и Эволюционируемость
С Porto вы можете легко приспособиться к будущим изменениям с минимальными усилиями.
Предположим, у вас есть веб-приложение, обслуживающее HTML, и недавно вы решили, что вам нужно мобильное приложение, поэтому вам нужен API.
Porto имеет подключаемый пользовательский интерфейс (WEB, API и CLI), и это позволяет сначала написать бизнес-логику вашего приложения, а затем реализовать пользовательский интерфейс для взаимодействия с вашим кодом.
Это дает гибкость для добавления интерфейсов по мере необходимости и адаптации к будущим изменениям с наименьшими усилиями.
все это возможно, потому что Действия являются центральным организационным принципом «не контроллер», который совместно используется несколькими пользовательскими интерфейсами. И пользовательский интерфейс отделен от бизнес-логики приложения и отделен друг от друга в каждом контейнере.
Удобство использования и Обучаемость
Porto позволяет очень легко найти любую особенность / функциональность. И понять, что внутри нее творится.
Это связано с использованием экспертного языка предметной области при именовании классов «компоненты». А также золотое правило единственной функции для каждого класса. Это позволяет вам найти любой вариант использования (Действие (Action)) в вашем коде, просто просматривая файлы.
Porto обещает, что вы найдете реализацию любой функции менее чем за 3 секунды! (пример: если вы ищете, где проверяется адрес пользователя - просто перейдите в контейнер адресов, откройте список действий и найдите ValidateUserAddressAction).
Расширяемость и Гибкость
Porto принимает во внимание будущий рост и гарантирует, что ваш код останется обслуживаемым независимо от размера проекта.
Это достигается за счет своей модульной структуры, разделения задач и организованной связи между внутренними классами «Компоненты».
Это позволяет вносить изменения без нежелательных побочных эффектов.
Гибкость и Возможность модернизации
Porto дает возможность двигаться быстро и легко.
Обновления фреймворка легко выполнять благодаря полному разделению между приложением и кодом фреймворка на уровне «Корабль».
Get in Touch
Your feedback is important.
For feedbacks, questions, or suggestions? We are on Slack.
Автор
Русский перевод (примерный)
Donations
Become a Github Sponsor.
Direct donation via Paypal.
Become a Patreon.
License
MIT © Mahmoud Zalt