Personal tools
You are here: Home Статьи Введение в компонентную архитектуру
Document Actions

Введение в компонентную архитектуру

by cray last modified 2007-07-25 10:17

Тезисы доклада, прочитанного на Exception #5. Писалось как краткое и обзорное введение в компонентную архитектуру :).

Тезисы доклада:

Введение:

Благодаря опыту чтения курсов по программированию на Zope, для нас очевидно, что пытаться рассказать "Про Zope" в рамках относительно краткого доклада - невозможно. Поэтому был избран другой путь: рассказать о том, что выделяет Zope из множества аналогичных инструментов: Джанго, РубиОнРайлс или даже отличает Zope3 (о котором пойдет речь) от Zope2. Эта черта - компонентная модель. Особенно хочется подчеркнуть закономерность рождения компонентной модели в Zope3, так как она внесла в язык python отсутстующее там совершенство.

Питон, типизация и компонентная модель:

Питон имеет недоделанную систему типов: не будем вдаваться в подробности относительно того какая она хорошая или плохая - просто назовем ее недоделанной.

Для простого программиста это означает что ряд операций с типами и объектами, привычных для других языков, превращаются в груду кода, которого могло бы и не быть, приведем характерный пример:

                def a(b) :

                    if b is None :

                        ....

                    elif issubclass(b,B) :

                        .....

Решение такой задачи хорошо известно в рамках языков с "доделанной" системой типов: это либо подбор функции под заданные типы аргументы (C++, PSQL), либо - автоматическая или нет - конверсия типов (опять же C++, PSQL).

Так как потребность писать такой код есть, закономерно появилось решение: компонентная модель.

Сравнение компонентной модели и ООП:

Трудно сказать в недрах чего компонентная модель зародилась - претендентов много: CORBA, DCOM, JBean - но в языке питон она попала на благодатную почву, потому что оказалась способной заменить отсутствующую работу с типами.

Немного задвинем лирику и приведем сравнение двух парадигм: компонентное программирование против объектного. Основное отличие - вместо взаимодействия объектов рассматривается взаимодействие компонент, при этом:

Взаимозаменяемость:

Компонента - Экстенсиональное равенство;

Объект - Интенсиональное равенство;

Связывание с интерфейсом:

Компонента - Декларативное;

Объект - В силу наследования;

Преобразование:

Компонента - Проксирование (адаптация);

Объект - Невозможно (или преобразование структуры);

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

Дадим краткое формальное определение компонентной модели.
Объект
Некоторая сущность, обладающая состоянием и поведением;
Интерфейс
Декларация предпочтительного способа использования объекта;
Схема (Схема Интерфейса)
Декларация атрибутов и методов, предоставляемых интерфейсом;
Компонент
Некоторая сущность, предоставляющая предопределенный интерфейс и способная взаимодействовать посредством его с другими компонентами;
Адаптер
Программная реализация предоставления одного интерфейса через обращение к другому интерфейсу, предоставляемому объектом;
Утилита
Зарегистрированный под определенным интерфейсом и именем объект;
Реестр адаптеров
Реестр, позволяющий зарегистрировать адаптер и по данному преобразованию одного интерфейса в другой подобрать наиболее оптимальный адаптер;
Реестр утилит
Реестр, позволяющий зарегистрировать объект, и по затребованному интерфейсу и имени объекта найти наиболее подходящий объект;

Сделаем небольшой шаг - в пару десятилетий - назад в историю: Unix Way. Как его представляли современники? Как нечто, основанное на одном предположении: при наличии универсального интерфейса взаимодействия, легче написать четыре небольших программы, чем делающую то же самое одну большую программу. А далее произошел комбинаторный взрыв: четыре небольшие программы - это минимум 4! программ, что много больше 1 большой программы. Поэтому до сих пор умелый оператор юникса, используя знакомый с детства десяток команд, чувствует себя намного комфортнее, чем оснащенный сотнями программ виндоус-пользователь.

