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

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

{{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 8 месяцев назад

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

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

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

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

В итоге мы сначала почти полностью переехали на варинт 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)?