Аспектно-ориентированная веб-разработка и PHP - Web - Shelek
Уже много лет объектно-ориентированный подход к программированию пользуется широкой популярностью. В небольших краткосрочных проектах едва ли будут заметны его преимущества, но без него любой крупный проект фактически обречен. Именно объектно-ориентированные языки программирования содержат все необходимое для того, чтобы представить бизнес-логику проекта в наглядном виде. Даже при проектировании самой логики системы ныне напрашивается диаграмма классов UML. Наглядная бизнес-логика позволяет легко включаться в проект новым участникам, сберегает время авторам кода, вернувшимся в проект после длительного перерыва. Наглядная бизнес-логика ощутимо сокращает число ошибок в проекте. Но достаточно ли использования объектно-ориентированного подхода к программированию для того, чтобы достичь столь желанной наглядной бизнес-логики? Очевидно - нет. Добиться изящной объектно-ориентированной программной архитектуры достаточно сложно. Но если вы использовали приемы из книги Мартина Фаулера "Рефакторинг. Улучшение существующего кода", возможно, вам это удалось.

Однако даже теперь мы можем найти в коде сквозную функциональность (crosscutting concerns), участвующую в самых различных классах (протоколирование, кеширование, синхронизация, трассировка, контроль безопасности, контроль транзакций). Организовать подобную программную логику поможет AOSD (Аспектно-ориентированная разработка программного обеспечения).

Что такое AOSD?

Аспектно-ориентированная разработка программного обеспечения (AOSD) - это относительно новая парадигма разработки бизнес-приложений. Основа данного подхода - Аспект. Это точка зрения, с которой может быть рассмотрено какое-либо понятие, процесс, перспектива. Чтобы быстрее вникнуть в суть подхода, давайте рассмотрим веб-сайт в различных аспектах. Информационная архитектура описывает сайт в аспекте организации его структуры. Usability описывает сайт в аспекте удобства его использования. Графический дизайн представляет сайт в аспекте его визуального восприятия. Функциональная модель описывает сайт в аспекте его бизнес-логики. Все это различные составляющие процесса разработки веб-сайта, каждая из которых требует специфичных ресурсов, подходов и реализаций. Успешный проект подразумевает качественное решение со стороны каждого их этих аспектов. Если кому-либо данный пример покажется сложным, можно обратиться к более простой и универсальной схеме. Когда проектируется жилой дом, архитекторы проектируют каркасный чертеж, далее подготавливается схема электропроводки, схема водоснабжения и прочее. Очевидно, что каждый этап является самостоятельным, но необходимым для успеха всего проекта. Каждый этап - это аспект, в котором можно рассматривать проект. Как это ни банально, но в разработке программного обеспечения может быть использован тот же принцип выделения аспектов бизнес логики приложений. Более 20 лет назад Бьерн Страуструп воплотил в C++ компоновку программного кода в наборы логических объектов, поведение которых и соотношение друг с другом может быть определено различным образом (наследование, инкапсуляция, абстракция, полиморфизм). Это надежная, проверенная временем парадигма разработки программного обеспечения - объектно-ориентированное программирование. Однако этот подход имеет свои ограничения в декомпозиции аспектов бизнес-логики приложения. За прошедшее время было разработано множество новых подходов для преодоления данных ограничений. Среди них можно назвать адаптивное программирование, композиционные фильтры, гиперпространства, аспектно-ориентированное программирование, моделирование ролей, предметно-ориентированное программирование и т.д. Последнее время подобные изыскания стали подаваться под эгидой аспект-ориентированной разработкой программного обеспечения. Как уже говорилось выше, код сквозной функциональности так или иначе будет распределен по различным модулям, что наихудшим образом скажется на качестве программного обеспечения с точки зрения наглядности бизнес-логики, ее адаптации и способности к развитию. Задача AOSD в том, чтобы выделить сквозную функциональность и вынести ее за пределы бизнес-логики приложений.

Давайте представим какое-нибудь комплексное веб-приложение, например, CMS, и рассмотрим, на каком этапе и каким образом мы можем столкнуться с указанными проблемами. Допустим, что для некоторого числа функций системы требуется преобразование входных данных из XML в массивы. В данном случае аспект XML-парсинга затрагивает лишь небольшое число функций и не предполагает существенного развития. Как мы видим, здесь не требуется расширенная декомпозиция, нет очевидной необходимости в использовании AOSD. Логичнее всего определить эту процедуру в метод корневого класса (или же внешнего класса, смотря по обстоятельствам) и вызывать его по мере необходимости.



