Архитектура приложения в репозитории

Если программа обеспечивает получение тех или иных преимуществ (а такими должны быть все корпоративные приложения), задержка с ее внедрением в эксплуатацию означает прямые финансовые потери. Поэтому отнюдь не хотелось бы принимать решения, которые воспрепятствуют развитию системы в дальнейшем. Однако, если оснастить систему дополнительными службами с прицелом на будущее, но сделать это неправильно, новый уровень сложности как раз и может затруднить ее эволюцию, приостановить процесс внедрения и отсрочить получение преимуществ. (Мартин Фаулер)

Как вы поняли, тема посвящена архитектуре проекта в репозитории (API прикладной программы - набор классов, процедур, функций, структур или констант), которую я считаю весьма перспективной. И для начала, определение. Архитектура приложения - это основополагающие аспекты функционирования системы.

MVC / MVP

Поскольку MVC не имеет строгой реализации, то мы постоянно встречаем различные интерпретации данной концепции, отличные от классической (более ранней), поэтому появились разные производные, например многие используют MVP (часто думая, что используют MVC). Очень часто MVC привязывают к слоям приложения, мне это кажется не очень хорошей мыслью, поэтому, я предлагаю так не делать.

История

Поработав в нескольких серьезных компаниях, я видел разные подходы к архитектуре проектов и в результате полностью убедился, что слои, которые предлагают нам "богатые опытом" авторы, часто избыточны и как раз увеличивают сложность и процесс внедрения и самое главное поддержания.

Когда-то прочитав книгу Мартина Фаулера "Архитектура корпоративных программных приложений" 2005 г., мне показалась отличной идеей реализация слоев по Флойду Маринеску (Floyd Marinescu), но теперь я так не думаю, напомню она выглядит так:

Маринеску Фаулер
Представление Представление
Приложение Представление (контроллер приложения)
Службы Домен (слой служб)
Домен Домен (модель предметной области)
Сохранение данных Источник данных

Как видите, в обоих случаях слоев много, слои как бы говорят, что слою выше нельзя перепрыгивать ниже лежащий слой, но это не так, и следующая фраза Мартина Фаулера, мне представляется верной:

Иногда слои организуют таким образом, чтобы бизнес-логика полностью скрывала источник данных от представления. Чаще, однако, код представления может обращаться к источнику данных непосредственно. Хотя такой вариант менее безупречен с теоретической точки зрения, в практическом отношении он нередко более удобен и целесообразен: код представления может интерпретировать команду пользователя, активизировать функции источника данных для извлечения подходящих порций информации из базы данных, обратиться к средствам бизнес-логики для анализа этой информации и осуществления необходимых расчетов и только затем отобразить соответствующую картинку на экране.

Слои

Итак, рассматривая запрос, который порождает самую длинную цепочку использования слоев - например запрос сохранения данных, можно ожидать, что поведение будет следующим:

Архитектура приложения в репозитории

Важно: обратите внимание на стрелочки, они показывают разрешения (какой слой к какому слою может обращаться)

Как видите, теперь, чтобы получить данные, мы можем запросить их из Data-слоя сразу.

Примечание: часто в обязанности Command Handler-а (часто называемого Front Controller-ом) входят:

  1. Автолоадинг (динамичные языки программирования)
  2. routing / command line interface
  3. валидация запроса (при необходимости)
  4. проверка доступа (при необходимости)
  5. форматирование возвращаемых данных (при необходимости)

Обратите внимание, что например обязанность сохранения состояний с помощью транзакций не входит в обязанности Command Handler-а.

Да, опытный читатель сразу заметит аналогию с гексагональной архитектурой, которую еще называет "порты и адаптеры", краткая концепция которой изображена ниже:

Архитектура приложения в репозитории

Как видите, аналогия следующая:

Iput Adapter Command Handler
Application Service Business Logic
Domain Model Data
Output Adapter Data

 

 

Состояние

Известно, что реентерабельность, имутабельность и идемпотентность часто пытаются достичь с помощью микросервисов, однако, это абсолютно не оязательно, напримре Мартин Фаулер считает так:

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