Сегодня та же самая аксиома Unix Way повторяется уже как Zope3 Way: легче написать 4ре простых компоненты, чем одну большую программу. И интерфейс взаимодействия между ними есть: это вышеозначенная компонентная модель.

Что такое Zope3:

Дадим краткое описание составных частей сервера Zope3.
  • Существует некий абстрактный способ взаимодействия объектов, называемый компонентной моделью;
  • Существует сервер, принимающий запрос из сети и представляющий его в виде компоненты-запроса;
  • Существует брокер запросов, который находит подходящий компонент, активирует его и собирает среду для взаимодействия с запросом;
  • Существует способ построение среды для взаимодействия с запросом;
  • Существует хранилище, в котором компоненты могут сохранять свое состояние.

Обратите внимание, здесь практически не говорится об объектах - только о компонентах. Благодаря такому упору на компонентную модель, программируя под Zope можно практически абстрагироваться от понятия "Объект", хотя естественно, все компоненты реализованы на основе объектов и все обычные методики ООП применимы в полной мере (хотя и не все они нужны).

Ссылки:

Некоторое время назад был подготовлен курс лекций по программированию под Zope3, с черновиками материалов которого можно ознакомится тут:http://code.keysolutions.ru/svn/Seminar/trunk/source/. Данная лекция представляет собой сильно сокращенный, хотя и обновленный вариант полного курса.

http://localhost80/++apidoc++
в вашем сервере Zope всегда доступна полная документация;
/novosti/otzyv-andreas-jung-a-na-knigu-web-component-development-with-zope-3/
отзыв на книгу по Zope3, саму книгу тоже хорошо почитать;
http://code.keysolutions.ru/svn/Seminar/trunk/source/
краткий курс лекций по программированию на Zope (черновик);
Продукты, которые выросли из курса лекций:
http://code.keysolutions.ru/svn/smarttools/
инструменты разные;
http://code.keysolutions.ru/svn/cachedconvertor/
кеширующий конвертор;
http://code.keysolutions.ru/svn/fuzzytraverser/
нечеткий траверсер;
http://code.keysolutions.ru/svn/rubricator
автоматический рубрикатор;

Примеры:

Что бы пояснить суть компонентного программирование под Zope3, приведем несколько примеров - от тривиальных (паттерн модель-вид-контроллер), до эзотерических (выбор хранилища аннотаций). Каждый приводимый пример - это конкретный код или конкретное решение из проекта - нашего или чужого.

Паттерн модель-вид-контроллер:

Основой Zope3, при обработке запросов, как и большинства современных решений такого рода, является паттерн модель-вид-контроллер:

Модель (Model)
Модель предоставляет данные (обычно для View), а также реагирует на запросы (обычно от контролера), изменяя свое состояние;
Вид (View)
Отвечает за отображение информации (пользовательский интерфейс);
Контролер (Controller)
Интерпретирует данные, введенные пользователем, и информирует модель и представление о необходимости соответствующей реакции.

model-view-controller

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

Теперь попробуем описать тоже самое с точки зрения компонентной модели:

Модель - компонент, предоставляющий ряд произвольных интерфейсов для взаимодействия с ним;

Вид - компонент, предоставляющий стандартные интерфейсы для получения, компонента-результата;

Контроллер - адаптер, обеспечивающий адаптирование пары (модель, запрос), представленной интерфейсами, к компоненту "Вид".

model-view-controller-zope

Что бы немного прояснить ситуацию:
  • Компонент-вид дополнительно идентифицируется строкой, что позволяет иметь несколько видов для одного объекта;
  • Адаптер ищется в реестре адаптеров, автоматически, по заданной тройке (модель, запрос, имя вида);
Что удобно:
  • Модель-компонент пишется только однажды и никогда более не модифицируется;
  • Навигация по сайту (как процесс перехода между именованными видами для различных моделей) пишется более-менее независимо, и последующее изменение моделей или дополнение новыми моделями - не меняет процесс навигации по сайту;
  • Введение новых моделей или переработка старых, равно как и разработка новых способов работы с ними - это только модификация или создание новых адаптеров;
