В данном материале речь пойдет о кодовой базе, и для стартанем с классических понятий.
API прикладной программы — набор классов, процедур, функций, структур или констант.
Поскольку MVC не имеет строгой реализации, то мы постоянно встречаем различные интерпретации данной концепции, отличные от классической (более ранней), поэтому появились разные производные, например многие используют MVP (часто думая, что используют MVC).
Стоит заметить, что проверка полей ввода — это бизнес-правило, тесно связанное с приложением (валидация запроса от пользователя). Вычисление процентов по вкладу и подсчет запасов, напротив, — это бизнес-правила, более тесно связанные с предметной областью.
В итоге, очень часто MVC привязывают к слоям приложения, но по факту слоев может быть больше.
Давайте рассмотрим запрос сохранения данных, который порождает цепочку использования слоев:
Важно: обратите внимание на стрелочки, они показывают разрешения (какой слой к какому слою может обращаться)
Примечание: часто в обязанности Command Handler-а (часто называемого Front Controller-ом) входят:
Обратите внимание, что например обязанность сохранения состояний с помощью транзакций не входит в обязанности Command Handler-а.
На схеме выше не показан, но его можно ассоциировать с правой точкой Command Handler-а (из которой идут стрелки влево).
Для чего могут применять контроллер:
Опытный читатель может заметить, что такие сложные подходы используют в крупных, серьезных проектах и обычно в совокупности с гексагональной архитектурой, которую еще называет "порты и адаптеры", краткая (возможно не совсем корректная) концепция которой изображена ниже:
Как видите, аналогия следующая:
Input Adapter | Command Handler |
Application Service | Business Logic |
Domain Model | Data |
Output Adapter | Data + Infrastructure |
Hexagonal архитектура (еще называемая луковой) говорит о том, что по мере приближения к центру программное обеспечение становится все более абстрактным и инкапсулирует все более высокоуровневые политики. Самый внутренний круг является самым обобщенным и находится на самом высоком уровне в пирамиде кода.
Как видите, чтобы получить данные, мы можем запросить их из Data-слоя сразу (это нормально, здравая логика часто диктует нам такое поведение). Например в книге Мартина Фаулера "Архитектура корпоративных программных приложений" 2005 г., есть описание слоев приложения следующих авторов:
Флойд Маринеску | Фаулер |
Представление | Представление |
Приложение | Представление (контроллер приложения) |
Службы | Домен (слой служб) |
Домен | Домен (модель предметной области) |
Сохранение данных | Источник данных (работа с данными) |
Неопытный архитектор может подумать, что слою выше нельзя перепрыгивать в ниже лежащий слой, но это не так, и следующая фраза Мартина Фаулера, в этой же книге подчеркивает данную мысль:
Иногда слои организуют таким образом, чтобы бизнес-логика полностью скрывала источник данных от представления. Чаще, однако, код представления может обращаться к источнику данных непосредственно. Хотя такой вариант менее безупречен с теоретической точки зрения, в практическом отношении он нередко более удобен и целесообразен: код представления может интерпретировать команду пользователя, активизировать функции источника данных для извлечения подходящих порций информации из базы данных, обратиться к средствам бизнес-логики для анализа этой информации и осуществления необходимых расчетов и только затем отобразить соответствующую картинку на экране.
Но не стоит понимать данную фразу слишком буквально, ведь Фаулер говорит о ситуации более понятно описанной в книге "Руководство по проектированию архитектуры приложений":
Так в компонентах UI не должно быть кода прямого доступа к источнику данных, для извлечения данных в них должны использоваться либо бизнес-компоненты, либо компоненты доступа к данным.
Известно, что имутабельность, идемпотентность и реентерабельность (см. условия достижения) часто пытаются достичь с помощью микросервисов, однако, это абсолютно не обязательно, например Мартин Фаулер считает так:
Вам не стоит без особой нужды пытаться расчленять цельное приложение на отдельные Web-службы, взаимодействующие друг с другом. Лучше спроектировать приложение и представить те или иные его части как Web-службы, трактуя их как интерфейсы удаленного доступа.
Мной было замечено, что например Fabien Potencier реализовал это высказывание, имплементируя SOA ( Service Layer) внутри репозитория, в этом очень помогает Dependency Injection Container.
Таким образом и появились классы-сервисы, имплементирующие паттерн "интерактор" — это объект который представляет конкретный сценарий использования. Основная идея интеракторов заключается в том, что вы извлекаете изолированные части функциональности в новый класс (каждый класс отвечает за четко выраженную область знаний).
Любителям всюду использовать DTO/VO, Фаулер говорит:
SOA-слой служб не нуждается в использовании объекта переноса данных (Data Transfer Object) — в большинстве случаев клиенту возвращаются реальные объекты домена.