Мной было замечено, что например Fabien Potencier реализовал это высказвание, имплементируя SOA (Service Layer) внутри репозитория, в этом очень помогает Dependency Injection Container. А любителям всюду использовать DTO/VO, Фаулер говорит:

SOA-слой служб не нуждается в использовании объекта переноса данных (Data Transfer Object) — в большинстве случаев клиенту возвращаются реальные объекты домена.

Модули

Итак, со слоями все понятно, но возникает вопрос, как организовать хранение слоев в репозитории не нарушая при этом CQRS (CQS).

В корне репозитория обычно создается директория src, которая хранит список модулей системы. Каждый модуль является обособленным и согласно ниже изложенным доступам, имеет права взаимодействия:

Имя модуля Описание Доступ
Core ядро проекта не может взаимодействовать ни с одним модулем системы, но любой модуль может взаимодействовать с модулем Core
Report отчеты/статистика может взаимодействовать с любым модулем, но ни один модуль не может взаимодействовать с модулем Report
ModuleName любой другой модуль может взаимодействовать только с модулем Core, ни один модуль не может взаимодействовать с модулем ModuleName

Данные правила доступа обусловлены требованием обеспечить минимальную связанность модулей проекта + исключить возможную рекурсию. 

Структура модуля

Структура модуля имеет обязательные и необязательные директории, ниже изложена структура модуля, описанная максимально абстрактно, но с примерами реализации. Итак, рассмотрим возможное содержимое директории src/ModuleName/

Адрес к директории или файлу Тип Описание
Docs Folder Хранит документацию по текущему модулю
- README.md File Файл описывающий некоторые тонкости по использованию данного модуля
Configuration Folder Хранит настройки модуля
- RouteProvider.php Class Реализует маршрутизацию модуля (связь запросов и обработчиков запросов)
- DependencyInjectionProvider.php Class Реализует подключение сервисов модуля в Dependency Injection Container
Data Folder DataAccess Layer - слой хранения данных (это сущности, модели и data-провайдеры внешних сервисов)
- User Folder Пример директории, в которой хранятся файлы по работе с пользователями
- - UserModel.php Model Класс работы с пользователем (чистая модель - не хранит запросы по работе с др. таблицами бд). Нельзя бизнес-сервисы дергать из моделей.
- - UserDsp.php Service Dsp (data source provider) - класс с функциями, в которых реализуется работа c данными. Очень важный момент: метод DSP всегда выполняет только одну функцию и не хранит бизнес-логику и не занимается форматированием/валидацией данных, его обязанность сохранить/возвратить/удалить данные. Пример: при работе с данными пользователей, таблица пользователей обязана быть главной таблицей из которой выбираются данные, остальные таблицы второстепенные (JOIN к ним может быть, а может и не быть).
- - Enum Folder Место расположения файлов-списков, оформленный с помощью интерфейса с константами
- - - UserFieldEnum.php Interface Список полей в бд, которые могу быть названы с ошибками или на русском, в общем которые нужно зарефакторить, но времени на это пока нет
- - - UserTypeEnum.php Interface Список типов пользователя
Business Folder Business Layer - это Application Layer (слой хранения бизнес-логики предметной области)
- MedicalService Folder Хранит сервисы медицинских услуг (поэтому имя директории содержит суфикс Service, иначе суфикс Service писать директории не нужно). Функции сервиса содержат только бизнес-логику (валидация или форматирование данных выполняется за его пределами).
- - Dto Folder Хранит классы реализующих паттерн Dto
- - - SomeDto.php Class Используется для передачи данных между подсистемами модуля/модулей
- - Template Folder Хранит список файлов-шаблонов (мы используем twig по-умолчанию для всех backend шаблонов)
- - - MailTemplate.twig File Шаблон письма для отправки на email
- - Formatter Folder Хранит классы позволяющих форматировать данные медицинских услуг
- - - MedicalServiceFormatter.php Service Классов позволяющих форматировать данные услуг
- - MedicalFacade.php Service Позволяет взаимодействовать с медицинскими услугами (любой класс-фасад с именем из одного слова, обязан иметь суфикс Facade)
- Patient Folder Хранит сервисы по работе с пациентами 
- - PatientFacade.php Service Фасад - хранит все методы по работе с пациентом (тут объявляются все зависимости и существующие бизнес-трейты)
- - Handler Folder