Адаптация при индексации:

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

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

index-adaptation

Однако, в рамках компонентной модели, проблема легко решается: вводится компонента "Каталог", которая индексирует компоненты с интерфейсом "Поисковый Запрос", для каждого компонента пишется адаптер интерфейса, существующего у этого объекта, к интерфейсу "Поисковый Запрос". Получив компоненту для индексации, каталог адаптирует его к интерфейсу "Поисковый Запрос" и передает индексу.

В чем выигрыш:
  • Раздельно проектируется поисковая служба (форма запроса, формат индексов и т.п.) и компоненты;
  • Добавление новой компоненты не требует вмешательства ни в код компоненты, ни в код поисковой службы, а только разработку адаптера;
Аннотирование:

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

Аннотирование задействует три решения:
  1. Использование интерфейса IAnnotations для того что бы сохранять произвольный объект в некотором хранилище таким образом, что бы оставалась связь между этим объектом и аннотируемым;
  2. Способ использования интерфейса, полученного от данного объекта путем адаптирования;
  3. Компонентный способ проектирования хранилищ аннотаций, благодаря которому решение о том, как и где будут хранится аннотации полностью отторгается от самих объектов;
Интерфейс IAnnotations
это словарь, в котором каждому уникальному ключу поставлена в соответствие аннотация определенного типа.

Типичный код, использующий IAnnotations выглядит примерно так:

                    annotations = IAnnotations(obj)

                    na = annotations.setdefault(uniqkey,NoteAnnotation())

Здесь:
obj
контекст, приводимый к интерфейсу IAnnotations;
na
сам объект-аннотация;
uniqkey
уникальный ключ, используемый при сохранении аннотаций данного типа.

annotation

Благодаря интерфейсу IAnnotations, для любого предоставляющего его контент-объекта можно получить другой объект-аннотацию, который в свою очередь предоставляет некий интерфейс, Что бы еще более формализовать такое связывание, удобно ввести специальный интерфейc, смысл которого состоит в том, что объект данного сорта можно аннотировать объектом, с конкретным интерфейсом-аннотацией. Тогда полученние аннотации можно рассматривать как адаптирование интерфейса контент-объекта к интерфейсу объекта-аннотации, а для выполнения этой операции нужно написать специальный адаптер.

Пример:

Пусть интерфейс контент-объекта называется INoteAnnotable, а интерфейс объекта-аннотации - INoteAnnotation.

Приведем фрагмент конфигурации, содержащий объявление такого адаптера:

                        <adapter
                            factory=".noteannotableadapter.NoteAnnotableAdapter"
                            provides=".interfaces.INoteAnnotation"
                            for=".interfaces.INoteAnnotable" trusted="true"
                         />

и код этого адаптера:

                        from noteannotation import NoteAnnotation from
                        zope.app.annotation.interfaces import IAnnotations

                        from interfaces import noteannotationkey

                        def NoteAnnotableAdapter(context) : 
                            return
                                IAnnotations(context). \
                                setdefault(noteannotationkey,NoteAnnotation())

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

                        na = INoteAnnotation(obj)

Приведенный пример демонстрирует удобство, которое формализация в виде компонентной модели позволяет внести в работу с аннотациями на уровне исходного кода. Рассмотрим теперь работу с аннотциями на уровне веб-интерфейса пользователя.

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

                    <form schema="..interfaces.INoteAnnotation"
                        for="..interfaces.INoteAnnotable"
                        label="Annotation" name="annotation.html"
                        class=".edit.Edit"
                        permission="zope.ManageContent" menu="zmi_views"
                        title="Annotation"
                    />

                    schema -- схема формы,

                    for -- интерфейс, для которого форма будет активироваться,

                    class -- mix-in класс, осуществляющий предоставление и редактирование
                        данных.

                        "formzcml":img:formzcml.png

