Создан  demius PM 4 года назад; Обновил  demius PM 9 месяцев назад

Подробная структура исходного кода проекта.

{{to(readMe.md, ‘%title%’)}} - пример wiki-функции, которые я когда-нибудь реализую, не выводятся в документе, но позволяют совершать над документацией разные действия, например этот будет отлавливать изменения в документе и при релизе править файл readMe.md в проекте

Соглашения о расположении

Разделение на слои:

  • Точно и четко делим на слои MVC
  • Сохраняем максимально легковесные контроллеры, даже если для этого нужны классы с одной функцией
  • Из twig-функций вытаскиваем всю логику, кроме элементарной логике представления
  • Сервисы подсистем состоящие из одного-двух классов храним в src/Services. Когда появляется четкое понимание, что у подсистемы будем больше классов, выделяем в новую директорию в srv/Service/Subsystem/. Если подсистема продолжает разрастаться и по планам будет хранить значимую часть функционала проекта, а так же будет иметь собственные поддиректории, выделяем в srv/Subsystem, (Когда таких станет много, возможно часть из них, в том числе предрасположенные нужно будет вновь убрать в поддиректории) *вот это сомнительно, наверное все же уберем Security в Service когда сами сервисы разделим на презентацию, домен и application
  • src/Model для хранения всех моделей application. Для моделей презентации отдельный src/ViewModel с предложением дальнейшего разделения.

Разделение классов, выделение абстракций и полиморфизм

  • Внутри основополагающий слоев, выделяем новые подслои по требованию. Например если в подсистеме все две функции лежат в слое сервисов, оставляем один Service класс, при увеличении логики, и понимании как переименовать сервис в <подсистема><Что-то делатель> - разделяем на отдельные сервисы (Например одна две функции работы с проектами -> ProjectService, появилось больше несвязанной логики, сервис перешагнул за 100 строк, разделяем на ProjectContext и ProjectRegistry)
  • Не выделяем интерфейсы, пока их реализует только один класс, но проектируем такие классы с учетом предположения, что в будущем нам наверняка потребуется интерфейс
  • Первые задачи на новую систему и так очень сложны, чтобы проектировать в них окончательные абстракции. Так что там, где слой должен обращаться к конкретной реализации и она там одна, допустимо вызывать напрямую или под условием instanceof. В следующих задачах расширения такие места необходимо превращать в зависимости от абстракций через введение интерфейсов, хендлеров и т.д. Например при реализации самого первого справочника, Dictionary может создавать свои элементы напрямую, через new DictionaryItem. В другой задаче, при реализации справочника требующего расширенного элемента, эта система переделывается на билдер внутри справочника, переопределяемый в потомках (если понадобится еще более сложная логика, только тогда в другой задачи этот билдер можно выносить в отдельный сервис-фабрику элементов справочника)