Рисунок 1. Приемлемая декомпозиция.

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



Рисунок 2. Неприемлемая декомпозиция.

Насколько было бы удобнее попросить систему в рамках некоторого аспекта должным образом обслужить заданные события в определенных объектах. Скажем, проходит время, и для нашего большого и сложного проекта приходят новые требования по безопасности. Мы пишем процедуру дополнительных проверок безопасности и требуем от системы в рамках аспекта безопасности при вызове заданных методов выполнять данную процедуру. Парадигма аспектно-ориентированной разработки программного обеспечения подразумевает это. Мы выделяем новый уровень абстракции вне существующей программной архитектуры и декларируем в нем функциональность, в том или ином отношении применимую к системе в целом. Как видно из рисунка 3, мы можем выделить программные процедуры, обслуживающие систему, например, в рамках аспекта безопасности и вынести их из основных классов. Далее мы можем проделать то же в отношении процедур аспекта контроля входных данных. Таким образом, при каждом проходе мы освобождаем бизнес-логику системы, делаем ее более наглядной. Фактически мы получаем в результате наглядную бизнес-логику в основных классах системы и аккуратно расфасованную сквозную функциональность вне базовой модели.



Итак, резюмируем:

* Основа аспектно-ориентированного подхода состоит в идентификации общности программного кода в рамках каких-либо аспектов и вынесении выделенных процедур за пределы основной бизнес-логики.
* Процесс аспектной ориентации и разработки программного обеспечения может включать моделирование, дизайн, программирование, обратный инжениринг, реинжениринг.
* Зона покрытия аспектно-ориентированной разработки ПО включает приложения, компоненты, базы данных.
* Взаимодействие и интеграция с прочими парадигмами осуществляется посредством фреймворков, генераторов, языков программирования и языков описания архитектуры (ADL).

Основы аспектно-ориентированного подхода

Аспектно-ориентированное программирование (АПО) позволяет выделить сквозную функциональность в отдельные декларации - аспекты. Можно определить функциональность для строго заданных точек выполнения программы JoinPoints (вызов метода, инициация класса, доступ к полю класса и т.д.). В языках, поддерживающих АОП, чаще используются назначения для множества точек - Pointcut. Функциональность в точках определяет программный код, который принято вежливо называть Advice (AspectJ). Таким образом, в пространстве аспектов описывается сквозная функциональность для определенного множества компонентов системы. В самих компонентах представлена лишь бизнес-логика, которую они призваны реализовать. В ходе компиляции программы компоненты связываются с аспектами (Weave).

Чтобы лучше понять базисы АОП, давайте вернемся к примеру с выделением аспекта мониторинга производительности (см. рисунок 2). Нам требуется снять показания таймера на входе и на выходе всех методов классов Model, Document, Record, Dispatcher. Итак, мы имеем аспект Logging. Нам потребуется завести к нему Pointcut с перечислением всех требуемых функций. В большинстве языков программирования, поддерживающих АОП, для охвата сразу всех методов класса можно использовать специальную маску. Теперь можно описать для этого Pointcut. Создаем Advice на входе в методы из списка Pointcut (Before) и на выходе из них (After). Advice для событий Before, After, Around - наиболее популярны в языках, поддерживающих АОП, но порой доступны и другие события.

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

Aspect - определение некоторого набора сквозной функциональности, нацеленной на конкретную задачу;

Poincut - код применимости аспекта. Отвечает на вопросы, где и когда может быть применена функциональность данного аспекта (см. рисунок 3)

Advice - код функциональности объекта. Непосредственно код функциональности для заданных событий. Другими словами, это то, что будет выполнено для объектов, указанных в Pointcut.

Все еще сложно вникнуть в этот подход? Полагаю, все встанет на свои места, когда мы привнесем немного предметности. Давайте начнем с самого простого примера. Я некогда написал эту маленькую библиотеку специально, чтобы проиллюстрировать как преимущества подхода АОП, так и его доступность. Кроме того, для того, чтобы воспользоваться данной библиотекой вам не потребуются глубокие знания PHP и специального программного обеспечения. Вам будет достаточно включить библиотеку aop.lib.php в ваши скрипты под управлением PHP 4 (или выше) в его стандартной комплектации. Мы можем определить некоторый аспект для сквозной функциональности (скажем, ведение журналов транзакций) посредством инициирования класса Aspect.