Mix-In класс директивы form:

                    class NoteAnnotableEdit(object) :
                        def getData(self,*kv,**kw) :
                            """ Получить данные """"
                            self.na = INoteAnnotation(self.context)
                            return [ (x,getattr(self.na,x)) 
                                for x in  getFieldNames(INoteAnnotation)]

                        def setData(self,d,**kw) :
                            """ Сохранить данные """
                            for x in getFieldNames(INoteAnnotation) :
                                setattr(self.na,x,d[x])
                            return True

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

Такое абстрагирования алгоритма связывания (например) внутри адаптера хорошо видно на проектировании хранилищ аннотаций:

Что бы сохранить аннотацию, от аннотируемой компоненты требуется интерфейс IAnnotations, который, собственно, и предоставляет схему атрибутов, используемых для сохранения аннотации. Но, сама компонента не имеет такого интерфейса, зато имеет интерфейс IAnnotable. Для того что бы реализовать свое собственное хранилище аннотаций, достаточно реализовать адаптер IAnnotable к IAnnotations, и в нем реализовать какой-то алгоритм хранения либо связывания аннотаций.

annotationadapter

annotationadapter-simple

В чем выигрыш:
  • Прозрачность для программиста, как и в случае с самой аннотацией, программист не замечает деталей реализации хранилища аннотаций;
  • Независимость: введение другого хранилища аннотаций вместо существующего приведет только к изменению адаптера, ни аннотация, ни аннотируемая компонента не будут затронуты;
Кеширование объектов:

В качестве еще одного примера рассмотрим smartimage.

Это продукт, который позволяет незаметно для пользователя подменять изображения их отмасштабированными копиями. Использование продукта требует регистрации и настройки утилиты SmartImageCache, которая посредством событий оповещается о создании, изменении или удалении рисунков. SmartImageCache, в соответствии со своими настройками масштабирует рисунок к необходимому размеру и размещает его внутри себя.

smartimg-view

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

Преимущества:
  • Только один раз нужно указать, что для получения изображения используется специальный вид:
                               http://localhost/img/@@smartlogo;
    
  • Подмена изображение на изображение из кеша, происходит прозрачно для программиста, и на самом деле он не знает что работает с откешированным изображением.

События

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

Компонент-нотификатор
порождает событие и отправляет в общий канал;
Компонент-обработчик
принимает событие и выполняет некоторую ео обработку;

Стандартные компоненты Zope спроектированы так, что бы порождать события по этапам жизненного цикла объектов: создание, модификация, удаление и пр.

eventinteraction

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

eventinteraction-simple

Преимущества:

  • Нотификатор и обработчик не требуют существования друг друга, их появление в системе приводит к началу взаимодействия и не требует каких-либо дополнитеьлных усилий;
Примеры взаимозаменяемости:

Таких примеров можно привести настолько много, и это настолько распространенная практика, что наверно просто достаточно рассказать суть и не приводить никаких примеров:

  • Zope предоставляет различные способы поиска компонент: это указание пути на графе объектов в объектном хранилище или же запрос компонента в реестре утилит по названию и интерфейсу - не столь важно (хотя последний способ имеет наибольшее распространение);
  • Всегда можно заменить одну компоненту другой, главное что бы их интерфейсы совпадали, такая подмена пройдет совершенно прозрачно для всей остальной системы, хотя ее поведение может сильно изменится;

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

Заключение:

Использование компонентной модели в Zope3 дает главное преимущество, обуславливающее выбор этой системы перед другими: независимость разработки. При чем, это не какая-то там независимость кода от дизайна или контента от движка по управлению контентом: при правильном проектировании проекта, практически все его части получаются независимыми друг от друга, и, при особенном навыке, даже от Zope. Связывание в единый проект происходит на самом последнем этапе разработки, и в течении всего время жизни проекта, любая его деталь может быть кардинально изменена с минимумом усилий.

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


Powered by Plone CMS, the Open Source Content Management System