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

В данном материале речь пойдет о кодовой базе, и для стартанем с классических понятий.

API прикладной программы — набор классов, процедур, функций, структур или констант.

MVC / MVP

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

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

Стоит заметить, что проверка полей ввода — это бизнес-правило, тесно связанное с приложением (валидация запроса от пользователя). Вычисление процентов по вкладу и подсчет запасов, напротив, — это бизнес-правила, более тесно связанные с предметной областью.

В итоге, очень часто MVC привязывают к слоям приложения, но по факту слоев может быть больше.

Слои в приложении

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

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

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

Command Handler

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

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

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

Controller

На схеме выше не показан, но его можно ассоциировать с правой точкой Command Handler-а (из которой идут стрелки влево).

Для чего могут применять контроллер:

  1. соблюдение контакта - вариантов контракта может быть большое количество (это неизбежно, потому что например у приложения может быть реализована интеграция с другими приложениями, которые не будут подстраиваться под контакт Вашего приложения)
  2. реализация какого-то протокола передачи данных или архитектурного стиля - например REST, где есть PUT запрос, в котором ID изменяемого объекта является частью роутинга (/book/123), а JSON-структура объекта приходит в Body POST запроса
  3. проверка прав доступа - приложение в большей или меньше степени может иметь проверку прав доступа основанную на роутинге (т.е. еще до использования контроллера), иногда наоборот такой проверки нет и эту обязанность берет на себя контроллер, а иногда проверять права доступа вовсе не нужно проверять (данные публичны), или точка доступа обязана быть публична (например эндпоинт авторизации/регистрации/восстановления доступа)
  4. взаимодействие с разными видами клиентов - инструкции, которые отправляет приложение одному клиенту, могут быть не нужны другому клиенту, например если клиент является браузером, то приложение обязано отправить инструкции хранения Cookies (будь то ID сессии или просто факт авторизации в виде ID авторизованного пользователя), в тоже время авторизация консольным приложением это сохранение авторизационной информации в памяти (консольному приложению нет необходимость отправлять полноценный HTTP-запрос)

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

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

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

Input AdapterCommand Handler
Application ServiceBusiness Logic
Domain ModelData
Output AdapterData + Infrastructure

Hexagonal архитектура (еще называемая луковой) говорит о том, что по мере приближения к центру программное обеспечение становится все более абстрактным и инкапсулирует все более высокоуровневые политики. Самый внутренний круг является самым обобщенным и находится на самом высоком уровне в пирамиде кода.

Слой Data

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

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

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

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

Но не стоит понимать данную фразу слишком буквально, ведь Фаулер говорит о ситуации более понятно описанной в книге "Руководство по проектированию архитектуры приложений":

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

Состояние

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

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

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

Таким образом и появились классы-сервисы, имплементирующие паттерн "интерактор" — это объект который представляет конкретный сценарий использования. Основная идея интеракторов заключается в том, что вы извлекаете изолированные части функциональности в новый класс (каждый класс отвечает за четко выраженную область знаний).

Когда не стоит создавать DTO/VO

Любителям всюду использовать DTO/VO, Фаулер говорит:

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


22.03.2015 16:52