Пользователь
0,0
рейтинг
4 апреля 2013 в 16:54

Разработка → M в MVC: почему модели непоняты и недооценены (перевод) из песочницы

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

Многие из вас наверняка заметили, что я пишу книгу о Zend Framework. Недавно я закончил черновики двух глав: «Архитектура приложений на Zend Framework» и «Понимая Zend Framework». В первой главе объясняется архитектурный шаблон Model-View-Controller (MVC) и причины, по которым он стал стандартом де-факто для веб-приложений. Во второй исследуется связь MVC с компонентами Zend Framework, их структурой и взаимодействием.

Завершив обе главы я осознал, что большую часть времени описывал модель и ее фактическое отсутствие в Zend Framework. На самом деле ни один веб-фреймворк не предлагает нам полноценную модель (по причинам, которые я объясню чуть позже). И ни в одном из них не дается внятного объяснения этому обстоятельству. Вместо этого они последовательно связывают понятие модели с родственным, но не идентичным понятием доступа к данным, что изрядно всех запутывает.

Эта сторона фреймворков никогда не привлекала особого внимания. И все же именно она лежит в основе целого класса проблем в тех приложениях, которые пытаются использовать MVC по образу и подобию фреймворков для веб-приложений. Более того, попытки донести идею модели до других разработчиков нередко напоминают битье головой о стену. Я не хочу сказать, что все разработчики тупые или не понимают саму идею, просто никто из них (вне зависимости от того, работают они с PHP или нет) не связывает модели с той областью, которая наделяет их смыслом — принципами объектно-ориентированного программирования.

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

Модели непоняты

Модели можно описать по-разному. На самом деле только об этом можно написать целую книгу, многие именно так и поступали! Как правило описываются две роли модели:

1. Модель отвечает за сохранения состояния между HTTP-запросами

По сути дела любые данные — в базе данных, файле, сохраненные в сессии или закешированные внутри APC, должны быть сохранены между запросами в виде состояния приложения на момент последнего запроса. Помните, модель не ограничивается базой данных. Даже получаемые из веб-сервисов данные могут быть представлены в виде модели! Да, даже ленты новостей в формате Atom! Стремящиеся побыстрее познакомить с моделью фреймворки этого никогда не объясняют, усиливая непонимание.

Возьмем в качестве примера разрабатываемый мной компонент под названием Zend_Feed_Reader, который на самом деле является моделью. Он читает ленты новостей, обрабатывает их, интерпретирует данные, добавляет ограничения, правила и по большому счету создает удобное представление нижележащих данных. Без него мы имеем Zend_Feed (лучшее средство для чтения новостных лент на данный момент), который требует большого количества работы для получения полноценной модели. Другими словами, Zend_Feed_Reader является моделью, в то время как Zend_Feed ограничивается доступом к данным.

2. Модель включает в себя все правила и ограничения, управляет поведением и использованием данной информации.

Для примера, вы пишете бизнес-логику для модели заказа в снабженческом приложении и по внутренним правилам компании на покупки за наличные может быть наложено ограничение в 500 евро. Покупки на сумму более 500 евро должны быть запрещены в вашей модели заказа (для них может требоваться одобрение вышестоящего начальства). У модели должны быть средства для установки подобных ограничений.

Все станет предельно ясно, как только вы задумаетесь над смыслом слова «модель». В климатологии есть модели климата, описывающие данные, процессы, предполагаемое поведение и позволяющие рассчитать возможные результаты. М в MVC называется моделью не просто так. Модель представляет не только данные, она представляет всю систему, в которой полезны эти данные. Само собой система может быть настолько сложной, что ей понадобится несколько взаимодействующих моделей, но вы поняли идею.

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

В программировании толстые модели предпочтительнее моделей с нулевым размером

Джамис Бак (Jamis Buck, автор Capistrano, сейчас работающий в 37signals) в свое время описал концепцию «Тощего контроллера, толстой модели». Крис Хартжес (Chris Hartjes) тоже написал статью на эту тему. Мне всегда нравилась простота этой концепции, так как она иллюстрирует ключевую особенность MVC. В рамках этой концепции считается, что по мере возможности логику приложения (вроде бизнес-логики из примера выше) лучше всего помещать в модель, а не контроллер или представление.

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

По большому счету, php-разработчики не совсем понимают, что такое модель. Многие считают модель красивым словом для обозначения доступа к базе данных, другие приравнивают ее к разным шаблонам для доступа к базе данных, вроде Active Record, Data Mapper и Table Data Gateway. Фреймворки очень часто продвигают это заблуждение, ненамеренно, я уверен, но энергично. Не полностью понимая, что такое модель, почему это столь великолепная идея, и как ее надо разрабатывать и развертывать, разработчики непреднамеренно вступают на темный путь, ведущий к таким методикам разработки, которые иначе чем убогими и не назовешь.