$aspect1 = new Aspect();

Далее мы можем создать Pointcut и сообщить, какие методы он затрагивает.

$pc1 = $aspect1->pointcut("call Sample::Sample or call Sample::Sample2");

Осталось лишь сообщить программный код для входной и выходной точек методов текущего среза.

$pc1->_before("print 'Aspect1 preprocessor<br />';");
$pc1->_after("print 'Aspect1 postprocessor<br />';");

Аналогичным образом мы можем описать дополнительный аспект, например:

$aspect2 = new Aspect();
$pc2 = $aspect2->pointcut("call Sample::Sample2");
$pc2->_before("print 'Aspect2 preprocessor<br />';");
$pc2->_after("print 'Aspect2 postprocessor<br />';");

Для того, чтобы задействовать один или несколько аспектов, достаточно воспользоваться функцией Aspect::apply()

Aspect::apply($aspect1);
Aspect::apply($aspect2);

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

Advice::_before(); и Advice::_after();
class Sample {
function Sample() {
Advice::_before();
print 'Class initilization<br />';
Advice::_after();
return $this;
}
function Sample2() {
Advice::_before();
print 'Business logic of Sample2<br />';
Advice::_after();
return true;
}
}

Как видно из примера, до кода бизнес-логики метода и после него установлены "оповещатели" этих событий. Когда процессор PHP минует такой "оповещатель", он проверяет, нет ли активных аспектов. В случае наличия такового, PHP проверяет, указана ли текущая функция в диапазоне Pointcut. Если и это условие верно, вызывается назначенная нами функция для данного события (например для Advice::_before()). Видите - как я и обещал, все достаточно просто. Но дает ли этот подход реальную пользу?

Давайте представим, что мы расставили во всех методах классов наших скриптов "оповещатели" и подключили библиотеку aop.lib.php. И вот однажды нам потребовалось получить подробный отчет о распределении нагрузки по выполняемым функциям нашего проекта. Мы создаем аспект и назначаем ему Pointcut, охватывающий все функции проекта.

$Monitoring = new Aspect();
$pc3 = $Monitoring->pointcut("call *::*");

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

function getMicrotime() {
list($usec, $sec) = explode(" ",microtime());
return ((float)$usec + (float)$sec);
}

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

Чтобы у вас не сложилось мнение, что АОП помогает решать лишь какие-то частные задачи, давайте рассмотрим еще пример.

Как известно, PHP лояльно относится к типам переменных. С одной стороны, это не может не радовать нас, так нет необходимости постоянно заботиться о соответствие заданным типам и тратить время на их декларацию. С другой стороны - это причина множества возможных ошибок. При большом количестве функций в проекте едва ли возможно удержать в памяти заведенный нами же синтаксис для них. Но достаточно лишь поменять местами аргументы функции, и ее поведение может стать непредсказуемым. Может ли здесь нам помочь АОП? А почему бы и нет?! Давайте вспомним диаграмму, приведенную на рисунке 2. Как мы видим, классы Document и Record содержат однотипные методы add, update, delete, copy, get. Хорошо организованная программная архитектура подразумевает однотипный синтаксис для этих методов: add($id, $data), update($id, $data), delete($id), copy($id1,$id2), get($id). АОП нам может помочь организовать как программную архитектуру, так и самих себя. Мы можем завести аспект валидации входных параметров и определить диапазон Pointcut для методов классов Document и Record. Функция события входа для методов add, update, delete, copy, get может проверять тип первого аргумента. Если он не является целочисленным (integer), то можно смело сообщать об ошибке. Можно также завести второй Pointcut для методов add и update. В данном случае будет проверяться тип второго аргумента. Он, очевидно, должен соответствовать типу массив (array).

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

Что особенно интересно, посредством АОП мы можем назначить специфичный вывод сообщения о системной ошибке для определенного набора функций. Допустим, ряд наших функций участвует в формировании кода разметки WML (WAP), WSDL (SOAP), RSS/ATOM или SVG. Разумеется, в данном случае недопустимо выводить на экран HTML-разметку с сообщением об ошибке. "Оповещатель" в обработчике ошибок PHP заставит систему отобразить сообщение либо в требуемой разметке XML, либо оповестить нас без отображения сообщения на экране (например, по Email).