Директория хранит хендлеры по работе с пациентом, идеологично похожие но не имплементирующие RequestHandlerInterface Функция хендлера:

  • выполняет список вызовов в значении, подобном теории автоматов не содержит
  • не содержит проверок бизнес-логики (не содержит бизнесовых if-ов, например изменение логики поведения функции хендлера в зависимости от переданного значения)
  • может содержать условия валидация данных, проверки доступа (if-ы проверок параметров запроса)
  • может содержать условие на отдаваемый формат данных (if requestType == xml), только в том случае, когда в приложении нет последующего слоя-преобразователя данных
- - - PatientOrdersTrait.php Trait Трейт реализует работу с заказами пациента
- - Request Folder Директория хранит объекты запросов (в данном примере потому, что PatientOrdersHandler.php доступен наружу)
- - - PatientOrdersRequest.php Class Объект запроса полученный с помощью JMS\Serializer\Annotation
- - Response Folder Директория хранит объекты ответа (в данном примере потому, что PatientOrdersHandler.php доступен наружу)
- - - PatientOrdersResponse.php Class Объект ответа полученный с помощью JMS\Serializer\Annotation. Плюсы использования JMS\Serializer\Annotation:
- строгая типизация запросов/ответов
- ООП подход
- автоматическая документация
- - - Translation Folder Директория хранит файлы переводов ответов с английского языка
- - - - Ru.yaml Yml Файл перевода с английского на русский
Infrastructure Folder Так называемый инфраструктурный слой, например классы реализующие какой-то общий функционал или расширяющие функциональность сторонних библиотек (бизнес-логики тут нет)
- Monolog Folder Хранит файлы расширяющие функционал стороннего модуля для логирования
- - Enum Folder Хранит файлы-списки
- - - ChannelEnum.php Interface Списком каналов логирования
- - Processor Folder Хранит процессоры, которые обрабатывают данные логирования
- - - SessionProcessor.php Class Процессор, который обрабатывает каждую запись отправляемую в лог
- Database Folder Хранит классы реализующие работу с базой данных
- - MySQL Folder Хранит классы по работе с б.д. MySQL
- - - Enum Folder Хранит файлы-списки
- - - - TableNameEnum.php Interface Список таблиц б.д.
- - - - NumericEnum.php Interface Особенности целочисленных типов данных (пример)
- Helper Folder Общие классы нашей команды, которые могут пригодится в любом месте кода
- - Validation Folder Классы валидации разного вида данных
- - - ScalarValidator.php Service Класс с методами валидации скалярных данных
- Middleware Folder Presentation Layer - классы реализующие обработку запроса (отвечают за преобразование протоколов и кодирование/декодирование данных)
- - Request Folder Директория хранит Handler-классы реализующие MiddlewareInterface
- - Response Folder Директория хранит Handler-классы реализующие паттерн ResponseListener
UI Folder user interface — по́льзовательский интерфейс (Front-end)
- Vue Folder Хранит файлы и директории vue компонентов (структура SPA-приложения на Vue.js)
- - App.vue File Основной файл конфигурации текущего модуля
- - Router.vue File Основной файл роутов текущего модуля
- - Component Folder; Директория компонентов
- - - PatientOrders Folder Хранит файлы компонента, которые будут скомпилированы
- - - - PatientOrders.vue File Содержит подключение шаблона, скрипта и стили vue компонента, все это будет скомпилировано
- - - - PatientOrders.js File Скрипт vue компонента
- - - - PatientOrders.css File Стили vue компонента
- - - - PatientOrders.html File Шаблон vue компонента
- Bootstrap Folder Пример расширения еще одно всем известной фронтенд библиотеки
- - Theme Folder Собственная тема
- - - Office Folder Место применения
- - - - Tree.css File Файл, который расширяет/переопределяет тему библиотеки
Tests Folder Хранит тесты подсистем модуля
- Functional Folder Хранит функциональные тесты подсистем модуля
- Unit Folder Хранит одиночные тесты подсистем модуля
- bootstrap.php File autoload-файл для тестирования на основе подмены классов (используется только в модуле Core)
Pattern Folder Хранит список паттернов используемых в данном модуле (если паттерн используется в нескольких модулях, то кладется в Core\Pattern
- DaoInterface.php Interface Интерфейс для правильной реализации паттерна

В выше обозначенной структуре есть важные моменты:

  1. любой объект обязан быть создан с помощью DependencyInjectionProvider.php кроме объекта, экземпляров которого может быть одновременно несколько, например: Model, CDbCriteria, Entity, EntityAttributeValue, DataTransferObject, ValueObject
  2. Configuration-классы не имеют никаких состояний провайдера и не должны использовать других зависимых классов (должны быть final)
  3. имена директорий/файлов пишутся в стиле UpperCamelCase (PascalCase)

UI (Frontend)

Интерфейс, с которым взаимодействует пользователь, часто написан на языке отличном от языка, на котором написан backend. Поэтому, считается недопустимой связанность представления (презентационная логика) и бизнес-логики. Предлагаю рассмотреть еще несколько преимуществ независимости уровней:

  • Представление, логика и данные разделены
  • Несоединённые слои вообще никогда не взаимодействуют
  • Каждый слой (данные, представление и логика) не зависит от остальных и не зависит от реализации
  • Каждый слой может быть потенциально запущен на отдельной машине
  • Изменение платформы влияет только на тот уровень который на ней находится
  • Задача разработки хорошо делится и поэтому может быть быстрее решена (уровни можно разрабатывать параллельно): Web дизайнер делает уровень представления, Инженер (Software Engineer) делает логику Администратор БД делает модель данных
  • Лёгкость в поддержке

Давайте рассмотрим пример:

Архитектура приложения в репозитории

Следовательно, код интерфейса лучше хранить в отдельной директории модуля. Предлагаю, рассмотреть вариант, когда UI написан на языке JavaScript и популярном сейчас фреймворке Vue (главной особенностью которого является факт того, что все в нем является компонентом).

Скомпилированный файл UI-компонента должен быть размещен в публичной директории, содержащей имя директории UI-компонента.

На примере PatientOrders-а, это директория: public/UI/ModuleName/PatientOrders в которой js, css и html, будет скомпилированны в Build.js

Файл Build.js не должен попасть в систему контроля версий (например git), поэтому следует добавить в .gitignore игнорирование всех файлов Build.js

Если предполагаются какие-либо медиа-файлы, то их следует размещать в директории public/Resource/ModuleName/PatientOrders/

Например:

Путь Описание
public/Resource/ModuleName/PatientOrders/Image Директория с изображениями
public/Resource/ModuleName/PatientOrders/Pdf Директория с PDF-документами
public/Resource/ModuleName/PatientOrders/Zip Директория с архивами

Директории UI и Resource расположены выше по иерархии по следующим причинам:

  • нет нагроможденности в директории public (всего две директории: UI и Resource, вместо Х директорий равных кол-ву директорий модулей)
  • удобный роутинг (удобно конфигурировать роутинг статики на веб-сервере)
  • удобно монтировать директории (часто директории UI и Resource являются монтированными имея общее единое место хранения)

На последок

Стоит заметить, что в проектировании приложения я стараюсь и советую придерживаться следующих постулатов:

Источник: 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9

Оцени публикацию:
  • 1,5
Оценили: 1


Предложения и пожелания:

 

youtube.com/watch?v=7hFivbgIEqk

При полном или частичном использовании материалов данного сайта, ссылка на сайт "yapro.ru" обязательна как на источник информации.
Автоматический импорт материалов и информации с сайта запрещен.
Copyrights © 2007 - 2019 YaPro.Ru

Лебеденко Николай Николаевич
Ошибка в тексте? Выделите её мышкой и нажмите: Ctrl + Enter