Небольшое мысленное упражнение даст вам почву для размышлений. Представьте, что вы только что написали самое замечательное в мире веб-приложение с использованием Zend Framework. Клиент поражен, его восторги (и деньги) крайне приятны. К несчастью они забыли упомянуть, что их новый технический директор требует использовать Symfony во всех новых приложениях и предлагает крайне интересную сумму за преобразование вашего приложения. Вопрос: насколько это будет просто? Задумайтесь об этом на секунду…

Если логика вашего приложения завязана на модель — вы на коне! Symfony, подобно многим (но не всем) фреймворкам, принимает модели вне зависимости от того, поверх чего они написаны. Вы можете перенести вашу модель, ее юнит-тесты и вспомогательные классы на Symfony ничего или почти ничего не меняя. Если вы связали все это с контроллерами, у вас проблемы. Вы действительно считаете, что Symfony сможет использовать контроллеры Zend Framework? Что каким-то волшебным образом заработают функциональные тесты, использующие PHPUnit-расширение Zend Framework? Оба-на. Вот почему контроллеры не способны заменить модели. Их практически невозможно использовать повторно.

Непонятые, недооцененные, нелюбимые: модели в депрессии

Так как разработчики очень часто занижают роль модели, ограничивая ее доступом к базе данных, как это по умолчанию делается в 99,9% фреймворков, нет ничего удивительного, что их не впечатляют связанные с ней теоретические идеалы. Сосредотачиваясь на доступе к данным разработчики полностью пускают один очень важный момент: классы моделей не связаны с текущим фреймворком. Им не требуется сложная установка, вы просто создаете и используете их объекты.

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

Модели в PHP — неудачники. С момента появления Smarty и его сородичей все увлечены представлениями. Контроллеры тоже очень важны, так как они считывают данные из базы и передают в шаблоны (общепринятая интерпретация VC). Да-да, контроллеры — это логичная эволюция въевшегося в мозг контроллера страницы, который используется каждым PHP-разработчиком и его собакой начиная с PHP3. Как минимум большинству это кажется очевидным. Мы разрушим миф о «контроллере = контроллеру страницы» позже.

А модели? Так как у них нет идеологической привлекательности или схожести со старыми привычками, люди рассматривают их как банальный «доступ к данным». Подобно ссылочным типам в PHP, указывающим на одно и то же значение в памяти. Язык изменился, но старые идеи все еще прячутся за кулисами, сбивая с толку наши нейронные сети.

Но постойте… ведь разработчики все-таки пишут работающие приложения! И если они не используют модели, содержащие логику приложения, то что же, черт побери, они используют?!

Толстые, тупые, уродливые контроллеры: смиритесь (Fat Stupid Ugly Controllers: SUC It Up)

Так как разработчики почти ничего не знали о моделях, они изобрели новое понятие: толстые тупые уродливые контроллеры (ТТУК). Столь яркое определение я придумал не просто так, оно кажется очень забавным в 10 вечера, после нескольких кружек пива. И все равно вежливее того, что я о них на самом деле думаю. (Fat Stupid Ugly Controllers — FSUC — FUC). Их изобрели потому, что модели были непривычными, чуждыми и похожими на террористов сущностями, которым никто не решался доверить хоть что-то выходящее за пределы доступа к данным.

Типичный ТТУК читает данные с базы (используя уровень абстракции данных, который разработчики называют моделью), обрабатывает их, проверяет, пишет и передает в представление для вывода на экран. Он невероятно популярен. Я бы сказал, что большинство пользователей фреймворков создают их так же естественно, как раньше создавали контроллеры страницы (Page Controllers). Они популярны, потому что разработчики осознали, что они могут обращаться с контроллерами почти так же, как с контроллерами страницы — это практически не отличается от древней методики использования отдельных php-файлов для каждой «страницы» приложения.

Не заметили ничего необычного? ТТУК выполняет все возможные действия над данными. Почему? Потому что в отсутствие модели вся логика приложения перемещается в контроллер, что делает его своеобразной моделью-мутантом! Я не просто так употребил слово «мутант». ТТУКи очень большие, громоздкие, уродливые и определенно толстые. Есть псевдо-программистский термин, очень точно описывающий происходящее — "раздутые". Они выполняют задачи, для которых никогда не были предназначены. Это полная противоположность всем принципам объектно ориентированного программирования. И они бессмысленны! По каким-то загадочным причинам разработчики предпочитают использовать ТТУКи вместо моделей, несмотря на тот факт, что такие контроллеры на самом деле просто модели-мутанты.

Помните наше мысленное упражнение? Если вы поместите все в контроллеры, перенос приложения на другой фреймворк станет крайне непростым занятием. Столь жесткое связывание фанаты Кента Бека (Kent Beck) называют «кодом с запашком» (code smell). И это не единственный источник запаха в ТТУКах. ТТУКи большие, с массивными методами, множественными ролям (как правило по одной на каждый метод), множественными повторами кода, не вынесенной во внешние классы функциональностью… ночной кошмар тестировщика. Так как фреймворков много, вы даже не можете правильно применять разработку через тестирование (TDD) без написания собственных дополнений и необходимости обрабатывать объекты запросов, сессии, куки и сбрасывать контроллер входа (Front Controller resets). И даже в этом случае вам придется тестировать созданное контроллером представление, так как у него нет способов вывода, независимых от представлений!