Каждый, кому приходилось участвовать в разработке тиражируемых программных продуктов, знает, насколько непросто решить проблему обновления версий продукта. Конечно, все мы знаем о наличие специального программного обеспечения для контроля версий, например, о CVS (Concurrent Versions System). Однако проблема состоит в том, что для каждого нового продукта на базе нашего тиражируемого продукта требуется некоторая кастомизации, и часто достаточно сложно выяснить, не затронет ли обновление области, адаптированные под конкретный проект. Наверняка кто-нибудь из вас встречался с проблемой, когда после очередного обновления версии базового продукта приходилось восстанавливать весь проект из резервных копий. А представьте себе ситуацию, когда "пропажа" кастомизации в отдельных интерфейсах проекта выясняется спустя продолжительное время после обновления версии базового продукта! Вы скажите "А причем здесь АОП?!". Дело в том, что АОП как раз может помочь в решении данной проблемы. Мы ведь можем перенести весь код кастомизации проекта как сквозную функциональность за пределы основной бизнес-логики. Достаточно определить аспект наших интересов, указать область его применимости (Pointcut) и разработать соответствующий код функциональности. Имеет смысл взглянуть на то, как это может работать.

Вернемся к моему излюбленному примеру с ПО для управления содержанием сайта. Подобный продукт наверняка будет содержать функцию для отображения списков записей (recordsets) Record::getList() и подготовки кода для отображения данного списка View::applyList(). Record::getList() получает в качестве аргументов идентификатор recordset и параметры выборки в нем. Возвращает эта функция массив с данными по результатам выборки. Функция View::applyList() принимает на входе этот массив и формирует код оформления для него, например HTML-код таблицы. Допустим, в нашем продукте каталог товаров представлен в виде подобных списков записей. Это универсальное решение для тиражируемого продукта, но для конкретного проекта, базированного на нем, требуется отображать дополнительную колонку в списках. Например, в базовом продукте принято отображать таблицу с полями "артикул", "наименование товара". А требуется к ним добавить поле "Рейтинг покупателей". Для этого мы всего лишь пишем Advice для функции Record::getList(), по которому на ее выходе в возвращаемый массив будет введена дополнительная колонка. Если наша функция View::applyList() не способна автоматически адаптироваться под изменения во входящем массиве, то придется написать Advice и для нее. Допустим, спустя время заказчик потребовал от нас выделить все строки в списках, обозначающие товары, которые отсутствуют на складе. Мы дописываем Advice для View::applyList(), где сверяем значение атрибута записей "Наличие на складе" и соответствующим образом оформляем их. Обратите внимание, что мы можем завести отдельную папку Plugins для деклараций аспектов и скриптов их функциональности. Таким образом, вся кастомизация для любого из проектов будет сосредоточена в одно заданной папке. В дальнейшем у нас уже не будет проблем с обновлением версий. Мы сможем смело обновлять любые системные скрипты, за исключением тех, что представлены в папке Plugins.
Аспектно-Ориентированная Разработка ПО на PHP
В настоящий момент имеется ряд инициативных проектов, авторы которых представили различные способы реализации АОП и PHP. В проекте aoPHP представлен препроцессор PHP, написанный на Java 1.5. Мы можем писать привычный PHP-код, но должны будем сообщать препроцессору о нашем желании приобщения к АОП. Для этого вместо конструкции <?PHP .. ?> мы будем использовать <?AOPHP ?>. Сквозную функциональность мы сможем разместить в отдельных скриптах.

before(): execr(add($x,$y)) | execr(sub($x,$y)){
echo "<font color=red>Im About To Add/Sub $x & $y</font><br>";
}

Эти скрипты при необходимости могут быть задействованы путем указания при декларации кода AOPHP

<?aophp filename="aotest.aophp,aotest2.aophp" debug="off"
// PHP code
?>

В проекте Seasar.PHP применен иной путь. Здесь для структурирования деклараций аспектов используется XML, а компоновку производит сам PHP, после чего выполняет результирующий код посредством функции eval().

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

$pointCut = new PointCut();
$pointCut->addJoinPoint('Example', 'Foo');
$pointCut->addJoinPoint('Example', 'Bar');
$test1 = new Aspect($pointCut, before, 'echo "Before $MethodName";');
$test2 = new Aspect($pointCut, after, 'echo "After $MethodName";');

В отличие от библиотеки aop.lib.php в данном решении у вас нет необходимости расставлять "оповещатели" "вручную" для каждой функции. Но придется инсталлировать на сервере дополнительное расширение PHP PECL Classkit.

