Введение
Хэй, всем привет. Мы начинаем небольшой блог по разработке пет-проекта. О чем будет проект? Хороший вопрос...
На данный момент о статистике дрифта. Да, именно о статистике дрифта. Проект, на самом деле, большой, но мы будем делать его по "запчастям", чтобы медленно и уверенно расширять его. Этот подход поможет нам совершенствовать навыки разработки и архитектуры, а также не позволит "утонуть" в потоках мыслей и объемах работ.
Ну хватит с введением, давайте приступать. Первая часть статистики - встречи пилотов. Что? Не-не, не те всякие современные "европейские встречи", а сколько раз и где пилоты соревновались друг с другом. Для чего нам нужна такая статистика? Все очень просто! Когда идет этап, комментаторы часто говорят, что эти пилоты уже не раз встречались, а их счет встреч N:M. И я вот подумал, а что если просто взять данные, составить их структуру, сделать открытый доступ к данным через красивый визуальный интерфейс? Прикольно же, когда ты можешь в режиме реального времени зайти на сайт, выбрать двух пилотов и посмотреть подробную или краткую статистику их встреч. Ну вот и давайте попробуем это сделать максимально интересно, технологично (тут как выйдет) и практично.
С чего мы начнем? Файловой структуры проекта. Учтите, что в процессе разработки что-то может меняться, а что-то кардинально изменяться. Это норма, так как я не пишу готовую статью, где все уже "вылизано до блеска". Это лайф-тайм блог по проекту.
Файловая структура
Наша файловая структура сейчас не будет привязана ни к какому фреймворку, поскольку мы делаем изолированный проект на "честном слове". Что? Да, я решил пойти по принципу - написать внутренность проекта, а потом натянуть на это что-то готовое из фреймворков. В чем плюс? Пока точно не знаю, но мне нравится писать изолированные вещи, которые работают без привязки к чему-то, а потом страдать с переносом этого на что-то готовое. На самом деле, как показывает практика, такой подход отлично подходит, чтобы "изучить" ядро проекта, а потом "изучить" работу фреймворка на готовом ядре. А еще прикол этого подхода в том, не будет использоваться огромная часть готового решения, так как поймем, что она нам просто не нужна, а глаз мозолит.
Давайте я сразу накидаю всю структуру, а потом ее детально опишу?
- config
- src
- App
- Domain
- Infrastructure
- Shared
- tests
- var
- cache
- reports
- tests
- vendor
В config
будут лежать какие-то конфиги приложения.
В src
будет лежать основной код приложения. Эта папка содержит в себе 3 слоя:
App - команды, события, мапперы, DTO и прочие вещи. По сути - тут лежат файлы "первой точки обращения";
Domain - доменные сущности, интерфейсы и прочие запчасти сущностей (не путать с моделями базы данных);
Infrastructure - репозитории, работа с базой данных и прочие вещи.
А что за Shared
папка? Да все просто - общие элементы: базовые команды, обертки и прочая "общая" ерундистика.
В tests
- тесты, которые будут разбиты также, как и папки в src
. Ну или почти также.
В var
- отчеты, кэш и прочая служебная история.
В vendor
будут лежать необходимые пакеты для разработки.
Надеюсь, что структуру вы поняли, но про src
я немного дополню приблизительной логикой:
Делается запрос к нашему проекту (будет сделано в самый последний момент).
Что-то, что отвечает за обращения, вызывает из
App
слоя команду.-
Команда вызывает репозитории для работы с данными что-то обрабатывает и т.д., а в ответ возвращает DTO результата.
Репозиторий стучится в БД, чтобы получить данные.
Маппит "сырые" данные в сущности
Domain
и возвращает их в команду
Как бы все. Это просто и понятно. Хотя, вам может показаться это избыточным, но такая архитектура помогает четко разграничить области ответственности по слоям. Вы точно будете знать, что, где и для чего находится. Ну и что делает, соответственно.
Теперь, давайте поговорим про настройки и необходимые пакеты для старта нашего интересного проекта.
Настройки PHPStorm и composer пакеты
Начнем с простого и очевидного - пакеты composer. На данный момент, я подразумеваю, что мне понадобится вот это:
{
"require-dev": {
"phpunit/phpunit": "^11.0",
"qossmic/deptrac-shim": "^1.0"
},
"require": {
"php-di/php-di": "^7.0"
}
}
Ну как бы тут есть понятно, phpunit
для тестов, php-di
для контейнера зависимостей. А что за deptrac-shim
?
Ой, относительно крутая штука, которая немного контролирует вас в слоях. Помните, я писал про слои: App
, Domain
, Infrastructure
? Так вот, между ними есть "особое" взаимодействие. Точнее, кто что может "дергать". Например, App
слой может использовать все напрямую. Но чаще всего, ему надо работать с Infrastructure
слоем, который может в Domain
слой. Почему App
всемогущий? Потому что ему надо и вызвать штуку по работе с БД, но и передать ей данные в нужном формате (в нашем случае будут ValueObject доменного слоя).
Немного сложно, но это нормально для начала. В процессе все поймете. Если коротко - сильно разделяем слои и их использование между собой. Давайте настроим это дело!
Deptrac
Выполняем команду vendor/bin/deptrac init
, чтобы создать конфигурационный файл deptrac.yaml
. В него вписываем такую конфигурацию:
Код deptrac.yaml
Полный код можно посмотреть в оригинальной статье: Пихта DEV - Проект «Статистика дрифта». Часть 1. Настройка.
Что тут за магия такая происходит? Все очень просто. В начале мы указали, где смотреть (paths
) и что исключить (exclude_files
). Далее указали наши слои и настроили их. Например, вот так мы создали слой App
, который имеет тип проверки directory
и находится в src/App/
:
name: App
collectors:
- type: directory
value: src/App/.*
Потом в секции ruleset
мы просто указали какой слой какие слои может использовать. Как бы все. И по выполнению команды vendor/bin/deptrac analyse
мы будем получать отчет, который покажет нам корректность использования слоев.
Но сам deptrac будет писать cache файл прям в корень проекта. А мне такое не нравится! Понятно, что можно исключить его из git, но я же сделал для этого отдельную папку var/cache
. Проблема в том, что я не нашел как поменять конфигом путь до cache файла. Но, если запустить команду vendor/bin/deptrac analyse --cache-file=var/cache/.deptrac.cache
, то все хорошо. Это не очень удобно, так как мы можем забыть про дополнительные ключи CLI команды. Давайте создадим еще одну новую директорию bin
, в которой будем хранить исполняемые файлы?
И наш первый файл - deptrac.sh
, в котором вот такое простое содержание:
../vendor/bin/deptrac analyse --config-file=../deptrac.yaml --cache-file=../var/cache/.deptrac.cache
Это позволит нам запускать скрипт с корректными для нас настройками, а также расширять его в будущем. Возможно, когда-нибудь можно будет подключить его к сборке проекта (билд), поставить в pre-commit событие git и вообще круто сделать чистую сборку или push.
А теперь, предлагаю настроить phpunit, чтобы также круто запускать тесты, как проверки deptrac.
PHP Unit
Создаем файл в корневой директории phpunit.xml
с очень простым содержимым:
Код phpunit.xml
Полный код можно посмотреть в оригинальной статье: Пихта DEV - Проект «Статистика дрифта». Часть 1. Настройка.
Тут особо нечего рассказывать, кроме того, что в этих секциях: bootstrap="vendor/autoload.php"
, colors="true"
и cacheDirectory="var/cache"
- мы указали загрузчик, включили цвета (для красоты вывода) и указали директорию кэша. В testsuites
указали местоположение наших тестов, а в source
указали местоположение нашего основного кода. А еще я люблю покрытие. Дааа, я тот самый шибанутый, который любит максимальное покрытие кода... Не судите строго, каждый псих по своему. Поэтому, я добавил секцию coverage
по умолчанию и указал 2 формата: html и xml - экспорта в папку var/reports/tests
. Зачем? Html буду смотреть в браузере, там красиво и удобно, а xml может понадобиться нам в будущем для сборки.
Теперь, создайте папку tests
в корне проекта и допишите в composer.json
такую штуку:
{
"autoload": {
"psr-4": {
"tests\\": "tests/"
}
}
}
Это позволит использовать внутри папки tests
namespace tests\*
. А еще это надо прописать в IDE, чтобы не ругался. Идем в File -> Settings -> Directories
, справа есть папка tests
. Нажимаем на карандаш и пишем tests\
. Сохраняемся и выходим. Теперь, нажимаем по нашей папке tests
правой кнопкой и выбираем Mark Directory as -> Test Source Root
. Теперь она светится зеленым цветом и нам хорошо. А да, раз уж про директории зашла речь, то по известному алгоритму пометьте src
как Source Root
, а папки var
и vendor
как Excluded
. Вот теперь супер все настроено.
Ну и напоследок создайте .gitignore
файл, в котором напишите:
vendor
.idea
var
test.php
Что мы тут сделали? Мы объяснили нашему git репозиторию, что нам не надо заносить под контроль версий папки vendor
, var
и .idea
, а также файл test.php
. Так, стоп! А что за файл test.php
? А это, ребята, стандарт одного из видов крутого атомарного тестирования! Шучу, просто иногда надо что-то быстро запустить проверить и проще писать в test.php
файле вызовы, чем строчить тест или идти на какой-нибудь онлайн ресурс.
Комментарии (15)
sibvic
04.09.2024 01:30+1Лет 10 назад я такое уже писал. Правда не только для парных заездов. Но не взлетело. Надеюсь у вас взлетит
mepihin Автор
04.09.2024 01:30+1Если поделитесь опытом, то будет очень кстати. Проект реализуется от простого к сложному, потом будет и квалификация и прочее. Пока решил сделать так, чтобы не захламлять мозг и не путаться от масштаба.
storoj
04.09.2024 01:30Какая разница в каких папках лежат какие файлы?
mepihin Автор
04.09.2024 01:30+1Разница в архитектуре приложения, связанности классов, разделения логики. Например, вы обедаете на кухне, а спите в спальне. По хорошему, вы не должны обедать в спальне. Вот и такая история тут.
storoj
04.09.2024 01:30Потом всё равно это превратится в кашу, потому что иерархическая структура тут не подходит. Один "файл" может логически относиться к более чем одной "папке". Гораздо легче навигироваться просто по имени файла/символа. Я не помню когда в последний раз вообще пользовался деревом проекта, кроме как в ситуациях когда нужно создать новый файл и мучительно выбирать куда бы его положить.
Прозвучит неожиданно, но избавившись от папок (просто кладя всё в "src") можно избавиться от головной боли по организации того, что организовать невозможно.
mepihin Автор
04.09.2024 01:30+1Что же, это ваш выбор, но уйма ООП приложений PHP работают именно по какой-то архитектуре директорий, символизирующие не только слои приложения, но и namespace.
FanatPHP
04.09.2024 01:30Один "файл" может логически относиться к более чем одной "папке"
Можно пример? Желательно в рамках предложенной структуры, но если не получится, то тогда свой.
SerafimArts
О, гексагоналка! Начало уже интригующее и выглядит как годный материал, наконец-то)
У меня следующий вопрос остался.
Правильно ли я понимаю, что это НЕ классический вариант с
App\Example\{Domain, Application, Infrastructure, Presentation}
+App\Some\{Domain, ...etc}
, а в обратную сторону:App\Domain\Example + App\Infrastructure\Example + ...
+App\Domain\Some + ...etc
(иначе зачемShared)
? Или предполагается что контекст/предметная область/домен у нас один единственный на данный момент?Вопрос вот к чему: Известно что один домен не может лазать в другой и наоборот. Это считай что разные приложения должны быть, которые можно в будущем разнести вообще на разные сервисы.
Так что интересно как будет решена проблема с VO, которые пересекаются между разными доменами. Ну например, есть у нас
App\Domain\Car\...
внутри которого всякие доменные сервисы, энтити и прочая шалупонь. Есть VOApp\Domain\Car\Name
+ его сервисыApp\Domain\Car\Name\NameParser
(например).Потом добавляется статистика и какие-нибудь события:
App\Domain\Statistics\Event\CarHasBeenDestroyed
, которые в свою очередь могут дёргатьApp\Shared\Domain\CarId
(потому что шаред), но не могут дёрнутьName
, т.к. это другой домен. Предполагается двигатьName
тоже вShared
? Но это всё же VO не общее, а принадлежащее исключительно машинам (доменуCar
). Что в таких случаях предполагается делать?P.S. А в чём была причина отказываться от
Presentation
слоя, куда складываются хттп контроллеры, консольные команды, реквест/респонз дто и проч.? Ради упрощения (я так понимаю вы его сApplication
объеденили) и дабы не оверинженерить лишние слои?P.P.S. Вместо
*.sh
скриптов можно использовать composer ;)Просто туда добавить эту простыню из команды и повесить алиас, чтоб можно было вызывать как
composer deptrac
и всё: https://getcomposer.org/doc/articles/scripts.mdmepihin Автор
В папке Domain и других лежат "модули". То есть у нас как бы одно единственное приложение, которое не разделено на структуры типа Car\{App, Domain и Infrastrucutre}, а наоборот - все объединено в App, Domain и Infrastrucutre и имеет внутри то, с чем работаем. Данный подход на текущем этапе достаточен.
По поводу работы VO "разных доменов" тут получается так, что мы можем можем использовать в рамках Car все, что внутри, если нам надо использовать в Racer что-то из VO Car, то для этого формируется VO на стороне Racer, который на инфраструктурном слое парсится из CarVO -> RacerCarVO. Другой подход - прямое использование, так как по факту приложение одно и "область видимости" тоже. Вот если бы были разные структуры (описал выше), то тогда да, маппинг через anticorruption layer или что-то в таком духе.
Никто не отказывался, мы просто пока не дошли до этого момента. Я же пишу в формате "живого блога". Поэтому, когда мы дойдем до запросов (а это вообще последнее, что будет), то добавим нужные слои. Но, на самом деле, это вообще не важно, так как мы будем писать команды, которые по факту будут вызываться в Presentation слое.
Дельное замечание, забыл про это совсем. Подумаю над переносом в composer.
SerafimArts
Понял, если разные домены, то предлагается копипастить. Благо DTO и не критично. А что делать в таком случае с сервисами, относящимися к этому VO? Ну там парсеры, валидаторы, ещё мб что?
mepihin Автор
Например, также берем anticorruption layer и связываем все что хотим. Просто если мы были бы разными командами разработки, то напрямую ничего вашего трогать не могу. Тут цельная структура об одном и том же (контекст), так что можно использовать без проблем.
С другой стороны, никто не мешает написать адаптеры на anticorruption layer, чтобы "законно" использовать что-то "чужое"
SerafimArts
Ну вот да, я тоже об этом. Что если мы совсем в разных отделах сидим, то трогать не получится. А копипаста уж очень избыточна.
Хм, не совсем понимаю. Предполагается что эти адаптеры потом просто в шаред сложить? Или каким образом? Допустим у нас разные команды (т.е. не ваш вариант из статьи, а тот что предположили в комментариях), вот в таком случае:
И кроме как скопипастить этот VO, а общение с его сервисами настроить через CommandBus/QueryBus у меня идей не возникло, да и статей особо не нашёл (плохо гуглил возможно).
mepihin Автор
Не, ты делаешь в своем "модуле" этот слой, где повторяешь структуру, а классы в ней - адаптеры, которые используют чужие классы и мапят их на свои так, как тебе это надо. Это оптимальный вариант использования.
SerafimArts
Хм, т.е. нейминг "Adapter" (ну и предметная область соответственно) фактически "легализует" доступ до сторонних доменов если я правильно понял?
mepihin Автор
Есть шаблон проектирования "Адаптер" посмотри.