Продолжим задавать вопросы! Как вы тестируете контроллер? Как вы рефакторите контроллер? Как вы ограничиваете роли контроллера? Какова производительность контроллера? Можно ли создать экземпляр контроллера вне приложения? Могу ли я последовательно объединить несколько контроллеров в один процесс и не сойти с ума? Почему я не использую более простые классы и не называю их моделями? Я вообще задумывался над этими вопросами?

Модели неизбежны (как смерть и налоги)

В ТТУКах совершается классическая ошибка. Считая, что идея модели глупа и простой доступ к данными работает лучше всего, разработчики неосознанно завязывают всю логику приложения на контроллер. Мои поздравления! Вы только что создали модель, дрянную, уродливую модель-мутант, настаивая, что она контроллер. Но этот самозваный контроллер крайне сложно переносить, тестировать, поддерживать и рефакторить (считая рефакторинг понятием из реального мира… такое бывает!) Природа контроллеров такова, что они тесно связаны с лежащим в их основе фреймворком. Вы можете выполнить контроллер только после инициализации всего набора MVC данного фреймворка (что скорее всего означает зависимость от десятков других классов!)

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

Модели неизбежны. Кто-то может называть ТТУК контроллером, но нас самом деле это контроллер+модель, крайне неэффективная замена модели. Некоторые люди просто посмеются над всеми этими дураками, рассуждающими о необходимости хороших и независимых моделей предметной области (good independent domain models), и продолжат писать запутанный код. Пусть смеются. Ведь именно им придется поддерживать и тестировать свой бардак.

Именно здесь большинство фреймворков для веб-приложений подводят своих пользователей. Они окружены огромным количеством маркетинговой чепухи, неявно предполагающей, что они предлагают полноценную модель. Вы хоть раз видели фреймворк, прямо говорящий нечто иное? В конце концов, это же MVC-фреймворк. Признание того, что разработчик должен писать М самостоятельно, может произвести плохое впечатление. Так что они прячут правду в множестве подробностей, разбросанных по документации доступа к данным или вообще не упоминают о ней.

На самом деле они предлагают только классы доступа к данным — настоящая модель отражает особенности конкретного приложения и должна разрабатываться независимо, после общения с клиентами (можете сами подобрать для этого красивое название — лично я предпочитаю «экстремальное программирование»). Ее надо тестировать, проверять, обновлять и вероятность успеха/провала будет неизменной, вне зависимости от используемого фреймворка. Плохое приложение на Rails останется плохим приложением на Code Igniter.

Контроллеры не должны охранять данные

Еще одним следствием всеобщего недоверия к модели является то, что разработчики стараются использовать ее по минимуму и доверяют контроллерам новую роль хранителей данных (одна из главных причин их мутации в ТТУК). Хотите, я еще сильнее разожгу огонь всеобщего несогласия со мной?

Некоторое время назад я писал проект (Zend_View Enhanced), который рано или поздно будет принят в Zend Framework для внесения объектно-ориентированного подхода в создание сложных представлений, и начал жаловаться на то, что контроллеры являются единственным методом передачи данных из моделей в представления. Я считал, что представления могут обойтись без посредника и использовать вместо него помощников представлений (View Helpers) для чтения данных напрямую из моделей. Это приведет к архитектуре, в которой для многих страниц, доступных только для запросов на чтение, ваше действие контроллера (Controller Action) будет… пустым. В нем не будет никакого кода. Аминь!

Самый лучший контроллер для меня — это отсутствие контроллера.

Я немедленно столкнулся с массовым сопротивлением. Очень немногие поняли, что пустой контроллер, где все взаимодействие с моделью вынесено в простые и повторно используемые помощники представления, сократит повторяющийся код в контроллерах (очень большое количество повторяющегося кода!) и избавит от необходимости выстраивания цепочек действий контроллеров. Вместо этого я услышал немало заковыристых выражений. Многие считали, что MVC работает следующим образом: запросы идут к контроллеру, контроллер получает данные из модели, контроллер передает данные из модели в представление, контроллер отрисовывает представление. Другими словами: контроллер, контроллер, контроллер, контроллер. Однажды я заметил, что сообщество просто одержимо контроллерами. До сих пор очень сложно добиться того, чтобы кто-нибудь дал представлениям объекты с данными и позволил им самостоятельно читать данные из моделей…

Примечание: не все так плохо — некоторые люди поняли о чем речь

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

Так почему бы не пойти дальше! Скольким представлениям хватает одной модели? Многие мои представления используют несколько моделей, обращения к которым очень часто повторяются. Помощник представления — это один класс, но для добавления повторяющихся обращений в контроллеры нам надо повторять эти обращения во множестве методов!