Структура директорий

  • assets - исходные файлы фронтенда
    • components - vue-компоненты
    • images - изображения идущие в комплекте
    • src_images - исходники изображений
    • styles - стили
  • код фронтенда
  • bin - консольные команды
  • config - файлы конфигурации
    • common - общие файлы конфигурации
      • packages - настройки сторонних пакетов
      • routes - файлы роутов (не особо нужны в видел кучи файлов, но так позволяют задавать роуты уникальные для разных окружений)
      • services - файлы сервисов.
    • dev - конфигурация специфичная для локльной разработки
      • packages - настройки сторонних пакетов
      • routes - файлы роутов
      • services - файлы сервисов.
    • prod - конфигурация специфичная для продакшена
    • test - конфигурация специфичная для тестов
  • docker - файлы постройки контейнеров и настроек для них
  • help - встроенная документация
  • node_modules - генерится для сборки фронтенда, npm-пакеты
  • public - доступная в веб директория сточкой входа и скомпилированным фронтендом
    • build - построенный webencore фронтенд
    • bundles - куски фронтенда построенные старой системой assetics по хорошему не должно быть, все должно собираться единым образом
  • src - исходный код бекенда
    • Command - команды
    • Contract - Контракты (сецйчас все в куче, но надо разделить по категориям)
    • Controller - контроллеры
    • Entity - Объекты-сущности, хранящиеся в doctrine должны уехать или в Model или в Persistance
    • Event - События пока в основном бизнес, но может быть что еще будет
    • EventSubscriber - Подписчики и слушатели событий
      • Menu - Подписчики строящие различные меню. Идеологически не являются слушателями событий, но AdminLTE диктует получение меню через события. С другой стороны можно разнести их код по зонам ответственности - проект, задача, документ, еще что-то что захочет добавлять элементы в хлебные крошки или sidebar, но это не отменяет факта, что это Builder, а не subscriber
    • Exception - Исключения
    • Form - Формы прямой кандидат на вынесение в презентацию
      • DTO - dto-объекты содержимого форм (определяют правила валидации для конкретной формы, но могут содержать и более сложную логику связанную с данными в них хранящими)
        • Doc
        • Project
        • Task
        • User
      • Type - Объекты Форм и контролов на них
        • Comment
        • Doc
        • Project
        • Task
        • User
    • Migrations - Миграции БД
    • Model - бизнес-модели
      • Dto
      • Enum
      • Template - наборы настроек по умолчанию
        • Table - настройки фильтров и сортировок тех или иных таблиц
    • Repository - Репозитории получения сущностей из doctrine (обсуждаемый вопрос, хранить ли там специализированные репозитории, хранящие объекты вне doctrine (Сервисы-регистры, Сервисы работы с объектами в nosql-хранилище) *вероятно стоит вынести в Persistance
    • Security это тоже кандидат в сервисы
      • Hierarchy - сервисы занимающиеся построением и быстрой отдачей карты полномочий
      • Voter - Избиратели, решающие предоставлять ли запрошенный доступ
    • Service
      • Badges - Баджи (цветные метки разных сущностей) может показаться, что презентация, но это бизнес логика, а как отображать решает твиг
      • Constraints - Валидаторы
      • Dictionary - сервисы работы со справочнками их на самом деле сильно облегчили, и будут еще до одного сервиса
      • Doctrine - специфические сервисы для доктрины, кажется это кандидат на Infrastructure
        • Type - наши кастомные типы данных
      • File - подсистема работы с файлами
      • Filler - Сервисы заполнители. Знают как правильно создать один объект из другого, или заполнить один объект данными другого. Умеют догружать нехватающую информацию и проверять сложную логику косистентности, выходящую за пределы доступные entity.
      • Monolog - сервисы для работы с монологом кажется это кандидат на Infrastructure
      • RequestResolver - сервисы обработки реквеста для подгрузки текуущей сущности
      • SpecBuilder - билеры спецификаций вобще подумать нужна ли для них отдельная диреткория, или они будут в своих подсистемах, лежащий там явно относится к Table
      • Statistics - подсистема сбора статистики
      • Table - подсистема строительства сложных больших таблиц данных
      • Twig - Расширения шаблонизатора а вот это явно presentation, причем его бы еще прошерстить и уменьшить количество функций, заменив их на что другое, например более гибкие viewModel, widget и т.д.
      • Wiki - подсистема wiki ссылок
    • Specification - спецификации, как универсальные, так и специфичные для той или иной модели
    • ViewModel - модели для презентации
    • ViewTransformer - трансформеры для презентаций создаюие viewModel или json для ajax-роутов
    • Kernel.php
  • templates - шаблоны страниц
    • auth
    • comment
    • doc
    • error - страницы ошибок
    • file - виджеты для работы с файлами
    • home
    • project
    • table - виджеты для работы с таблицами
    • task
    • user - страницы касающиеся пользователей видимые обычному пользователю (Список, свой профиль с возможность редактировать, профили других пользователей)
    • user_manager - страницы касающиеся администрирования пользователей
  • tests - тесты
    • Behat - сервисные классы для behat
    • Controller - интеграционные, где краулер ходит по роутам поднятого проекта переименовать бы
    • DataFixtures - фикстуры
    • features - Еще одни интеграционные, но на греклине надо бы оставить только одни, видимо эти
    • unit - собственно истинные юнит-тесты. Структура внутри соответствует структуре src
  • translations - файлы переводов
  • var - изменяемые файлы
    • cache
      • <env> - сюда собирается симфоневый кеш классов
    • log - для окружения хранящего логи в файлах, сюда складываются эти файлы
    • storage
      • <env> - здесь лежат базы данных и хранилища, если в данном окружении они видны из проекта
        • files - загуженные в проект файлы
        • mysql - sql база данных
        • redis - nosql база данных
  • vendor - генерится при сборке бекэнда, composer-пакеты

Предложения и обсуждения.

(великоваты для комментария, а в виде отдельного документа без системы гиперссылок и других пометок потеряются)

I: Делим по типам, аналогично предложенному симфони: Entity все доктриновские сущности, Enum - Все enum’ы, Services - все сервисы (из этого выбивается симфоневый же security)

  • Все сложено по типам, если мне нужно какое-то перечисление я сразу понимаю где искать.
  • Подсистема размазана по всему проекту, сложно будет сказать какой сервис где используется и можно ли его выдернуть. (у нас так размазаны формы и их дто по параллельным директориям и сущностям, не разбирая это часть crud или чей-то контрол)

** на данный момент следуем этим путем и присматриваемся к гексагональной архитектуре**

II: Делим по подсистемам, - Security - все связанное с полномочиями и авторизацией, Enitity - сущности доктрины, Objects - Jlob-объекты, Dictionary - справочники (сервисы справочников или и их jlob-объекты и их enum’ы) На это деление намекает services.yml, ищущий сервисы по всему src исключая Entity и Миграции.

  • Мы избавляемся (или как минимум уменьшаем помойки в виде директории Service, в которой рядом лежат очень не связанные друг с другом сервисы.
  • Когда мы хотим охватить взглядом подсистему не надо шастать по всем директориям в поисках где еще она лежит
  • Enum, Exception размазаны по всей системе. И если с первым наверное это и правильно, если нам нужен набор констант подсистемы логично искать его в подсистеме, то со вторым хуже, каждая подсистема будет иметь свой набор Exception?
Комментарии могут оставлять только авторизованные пользователи
 demius 10 месяцев назад

обновили документ

 demius 10 месяцев назад

По итогу сейчас развиваем по первому варианту, особых сложностей это не вызывает, разве что enum порой ищем. Может быть его переложить в контракты.

 demius год и 6 месяцев назад

В итоге мы сначала почти полностью переехали на варинт I, разнеся все по подсистемам, но когда начали эти системы обогащать ,появилось много взаимосвязей, и как-то потихонбку поехали обратно, с общими директориями на тип, Enum, Service, Model (заменило Object)

 demius 3 года назад

Как вариант, по мере реализации tndt-85 писать новый документ, а этот отправить в архив

 demius 3 года назад

3й вариант разделяет логику заполнения сущности, часть данных получается в неё кладет презентация, часть докладывает домен, не очень приятствено.

А чтобы dto из презентации не прорывались в аппликате, можно брать их по интерфейсам.

 demius 3 года назад

1 вариант сильно раздувает доменные сервисы (кстати, было бы правильно сделать им отдельную директорию и максимально облегчать, превращая в фасады, просто вызывающие разные функции, так как в них будет все больше бизнес-функций по мере наполнения проекта этими функциями). В TaskService уже 129 строк, и это только этапы задачи.

 demius 3 года назад

Так как мы активно утягиваем бизнес-логику в сервисный слой, готовя к разделению презентации и application, возникает вопрос с Fillers:

  • отказываемся от Fillers, вместо них работают соответствующие функции доменных сервисов: например TaskFiller::createFromForm() -> TaskService::openTask()
  • делаем и Fillers внутри Services: TaskService->openTask() { $taskFiller->createFromForm(); };
  • Fillers в презентации, Services работают с готовым объектом: TaskController::open() { $taskService->openTask($taskFiller->createFromForm()); }
 demius 3 года назад

Обновить, после разделения на слои. Вобще я надеялся разделять на слои после того, как появится версионирование, чтобы можно было посмотреть как менялась структура проекта. Но можно например, этот вобще отправить в архив, и на его месте создать копию.

 demius 3 года назад

Кстати Securty тогда тоже надо убрать в Services

 demius 3 года назад

И вот у нас появилась еще одна директория в корне, отделенная по своему типу, а не относительно системы. Таким образом при разделении слоев имеет смысл Dictionary вернуть в Services, а объекты словарей в src/Model/Dictionary

Да мы возвращает src/Object под именем src/Model, туда положим Entity, злощастные ProjectSettings и TaskSettings гуляющие то туда, то сюда, отдельно от Entity. Просто туда не нужно будет перемещать Form/DTO так как это viewModel форм.

 demius 3 года назад

Не забываем, что Command это то же презентация presentation/console

Так же надо подумать куда класть ProjectContext, он скрывает у себя знания о реквесте. По идее наш текущий контекст должен лежать в presentation/web/Services так как работает с httpRequest. консольный ProjectContext должен брать проект из переданного ключа суффикса. А сервисные классы не могут его инстанцировать напрямую, вместо этого нужен какой-то полиморфизм, подставляющий нужный контекст, если он возможен.

 demius 3 года назад

Может все же сделать единую директорию объектов (не презентации)

  • Model
    • Entity
    • Object
    • VO

Презентацию выделяем сразу, ибо понятно как это сделать:

  • Presentation
    • web
      • Controller
        • Widgets
      • Form
      • ViewModel
        • Project
        • Task
      • Service
        • Twig
        • Stylizer
        • Transformer (делает из глобальных моделей viewModel)
      • templates
        • widget
          • task
        • task
        • project…
 demius 3 года назад

Актуализировал, местами прокомментировал прямо в списке

 demius 4 года назад

Устарел. Мы начали перемещать директории. До полного перемещения может и не стоит его редактировать, но устаревшим объявить следует. И как только подъедут связи, надо связать с задачами по модификации структуры

 demius 4 года назад

Ну, кстати Service/Twig и Serivice/Constraint намекают нам на то, что в сервисах лежат не подсистемы, слишком маленькие, чтобы иметь свою директорию в src, а подсистемы касающиеся всего проекта. Serivice/Twig хранит twig-функции различных подсистем, Sercvice/Contraint валиаторы для всех подсистем.

 demius 4 года назад

Структура директорий устарела. Надо бы сверить с текущей.

Мы пошли по большей части по второму пути, исключая exceptions. Но остается вопрос по расположению подсистем Dictionary или Service/Dictionary (пока выбрано второе, но с появлением Activity, Statistic и т.д.)

 demius 4 года назад

Хотя последние уже не совсем походят на Regisry, если не думать о том, что они кеширует собранное в себе. (а внешний код не должен об этом думать)

 demius 4 года назад

Пока давайте условимся, что Repository это класс доступа к главному месте хранения объекта. В 95% случаев получения объекта из СУБД. Redis тоже БД, так что получение объекта из него тоже будет в Repository, но только для объектов, которые там и хранятся, а не кешируются. Все остальное - Registry, это и тонкие кеширующие прослойки к репозиториям, и сервисы умеющие ходить внутри переданных объектов и по бизнес-правилам собирать требуемое, как DictionaryRegisry.

 demius 4 года назад

Repository это всегда получение из БД? А из redis, тоже по идее Repository, аледать такие репозитории будут в src/Repository или вместе со своими сервисами в каталогах посистем? А Registry, т.е. классы умеющие извлечь объект, но не хранящие его (максимум, кеширующие). Например извлекающие справочник из сущности, он Regisrty или Repository? Хранятся они в srv/Repository или при службах? Скорее второе.

 demius 4 года назад

Repository vs Registry

  • Repository место хранения непосредствено объекта (почти всегда база, хотя может быть например redis, если это самостоятельный объект, не кеш).
  • Regisry хранит кешированную ссылку не него (умеет по аргументу извлечь из другого места, например правильно распаковать из аргумента), или берет из репозитория и кеширует у себя.
 demius 4 года назад

Стоит, как только появятся гиперссылки внутри проекта, разделить этот документ.

 demius 4 года назад

Не решенные вопросы:

  • Разница между Repository и Registry?
  • В src/Repository только классы поиска в mysql или любые репозитории, например ищущие в redis или умеющие искать по структуре объектов, или строить на лету и кешировать?
 demius 4 года назад

У нас появляется директория Enum, куда надо или собрать все остальные Enum, например из Security, или все же делить первый уровень по подсистемам, а не типам, и тогда enum не нужен, а нужно выделить директорию Dictionary (и в ней сервисы, enum, а сами справочники из Objects)?