Архитектура проекта на php
Содержание
Постановка вопроса
Время от времени возникает потребность в переписывании проекта сделанном на каком-нибудь из старых версий фреймворка или еще хуже с легаси кода.
Увы, но так просто переписать проект с Yii на Symfony или с легаси на Laravel не является возможным. Потому, что фреймворки не совместимы и не могут заменить друг друга.
Отсюда вопрос. Необходимо организовать такую структуру, при которой ядро приложения должно быть фреймворконезависимым. То есть должна быть возможность менять фреймворк или параллельно использовать несколько фреймворков, не затрагивая основной код приложения.
При этом важно разделять взаимодействие компонентов и чистый код приложения, ведь никогда не знаешь когда проект “устареет” и придется все переписывать.
Запрос и ответ
Очевидно, что любое приложение должно уметь принимать запросы и возвращать ответы.
Любой запрос попадает в контроллер, который в свою очередь обрабатывает результат.
Клиентом в данном случае может выступать любая программа или сервис, которая посылает этот запрос.
Получается, что на верхнем уровне мы должны определить какой контроллер запускать.
Здесь может быть очередь, консоль или мобильное приложение.
То есть имеем несколько каналов для входных запросов.
Это все фреймворки, которые мы может менять друг на друга в любых количествах и соотношениях. Это точка взаимодействия с конечным пользователем.
Структура директорий для контроллеров может быть такой
src/
CI/
Android/
Api/
Cli/
Http/
iOS/
RabbitMq/
other/
Приложение
Приложение состоит из набора операции или бизнес-процессов, с которыми мы можем что-то делать.
Все операции можно разделить два типа:
- Команда (Command)
- Запрос (Query)
Команда модифицирует какие-либо данные (post, put, delete, patch), а запрос только считывает результат (get).
Команда возвращает результат выполнения операции true/false
, например регистрация пользователя на сайте.
Запрос возвращает какие-либо данные, например информация о пользователе или список пользователей.
Операции — это некие процедуры или функции, способы взаимодействия с системой.
Пример обработчика команды
<?php
declare(strict_types=1);
class Handler
{
public function handle(Command $command): void
{
$email = new Email($command->email);
if ($this->users->hasByEmail($email)) {
throw new DomainException('User already exists.');
}
$date = new DateTimeImmutable();
$user = User::requestJoinByEmail(
Id::generate(),
$date,
$email,
$this->hasher->hash($command->password),
$token = $this->tokenizer->generate($date)
);
$this->users->add($user);
}
}
В данном случае команда регистрирует пользователя по email и добавляет его в базу данных.
Класс User
в данном случае входит в доменную модель нашего приложения.
Здесь содержится только чистый код.
Итог
При такой архитектуре можно свободно менять библиотеки, и даже весь фреймворк целиком, не затрагивая код приложения и доменной модели.
Получается гибкая структура, где контроллеры манипулируют нашими операциями, а операции сущностями, что дает удобство при тестировании разработке.