Для избавления от помощника представлений было придумано одно сумасшедшее решение, при котором действия контроллера переизобрели как многократно используемые команды. Если представлению требуются несколько моделей, вы просто последовательно вызываете несколько контроллеров. Эту идею я нередко называю сцеплением контроллеров (Controller Chaining) — его смысл в создании служебного кода, делающего возможным повторное использование контроллеров. Можете перевести его как: многократное использование любого класса, необходимого для выполнения конкретного действия контроллера. Не забывайте — ни один контроллер невозможно использовать без инициализации всего фреймворка. Хотя есть (всегда есть!) исключения.

Модели — классы, контроллеры — процессы

Мои нестандартные идеи наверное вас уже утомили, но вышеупомянутое сцепление контроллеров требует более подробного рассмотрения. Сцепление часто используется для доступа к нескольким моделям или объединения результатов нескольких представлений или и для того и для другого одновременно. Последнее случается чаще всего — если вы не используете помощники представлений для упрощения процесса, то контроллеры почти всегда обращаются к моделям и передают данные в представления.

Представьте, что вы создали три контроллера, каждый из которых создает представление. Чтобы создать некую новую веб-страницу вам необходимо объединить три представления в единую страницу по шаблону (или макету). Это делается последовательным вызовом всех трех контроллеров через Zend_Layout (или какое-нибудь другое решение для сборки представлений в один макет/секцию). А теперь посмотрим, что получилось — три контроллера означают, что мы три раза выполняем стек MVC. В зависимости от приложения это может привести к значительным затратам ресурсов. Просто для примера величины этих затрат, Symfony использует «компоненты» (“Components”) как специализированные типы контроллеров, предназначенные исключительно для смягчения удара по производительности, но в Zend Framework нет ничего подобного. Схожая идея в Rails вызывала массу жалоб на падения производительности. Расхожая мудрость гласит, что использование нескольких полных контроллеров в фреймворках чудовищно неэффективно и является последней надеждой в тех случаях, когда нет иных стратегий повторного использования.

Повторюсь, сцепление контроллеров — это код с запашком. Оно неэффективно, неуклюже и как правило не нужно.

Альтернативой, само собой, является использование частичных представлений (кусочков шаблона, способных объединиться в одно родительское представление), способных напрямую взаимодействовать с моделью через помощник представления. Избавьтесь от контроллеров в целом — в конце концов в них нет никакой логики приложения, кроме передачи пользовательского ввода соответствующему вызову модели (за исключением ТТУКов).

Основная идея в том, что модель — это просто набор слабо связанных классов. Вы можете создавать и использовать их экземпляры где угодно — в других моделях, контроллерах и даже представлениях! С другой стороны, контроллер является неотъемлемой частью общего процесса. Вы не можете повторно использовать контроллер не запуская весь процесс создания объектов запроса, диспетчеризации, применения помощников действий, инициализации представления и обработки возвращаемых объектов ответа. Это затратно и неуклюже.

В завершение разговора

Как вы могли заметить, эта статья была жизненно необходима. Я твердо уверен в необходимости внедрения изящных принципов объектно ориентированного программирования в MVC фреймворки. Именно поэтому я всеми силами продвигал Zend_View Enhanced в Zend Framework и видел, как его одобряли, обсуждали и крайне успешно использовали. Во многом это произошло благодаря Мэтью Вейнеру О'Финли (Matthew Weier O’Phinney) и Ральфу Шиндлеру (Ralph Schindler), присоединившимся к продвижению этой идеи. Забыв о простых методиках и принципах ООП мы завязнем в борьбе с MVC, забыв о его смысле. MVC — это великий архитектурный шаблон, но в конце концов любые наши толкования MVC и привычные убеждения производны от принципов ООП. Забыв об этом, мы начнем делать Плохие Вещи (ТМ).

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

