Ты хочешь контролировать? Конечно ты хочешь! Контролировать это круто! Но я берусь утверждать, что иногда *слишком много* контроля, означает выполнение большей работы, если ты пишешь веб-приложение.
Эта блог-запись собирается быть трудной, потому что я уже знаю, она не будет легко понята любым кто не пробовал Zope. Я хочу донести до разработчиков и пользователей распространенных Python веб-фреймворков (Pylons, TurboGears, Django, etc). В частности, я хочу поговорить об “инверсии управления”.
Одна из распространенных жалоб на Zope в качестве веб-фреймворка, что его уровень “инверсии управления”, как правило, очень высок. Люди ненавидят “инверсию управления”. Вы чертовски хорошо знаете, что вы разрабатываете приложение с использованием Zope, поскольку он имеет тенденцию хотеть сделать многие вещи для вас, что вы бы в противном случае делали «руками» в других фреймворках. Например, он очень хочет помочь вам в соблюдении политики безопасности, своим особым способом. Он также предполагает, что вы готовы сделать некоторые вещи своеобразно, что Вы могли бы возможно сделать различными способами в других фреймворках. Например, предполагается, что вы чувствуете себя комфортно просматривая объекты «модели» приложения с помощью графа объектов (как правило, в пределах базы данных ZODB).
Вы должны чувствовать себя свободно, выбрасывая концепции Zope, там где вы уверены, что они неправильны по отношению к “инверсии контроля”, потому что большую часть времени вы будете правы. Втискивание логики вашего приложения в его ограничения, часто утомительно, а иногда это просто мартышкин труд. Мудрость толпы указывает, что “инверсия контроля” не очень популярна в наши дни, потому что она налагает ограничения.
Одной из причин по которой я вынужден был начал писать еще один веб-фреймворк(repoze.bfg), было желание написать фрейворк с пониженным “inversion of control”. BFG это фреймворк который нагло копирует Zope. Но он значительно снижает некоторые из глупых проявлений “инверсии управления” в Zope 2 и 3 в самых важных по моему мнению местах(например, он не имеет зависимости от ZODB, вам не нужно знать о интерфейсах, адаптерах и т.д чтобы использовать его). Но однако, он также избирательно сохраняет некоторые из паттернов “inversion of control”, впервые появившиеся в Zope. Это сохранение может показаться еретическим в наши дни, но я твердо верю, некоторая “инверсия управления” помогает обеспечить лучшее разбиение проблемы при написании веб-приложений. Если бы другие фреймворки поощряли (или хотя бы сделали возможным использовать без "плытия против течения") вид “inversion of control”, который я перенес с Zope в BFG, я не написал бы BFG вообще.
Одним из наиболее очевидных из этих случаев сохранения “инверсии контроля”, как по умолчанию работает авторизация(безопасность) в BFG. Для приложений, требующих безопасности, BFG обеспечивает более или менее полный набор инструментов, которые позволяет вам сделать декларативные утверждения безопасности, с помощью списков контроля доступа, состоящих из разрешений, названий групп и имен пользователей. Набор предположений сделанных реализацией по умолчанию, может быть заменена на другую реализацию пользователем фреймворка конролируемым образом. Но по больше части, никто не будет делать этого, потому что писать код относящийся к безопасности писать не очень весело, все будут использовать значения по умолчанию или будут использовать другие фреймворки. В любом случае, одна из основных концепций реализации политики безопасности по умолчанию, что списки ACL присоединены к объектам модели. Конечно, это означает что должно быть что-то что прикрепит эти утверждения к модели, поэтому они в дальнейшем могут быть найдены и обработаны, когда запрос достигнет приложения.
Когда запрос достигает приложения на BFG, в отличие от большинства других фреймворков, которые я видел(кроме Zope, конечно), первый шаг не заключается в том чтобы найти контроллер, который “делает всю работу”. Вместо этого, есть промежуточный шаг, который преобразует запрос (обычно через PATH_INFO присутствующий в окружении WSGI) в объект “модели”. Типичное BFG приложение использует обход графа для данного шага, но для это также возможно использовать другую стратегию (например, используя для этого Routes; и нет никаких трудностей чтобы путь находил/создавал модель вместо просто нахождения контроллера). В BFG как только модель найдена, начинается поиск “вьюхи” (иначе контроллер), в зависимости от типа объекта модели и данных в запросе. Объект модели затем передается “вьюхе” (иначе контроллеру) вместе с объектом запроса. “Вьюха” никогда не несет ответственности за составления объекта модели, это делается с помощью другой подсистемы. Я слышу “разрыв мозгов” сейчас, но не прекращайте чтение. ;-)
Содержание такого шага как “найди сначала модель” похоже наибольшее что отличает BFG(and Zope) от большинства существующих Python веб-фреймворков. Можно считать это вопиющим, но сохраняя некоторую “инверсию контроля”, когда фреймворк ответственен за более чем нахождение и запуск кода контроллера, имеет очевидную выгоду в довольно большом числе случаев. Если вы разделите ваш фреймворк так, что он сначала создает модель, фреймворк имеет шанс на обеспечение нормированой политики безопасности, вместо необходимости полагаться на спецефичные декораторы уровня приложения, прикрепленные к контроллерам или императивный код в вашем приложении. BFG делает именно это: он рассматривает объект модели на наличие ACL, и сравнивает его с учетными данными в запросе, и разрешает или запрещает дальнейшее исполнения кода “вьюхи”, основываясь на этом. Если пользователь не имеет разрешения, код приложения не вызывается вообще. Это все обрабатывает сам фреймворк.
В большинстве Python MVC фреймворках, существует явное возвращение контроля, что приводит к тому, что такого рода паттерн является редкостью. (“Это моё приложение, чёрт возьми, прекрати стоять на моем пути! Я просто хочу вывести некоторый HTML на экран прочитав из таблиц базы данных!”) В большинстве Python веб-фреймворков которые я видел (например, Pylons и Django), основная работа фрейиворка, это найти и выполнить код контроллера. Там нет шага, который находит сначала модель данных. Впоследствии, это работа контроллера (он же "вид") - создание объектной модели «на лету». Другими словами, нет никакой реальной “persistent manifestation” объектов модели в большинстве приложений на этих системах. Вместо этого они создаются напрямую кодом контроллера (как правило, с использованием некоторого ORM запроса). Таким образом, “объекты модели” на самом деле не существуют до того как код контроллера исполнится. Там просто не за что зацепиться.
С точки зрения безопасности, это ведет к тому, что растет количество декораторов (или старый добрый императивный код засоряет логику контроллера), которые фактически выполняют авторизацию. Если вы хотите чтобы метод контроллера был защищен какой-то регулярной политикой безопасности, вы оберните его в декоратор, который делает какую-то работу, чтобы выяснить, имеет ли вызывающий пользователь на самом деле право выполнить код контроллера, который вы защищаете. Это достаточно простой способ, и работает для большого числа приложений. И он может наложить довольно простую декларативную политику безопасности на любое приложение (с той оговоркой, что вы должны помнить, декоратор).
Но есть одна проблема с использованием декораторов поверх методов контроллера, чтобы обеспечить безопасность: иногда они не могут знать, что делать! В то время, как код декоратора выполняется, вы можете не иметь достаточно информации для обеспечения политики безопасности с учетом контекста. Конечно, вы знаете, что "Бобу разрешено редактировать записи» (основываясь на информации про безопасность во входящем запросе, который указывает что пользователь является Бобом, и он имеет право на редактирование блог-записей). Но что, если вы хотите знать, может ли Боб редактировать эту конкретную блог-запись (по конкретному URL)? Если Вы не разработали приложение достаточно тщательно, только код контроллера может знать, с какими объектами модели он взаимодействует, потому что он создает их. Но иногда интроспекция “модели” необходима для установления декларативной политики безопасности, особенно "контекстно-зависимой".
ИМО, поэтому так редко можно увидеть "защиту на уровне строк" в веб-фреймворках, которые не имеют определенный уровень инверсии контроля для подсистемы безопасности. Часто люди, которые на самом деле хотят "защиту на уровне строк" (иначе контекстно-зависимая декларативная система безопасности) иногда просто сдаются и делают принудительную проверку безопасности императивно в коде самого контроллера. Это потому, что в коде контроллера разработчик имеет достаточно информации, чтобы принять решение. По-настоящему умные люди использующие эти фреймворки, вероятно, используют свой собственный способ выражения “инверсии управления”: они, вероятно, создают “модели” в декораторе безопасности для того, чтобы получить достаточно контекста, чтобы принять решение. Я не могу знать наверняка, потому что я не достаточно читал кода и не использовал достаточно эти фреймворки. Может быть, вы можете сказать мне.
В любом случае, это пример того, как “инверсия управления” может реально помочь при создании веб-приложений: Вы можете писать меньше кода, и иметь достаточную уверенность, что вы можете создать действительно декларативную контекстно-зависимую политики безопасности, которая будет обеспечиваться должным образом. Код, который обеспечивает политику безопасности трудно написать правильно и не весело писать. Почему бы просто не написать его один раз и пусть фреймворк разруливает за вас? Особенно, если вы можете выйти из тупика путем замены на вашу собственную политику безопасности, если та что по умолчанию не работает для вас?
* Прим. пер.: В 2010 году BFG 1.4 был переименован в Pyramid 1.0