На мой взгляд, наиболее элегантное решение получилось у авторов проекта PHPAspect. Это стало возможным благодаря эффективному использованию новых возможностей PHP5, в частности, возможности создания абстрактных классов. PHPAspect вводит специальную конструкцию в язык PHP, которая наглядно представляет декларируемый аспект.

aspect TraceOrder{
pointcut logAddItem:exec(public Order::addItem(2));
pointcut logTotalAmount:call(Order->addItem(2));
after logAddItem{
printf("%d %s added to the cartn", $quantity, $reference);
}
after logTotalAmount{
printf("Total amount of the cart : %.2f ?n", $thisJoinPoint->getObject()->getAmount());
}
}

Как видно, в примере область заданного аспекта четко определена. Задание Pointcut и Advice столь лаконично, но емко, что складывается впечатление, будто это "родной" синтаксис PHP. Данный проект предлагает обслуживание событий Join point семи (!) типов: вызов метода (call), выполнение метода (exec), инициализация класса (new), запись в атрибут (set), чтение атрибута (get), деструкция класса (unset) и захват блока (catch). Возможно задание Advice трех типов: before, after, around. Проект позволяет использовать неожиданно гибкие маски для задания областей наблюдения в Pointcut. Так, к примеру, есть возможность задания области для всех классов с заданным префиксом в имени.

new(*(*));
exec(* Order::addItem(2));
call(DataObject+->update(0));

Для установки PHPAspect вам потребуется PHP версии не ниже 5.0.0 и установленные библиотеки PEAR Console_Getopt, Console_ProgressBar, PHP_Beautifier.

Данный проект был с успехом представлен в прошлом году на PHP конференции во Франции (на родине авторов) и, судя по всему, активно развивается и ныне. Вполне возможно, что Zend Inc. обратит на него внимание и учтет этот опыт в следующих версиях PHP.

Заключение

Безусловно, AOSD вовсе не панацея. АОП не избавит нас от ошибок в программном обеспечении, да и программы не станут писаться сами собой. Едва ли каждый программист получит по нобелевской премии, только за то, что использует АОП. Более того, нельзя сказать, что этот подход в программировании будет доминирующим в будущем. Вспомните, за два десятка лет объектно-ориентированное программирование обрело популярность, но по-прежнему большое число программистов ограничиваются процедурным кодом. С другой стороны, преимущества АОП перед ООП очевидны, так же как и преимущества ООП перед процедурным программированием. Можно сказать, что программисты, использующие АОП, находятся на шаг впереди. Их код лучше в плане удобочитаемости, соответственно, в нем меньше ошибок, он лучше подходит для развития. Таким образом, инженеры AOSD способны реализовать более масштабные, более надежные, более универсальные проекты, нежели программисты не использующие АОП. И заметьте, АОП не требует кардинальной переквалификации программистов. АОП не меняет логику программирования до неузнаваемости, как это было при переходе от процедурного программирования к ООП. Можно сказать, что АОП лишь расширяет ее. Проектируя новую программную архитектуру, вы лишь выносите из объектов все то, что "мозолит вам глаза", и определяете все это в подходящее место. Представьте, что вы упорно терпите в своем доме статуэтку, которая абсолютно не вписывается в дизайн интерьера, но дорога вам как память. Но однажды вы делаете скрытую от праздного взгляда нишу в стене, где все неугодные предметы находят свое истинное место. После чего остается лишь только восклицать: "Ну, правильно! Именно здесь оно и должно быть!".

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

Возможно, кого-то смущает тот факт, что официально в PHP не поддерживается АОП в настоящее время. Однако как раз в этой статье я показал вам на примерах, что без особого труда вы сами можете обеспечить поддержку основных принципов АОП. Не так важна реализация идеи AOSD, как ее суть. Какой бы вы образ ни выбрали, но если вам удалась более качественная декомпозиция в программной архитектуре, это в любом случае улучшит качество ваших программ. Сегодня АОП действительно поддерживается по большей части в расширениях к популярным языкам программирования, а не в самих языках. Однако ведущие игроки на рынке не остаются в стороне, и АОП постепенно пробивает себе дорогу в популярные программные платформы (http://www.internetnews.com/dev-news/print.php/3106021). Не стоит ожидать технологической революции под знаменами AOSD. Но эволюция неизбежна. Догонять ее или следовать во главе ее - выбор за нами. керама марацци
Information
  • Posted on 27.04.2013 17:20
  • Просмотры: 1914