The M in MVC: Why Models are Misunderstood and Unappreciated
@Vedomir
карма
90,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (53)

  • –7
    Стена текста. Хоть изображениями/схемами разбавили бы.
    • +22
      А лучше комиксов добавить!
      • +4
        И сисёк не меньше 3 размера.
        • +12
          Статье про модели без фотографий моделей чего-то не хватает… ;)
    • +10
      Стена хорошего текста, имхо.
      • +1
        Просто мне лично сложно сконцентрироваться при чтении сплошного текста технического, внимание теряется по ходу чтения.

        p.s. я не говорил, что информация в тексте плохая.
        • +2
          Ну я просто уточнил. Однако, это разве технический текст, скорее программистско-философский. Впрочем, не суть важно, лишь вопрос терминологии.
  • 0
    Обычно я пропускаю статьи в которых упоминается PHP (да, предрассудки), но эта статья хороша. Она относится не только PHP, но и к другим веб-платформам, где «M» попирается всеми возможными способами.
    • +1
      И не только к веб-платформам.
      • +1
        Я бы сказал — не только к IT.
  • 0
    Практически полностью согласен с автором.

    Живой пример: на каждой странице Хабра есть sidebar «Лучшее», который берет данные с модели.
    Разве не проще позволить отображению «вытягивать» эти данные с модели, чем городить «виджеты», «субконтроллеры» или еще что-то.
    • +1
      Проще. Но вы когда нибудь поддерживали проект написанный так? Где хелперы вида — суть модель? А написанные не вами такие проекты? Мне доводилось. Это ад. И этот ад MVC ничем не лучше ада MVC. Так что вот.
    • 0
      Так виджеты (в статье названы помощниками действий) всего лишь позволяют избежать дублирования кода. Того кода, с помощью которого отображение «вытягивает» данные с модели.
  • 0
    В Zend_Framework идеология несколько другая. Модель в принципе там изначально отвечает именно за представление одного объекта. За «сохранение состояния» отвечают Zend_Db_table реализации. По логике вещёй именно они должны «швыряться моделями» из базы, каждая из которых желательно должна иметь возможность рулить своим состоянием в БД.
    Главная проблема Zend_Framework ИМХО в том, что валидация вынесена в формы. Я хотел в своём проекте втянуть это дело в саму модель, но тут возникает вопрос производительности — по-сути избыточная проверка. А при том, что ZF весьма небыстр и имеет весьма нехилый при запуске overhead — подобное решение может стать узким местом.
  • 0
    Скажите, в Zend и Symfony у каждого контроллера есть своя модель? Или сущности контроллер и модель не связаны друг с другом?
    • +1
      Вам ничто не мешает сделать ни то, ни другое :) Физически они ничем не связаны, и фактически вы можете использовать любую модель (из любого модуля) в любом контроллере другого модуля.
      С другой стороны вы можете создать свой класс с реализацией, при которой создаются именно связанные модель и контроллер. Это будет иногда неплохим применением convention over configuration pattern
    • 0
      Скорее односторонняя связь многие-ко-многим. Любой контроллер может создать любую модель, а может не создавать вообще. А модели о контроллерах вообще ничего знать не должны по идее.
  • 0
    Книга, о которой говорит автор — www.survivethedeepend.com/
  • +2
    Отличная статья. Прекрасно описана проблематика. Полностью согласен с посылом. Одного прошу: Научите

    Я не представляю как в ZF посадить на диету контроллеры, когда весь мануал напичкан примерами его кормления. Да и компоненты (особенно Form/Filter/Validate), как было отмечено выше, ну никак не хотят становиться частью «модели».

    К слову, я за годы так и не научился делать «слабо связанные классы» моделей. В ZF

    И да, я все ещё предпочитаю толсто собрать SQL-запрос (Zend_Db_Select) в контроллере на основе параметров запроса, нежели написать модель с тремя десятками методов в духе getLastArticlesInTopicByAuthor.
    • НЛО прилетело и опубликовало эту надпись здесь
  • +5
    А есть ли в природе ASP.Net MVC -приложение, разработанное по этой RICH-концепции? Самые известные примеры MVC Music Store и NerdDinner (можно сказать классика) — модели практически пустые, вся логика в контроллерах.

    Music Store:
    Модель
    Контроллер

    NerdDinner:
    Модель
    Контроллер

    Чаще всего разработчики начинают изучение ASP.Net MVC с этих примеров и сами пушут в подобном стиле. Модель — простой класс с валидацией данных, контроллер — вся логика.

    Может есть причина для ASP.Net использовать именно такой подход?
    • –1
      Есть: «Developers, developers, developers!» ©
  • 0
    Книгу на русском увидеть хотелось бы.
  • +1
    Мне иногда кажется, что MVC — треугольник.
    Model — View — Controller: выберите два.
    • 0
      Не согласен.
      В частных случаях — может быть, в общем — все становится монструозным.
      • 0
        Я не говорю, что это хорошо. Мне кажется, что так часто получается.

        Ниже приводят варианты того, как разнести логику по разным сущностям. Но при этом то, что получается, уже не будет MVC. Если оставаться в пределах MVC, то что-нибудь начинает перевешивать в ущерб остальному.
        • 0
          Да, так получается, если следовать примерам к использованию framework'ов.
          А осмелиться сделать шаг назад и взглянуть на всю картину целиком, отбросив привычные способы работы и взяв на вооружение чистые концепции ООП — это может далеко не каждый. А кто осмеливается, пишет вот такие статьи. Хорошо, что после получения опыта разработки в разных направлениях (desktop, web, gameplay, IDE plugin, etc.) начинаешь чувствовать правильное направление. Но для этого нужна хорошая почва на основе базовых концепций. Если ее нет — это первое, чем необходимо заниматься в плане саморазвития. Отсутствие своего мнения с твердой аргументацией мешает видеть проблемы и пути их решения.
          • 0
            Вообще-то в этой статье автор предлагает отказаться от контроллеров по максимому:
            Самый лучший контроллер для меня — это отсутствие контроллера.

            Никакого открытия при этом не совершается. То, что можно придумать многоуровневую архитектуру — в этом никто не сомневается. Я же говорю про MVC.

            А ООП так вообще всему этому ортогонально, пусть MVC и придумали Smalltalk-еры.
    • +1
      Или добавьте четвертое — которое относится только к конкретным технологиям.
  • +1
    — «Ты по политическим взглядам кто?»
    — «Мне нравится, когда у модели большие сиськи!»
  • 0
    Хороший перевод, спасибо! На статью сам недавно ссылался. Сейчас добавлю ссылку еще и на Ваш перевод :)
  • +2
    В хорошем ООП не должно быть жырных чего-либо. Именно в этом проблема всяких PHP фреймворков. Возьмём маленький пример, есть форма, при отсылке которой создаётся запись в базе. Например, форма добавления новости на сайт. По-хорошему, контроллер не должен быть жырным и делать всё на свете (чтение данных, создание модели, передача данных, валидация и т.д.) и жырная модель, которая всё это делать нам тоже вообще не нужна. Нам нужен маленький контроллер, который создаст обьект обрабатывающий данные из формы, этот обьект считает нужные данные и отдаст моделе, модель сделает валидацию и выплюнет результат. Обработчик формы оповестит об этом контроллер, который в свою очередь расскажет вьюшке что делать. В итоге у нас всё тонкое и каждый обьект занят своим делом.

    Городить огороды — всегда плохо.
    • 0
      У меня от вашего комментария когнитивный диссонанс. Хорошая орфография, терпимая пунктуация, даже ё — и ЖЫ.
      • 0
        И все-таки несколько запятых пропущены, да :-)
      • +1
        В пятый раз перечитывая коммент я, кажется, понял о чем вы: лично я слово «жырные» воспринял как авторский стиль (или цитату, которую, опять же лично автор, не счел нужным «закавычивать»).
        • 0
          Тоже сначала так подумал. Но «и отдаст моделе» разубедило.
  • +5
    Как-то я привык в последнее время разбивать логику на 4 части:

    — модель: не зависит ни от чего, обычные POPO, работают в вакууме, не знают даже что они веб запрос обрабатывают и в базе данных (как правило) хранятся. В них сконцентрирована логика решения бизнес-задачи, например, правила учета или игровая механика. С остальными частями приложений они имеют связь только в виде инжектируемых фабрик для создания объектов типа агрегирующих документов или ошметков трупов на карте. и то, лишь затем, чтобы остальное приложение узнавало о них по факту, а не рекурсивным обходом занималось когда нужно сохранить. Хотя бы интересный вариант, когда модель завязывалась через инжект на сервис генерации случайных чисел — ничего более разумного не придумал. В дополнении к основной модели могут быть модели к бизнес-логике слабо относящиеся, например модель ACL, которую использует соответствующий сервис

    — сервисы — обеспечивают прежде всего персистентность моделей, но не только. Как в примере из статьи — обеспечивают получение ленты из внешнего источника, причем не в виде XML или DOM, а как объект модели, представляющий фид, с агрегированной коллекцией итемов и т. п. Или хранит десериализованные объекты в сессии или куках. Сервисы знают всё о контрактах моделей. Знают, что им можно на вход подавать и что можно получать. Сервисы-фабрики создают новые объекты модели. В них же забита и валидация входных значений, и обработка исключений, выбрасываемых при противоречивых состояниях модели вследствие ошибок моделей (пользовательские ошибки отвественность исключительно сервисов). Физически представляют собой тоже POPO как правило, но уже знают о, например, базе данных (когда навороченная ORM внедренная через DI-контейнер, когда mysqli или PDO напрямую «захардкожены»). Активно используют более-менее независмые модули фреймворка, которые собственно на стэк не завязаны

    — представления — в принципе понятно, но могут обращаться к некоторым сервисам, для получения (read only) моделей вспомогательных для страницы объектов (сайдбаров). В принципе возможны сервисы, возвращающие непосредственно html из SQL, но как-то необходимости не возникало, оверхид «результат запроса»-«преобразование в объект или массив объектов модели»-«вывод модели» незначительный по сравнению с инициализацией стэка и SQL или других внешних хранилищ

    — контроллер связывает всё это воедино. Плотно завязан на фреймворк или обрабатывает запрос самостоятельно без FrontController. Обычно извлекает параметры запроса, передает их сервису, чтобы получить основной объект страницы, проверяет права, вызывает, если нужно и можно, метод модели, сохраняет если нужно её состояние и отображает представление, передавая ему, если нужно, основной объект. Редко когда больше десятка строк.

    Толстые у меня обычно получаются модели и сервисы, но для них, как правило, применимы обычные методы тестирования и рефакторинга, невзирая на особенности фреймворка или его отсутствие. При тестировании моделей сервисы-фабрики легко заменяются на примитивные стабы, возвращающие захардкоженные (в коде теста или внешнем файле). То же с высокоуровневыми сервисами — низкоуровневые замещаются примитивными стабами, возврающими массив или XML-документ. Без юнит-тестов остаются только низкоуровневые типа запросов к БД или внешнему серверу.

    В общем сервисы у меня выполняют роль помойки, куда «выкидываю» весь код, который мне не нужен в модели, контроллере или представлении. Прежде всего это кучи кода из контроллеров, относящиеся к хранению и валидации данных, а также получения моделей к основноой функции контроллера не относящейся.
    • 0
      Набор процедурных сервисов, как правило, можно заменить набором связанных моделей.
      Причем модель инкапсулирует и управляет своим состоянием.
      Просто в наше время почему-то народ увлекся процедурным подходом. Видимо потому, что ООП сложнее, требует продумывания use-case'ов, разделения на модели и обдумывание связей между ними, а также поддержание целостности концепции. Процедурный же подход намного проще: взял параметры, обработал, передал результат следующему обработчику, и так пока не дойдет до кода представляния. И то, что все это делается с использованием классов и объектов, не делает этот подход объектно-ориентированным. Это процедурное программирование с использованием объектов, не более того.
      • 0
        Кстати, модель потому и получается переносимой, что в ней инкапсулирована реализована концепция абстрактного типа данных.
        • +1
          Например, можно делать так.
          Есть у нас информация об автомобиле. Это — абстрактный тип данных. Что можно делать с информацией? Читать и писать. Но сама она себя читать и писать не может — это должен делать кто-то еще. Получается, что нам нужен Обработчик информации об автомобиле, умеющий ее читать и писать. Возможно, он является частным случаем обработчика (произвольной) информации. И как ему читать и писать? Для этого нужны инструменты (глаза = wrapper для ResultSet, шариковая ручка = wrapper для PreparedStatement, для примера), которые он использует. Он умеет обрабатывать информацию из модели и подготавливать ее для записи, а также записывать в модель после чтения. И, заметьте, ему пофиг, из базы читать или из файла — это же тоже абстрактный тип данных, специфику оставим классам реализации. А для абстрактного типа данных самое важное — интерфейс. Интерфейс — это сама суть абстракции. Нужно мыслить интерфейсами — тогда ООП получается сам собой. Забудьте про реализацию, когда строите модель. Реализацию нужно вспомнить лишь после построения модели — и, возможно, поправить абстракцию для соответствия реальным возможностям. Но наоборот нельзя — концепция, как правило, рушится при обратном подходе: от реализации к интерфейсу.
      • 0
        Кто сказал, что сервисы процедурные? Они имеют свое состояние и его инкапсуляцию (например, на этом уровне кэширование работает и/или запоминание времени начала события, к которому потом все привязываются как к атомарной операции). Просто как раз юз-кейсы как правило не описываются в терминах БД или html, там даже термины хранения частенько отсутствуют: раз создали документ — он существует пока его не уничтожили с составлением акта, а пока документ не подписан — он не существует. Нет бизнес-термина «сохранить документ».

        А концепция у меня как раз целостная (по-моему): в любой момент времени, когда модель получает управление, в ней уже содержатся непротиворечивые данные, равно как и когда она его отдает (без выброса исключений). А вот накладывать на объекты или классы моделей ещё ответственность за хранение состояния лично мне кажется нарушением единой концепции. В принципе, вы можете считать подавляющее большинство моих сервисов частью модели, просто я счел нужным отделить персистентность состояния от использования состояния на уровень не предусмотренный MVC — мой обычный контроллер обеспечивает обмен данными между моделью и представлением только в качестве реакции на действия пользователя. И то, лишь для того, чтобы явно показать в контроллере, что конкретное представление зависит от его действий.
    • 0
      Cервисы это как раз из Anemic Domain Model, Rich Domain Model подразумевает интеграцию сервисов в соответсвующий класс модели с которым этот сервис работает. По этому ADM часто грешит процедурной парадигмой.
    • 0
      Эх, каким идеалистом я был пару лет назад, думая, что все проекты можно переписать с нуля, или ошибки при плавном рефакторинге могут повлечь лишь недополучение прибыли :)
  • 0
    Я плохо знаком с MVC, но статья зацепила. Правильно ли я понял что
    Контроллеры генерирует Представление (UI)
    Пользователь взаимодействует с Представление передавая ему данные
    Представление транслирует данные Контролеру
    Контроллер передает эти данные Модели
    Модель производит бизнес логику
    Модель отдает данные Контролеру
    Контроллер создает Представление
    Пользователь видит новые данные?

    А вся проблема в том, что создание представления в контролере производится автоматически на основе каких то правил?
    И если так, то это вполне очевидно. Максимум отделять Бизнес логику от Логики отображения. Декомпозиция.
    • 0
      Проблем (причин для холиваров) две обычно:

      — сложно разделить (в смысле понять) где уже бизнес-логика, а где ещё контроллер подготавливает данные для передачи. Скажем, получение данных из БД — должен ли контроллер передать модели готовые данные (грубо говоря, параметры конструкторов/сеттеров) или должен лишь указать модели что (id или условие поиска) она должна из БД тянуть (а может не из БД, а из ФС или внешнего REST-сервиса). Кроме проблем хранения сюда ещё как минимум входят проблемы валидации и санирования, аутентификации и авторизации, кэширования и прочих «умных слов» в предметной области не присутствующих.

      — представление обычно требует не один-два объекта или коллекции модели, а несколько, десятки, а то и сотни, к основной семантике контроллера не относящиеся — на хабре практически весь сайдбар такой, да меню «личного кабинета». Плюс, как правило, эти дополнительные данные одинаковы для разных, а то и всех контроллеров и возникает желание сделать «хак» в виде обращения к данным модели, минуя контроллер.
  • 0
    а не устарела ли статья?
    или еще кто то из адекватных разработчиков пишет ТТУКи?
    или кто-то не передает модели в представления?

    Конечно нельзя не согласиться с автором что контроллер это мост между приложением и моделью. По большей части это же очевидно.

    Да, можно создать автоматического контроллера, а что кто то еще так не делает?
    В известных фреймоворках это все уже предусмотрено, есть возможность подключать внешние экшены. Например в Yii Акшен CViewAction позволяет автоматизировать вывод статических страниц.


    мда, статья действительно старая, датируется 2008 годом.
    зачем она здесь? ностальгия?

  • 0
    На самом деле концепция «Тощего контроллера, толстой модели» характерна только для бизнес приложений. Большинство же сайтов (СМИ, развлекаловка, да даже форумы) по своей природе просто не нуждаются в переносе логики в модель. У них почти нету «модели» и контролеры не раздуты. 90% запросов это чтение и оно должно работать быстро, если модель увесистая то сразу возникают проблемы с производительностью доступа к БД. В современном вебе (публичном, а не корпоротивном) надо делать что то простое но ОЧЕНЬ быстро, по этому чем меньше абстракций тем лучше. На самом деле MVC это слишком поверхностный взгляд на то, что происходит. Мне кажется куда важнее описывать конкретные абстракции(элементы системы), к примеру: СУБД<->ORM<->Controller->Template. И тут уже проще понять куда и что. К примеру часть валидации можно положить констрейнами в СУБД (Postgres к примеру позволяет просто кучу всего валедировать), часть проверок можно установить в объектах ORM, а что то уже запихнуть в контроллер и даже в template можно через javascript/html5 проводить часть валидации. На этом примере мы видим, что у некоторых задачь нету чёткого места, каждая задача проходит декомпозицию и раскидывается по элементам системы.
    • 0
      Я бы поспорил на счёт раскидывания некоторых задач по элементам системы, в т.ч. ту же валидацию. Её нельзя проводить ограничениями в СУБД. И нельзя — на объектах ORM. Какой смысл пропихивать кривой ввод с представления, через контроллер и модель — в СУБД? Терять контекст в случае фейла и затем протаскивать ошибку обратно через всё это счастье? И заодно потерять гибкость на 1% задач, где временно необходимо проигнорировать консистентность данных на время выполнения запроса. Нет, валидацию нужно производить максимально близко к входу, а именно в браузере и контроллере. Каждой задаче — своё конкретное место: View, Controller или Model. Просто не нужно забывать, что всё это счастье крутится внутри некоторой исполняемой среды и нужна прослойка между миром приложения и суровым внешним миром. Обычно это называют инфраструктурой, которая неявно пронизывает каждый из модулей\слоёв\уровней. Именно там и живут задачи, которые кажется что не имеют чёткого места. И все абстракции встают на свои места.
      • 0
        Если форма отправляется через AJAX то перехватить ошибку от СУБД/ORM и вывести её пользователю вполне можно (не теряя контекст). Но на самом деле всё это я привёл только как пример. Вы к примеру сделали вывод, который противоречит данной статье и привели вполне себе веские технические доводы.
        MVC слишком абстрактный «патерн», мне кажется он больше путает.
        • 0
          Можно — всё. Я уже написал про это ниже.

          MVC абстрактный паттерн, но не слишком. Он конкретным образом бьёт наше приложение на вполне себе явные модули. Которые внутри уже могут быть реализованы хоть с использованием СУБД, хоть ORM (на примере модели). Просто одной статьи из Википедии недостаточно, чтобы понять данный паттерн.

          А по поводу статьи — я с ней не спорил. По-моему в ней просто расписаны способы, как можно извратить изначальную концепцию. Но только практически все примеры из неё — это уже не MVC. Только в воображении авторов кода. ИМХО.
      • 0
        Конечная валидация может быть только на стороне СУБД. Дублирование первичных ключей или уникальных полей, нарушение ограничений внешних ключей и т. п. до записи в БД не проверишь (ну, если не ставить глобальных блокировок в самом начале обработки запроса). С другой стороны, многие валидации если можно провести на стороне СУБД, то по разным причинам нецелесообразно. А предварительную валидацию хорошо бы провести на стороне клиента вообще. То есть валидация уж точно будет размазана по приложению в целом — от браузера до СУБД.
        • 0
          Если вы так сделаете — то действительно, логика вашего приложения будет размана от клиента и аппликейшне сервера до самой СУБД. И валидация в том числе. Можно пойти ещё дальше и в коде приложения валидировать считанные из СУБД данные. И на клиенте, после загрузки страницы, заодно проходить валидатором. А потом! Можно в коде каждого метода добавить проверки на входные данные и бросать исключения, если что-то не так. И заодно проверять инварианты\выходные данные от методов! Ващеее, идеальный проект.
          • 0
            Это обычная практика минимум две валидации: почти всё в приложении и кое-что в базе. Обычно и третья на клиенте хотя бы средствами html5. Можно всё в базе, но не практично, как правило.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.