Разработка сложных Web-приложений на примере Microsoft Active Server Pages

         

ASP-программирование: сравнение VBScript, JScript, PerlScript


    ASP-страницы, почему-то, всегда связывают со скриптом VBScript. Хотя это, наверное, худший из возможных вариантов. ASP могут быть написаны на любом WSH (Windows Scription Host)-cовместимом скриптовом языке. Рассмотрим 3 варианта - VBScript, JScript, PerlScript. Первые два - VBScript & JScript поддерживаются Miscrosoft, и не требуют дополнительных инсталляций. PerlScript автоматически устанавливается при инсталляции . Сравним эти три варианта.



Формированию уровня бизнес-логики


    Объекты 2nd tier принято разделять на две группы - представляющие клиента и его действия (Session Beans в EJB), и представляющие сущности источника данных (Entity Beans в EJB). Cущности источника данных (в частности - БД) выявляются и формулируются на фазе планирования / разработки логической структуры проекта. К сожалению во многих проектах эти фазы отсутствуют как класс и правит  тенденция к стихийному развитию структуры БД. Если вам достался подобный проект,  то придется заново тщательно проработать структуру базы данных, выявлять основные сущности и распределить все таблицы к конкретным сущностям. 

   Объекты, представляющие клиента на 2nd tier, реализуют возможные действия клиента. Эти действия осуществляются только через объекты - сущности (и не через прямой доступ к DB). Поскольку хардкодить все, даже самые незначительные, операции в компилируемых объектах возьмется далеко не любая компания, можно предложить писать эти объекты на ASP в страницах, не содержащих HTML (см. главу о вынесении HTML из ASP). Тогда получается такое изменение схемы распределенной архитектуры для IIS/ASP/COM:

    Мы можем выделить ASP-код, представляющий клиента, исключительно с бизнес-логикой, без HTML-форматирования и взаимодействия с пользователем. Как и в ситуации с хранимыми процедурами, такой отход от правила может быть, иногда, оправдан. Требования к нему просты: код бизнес-логики нужно организовать в независимую, от остального 1st tier, структуру объектов, которая не занимается делами 1st tier (т.е. никак не взаимодействует с пользователем напрямую). Этот код лучше хранить в отдельных файлах. А в обычном коде 1st tier использовать одинаковый интерфейс, способ создания и удаления таких встроенных ASP-объектов и настоящих 2nd tier объектов. В этом помогут, описанные ранее, Project ASP API и ObjectFactory. Тогда не возникнет "размазывания" кода бизнес-логики по интерфейсу пользователя, а, при необходимости, его легко можно будет вынести в настоящий 2nd tier объект.

   Объекты, представляющие сущности на 2nd tier, реализуют следующие услуги:

вернуть атрибуты сущности по уникальному ключу;

искать сущность по условию;

искать группы сущностей по условию или работать как итератор;

изменить атрибуты сущности;

создать новую сущность;

удалить сущность;

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



HTML-форма и ее ASP-обработчик в одном файле


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





Известные способы разделения HTML и бизнес-кода


    Можно ли как-то  изменить ситуацию со смешиванием ASP кода и HTML? Готовые решения, видимо, ждут нас в ASP+. Некоторые улучшения уже сейчас предлагает JSP, что не очень актуально для ASP. 

    Существуют, также, такие подходы как XML и CSS. XML, по своей идеологии, выглядит панацеей от проблемы смешивания информации и представления. Архитектор разрабатывает(если это необходимо) DTD-файл в самом начале проекта , XSL-файл отдает в разработку дизайнеру, а XML-файл генерирует программист.  Время покажет, какие ловушки приготовил нам XML, но пока не очень многие организации позволяют себе полностью отказаться от  HTML в пользу XML. Хотя когда-нибудь это произойдет.

    Однако, в том же HTML есть возможность частично вынести дизайн в отдельный файл - при помощи CSS. Только это желательно делать в самом начале проекта. Энтузиазм может придать осознание той мысли, что все записи типа <font size=...> или <b> и т.д. придется потом самому же вручную переписывать на <span class=...>. Первоначальный CSS файл программист может придумать самостоятельно - без дизайнера. Вот некоторые рекомендации, которые могут облегчить применение CSS:

стили задаются командами <span class=... > и </span>. Не следует использовать <font class=...> - т.к. тогда тяжело будет отследить наличие нестилевого дизайна; другие записи, например <td class=...>, вполне допустимы  ;

<b>,<i> - тоже должны быть заменены, хотя бы на <span class="bold"> и <span class="italic"> - это поможет дизайнеру;

не используйте имена цветов в названиях стилей. ( например, не надо писать class="while"). Если дизайн будет сильно меняться, это может оказаться другой цвет. Лучше используйте имена, отвечающие за позицию элемента на странице. Например class="body_text" - может означать обычный текст, а class="table_bold_link" - означать стиль ссылки в таблице, выделенную жирным цветом;




не перекрывайте форматирование CSS форматированием других тэгов (например <body text=... link=...>), которые вообще лучше оставить без атрибутов форматирования.

    CSS, без сомнения, может улучшить ситуацию. Но, во многом, дизайн все равно останется смешанным с кодом. Каким же образом разделить страницу на 2 части? Отдать дизайнеру-дизайнерово, а программисту - код?  Поискав в интернете, можно встретить несколько вариантов решения этой задачи. Наиболее распространенные советы - это вырезать из ASP кусочки HTML и сохранить  их в файлы, которые потом включить через SSI.  Второй подход - это HTML шаблоны, в которых заранее заданная подстрока заменяется на конкретные данные. 

    Если вы подумаете о  нелегком труде дизайнера, вы сразу поймете, почему так важно сохранить цельные HTML-страницы. Мир полнится рассказами о попытках вынести кусочки HTML во внешние файлы... Дизайнер обычно склеивает такие кусочки в один файл, редактирует его, и возвращает программисту на разрезание. Поэтому - возьмем за правило не искушать дизайнера неполными HTML-файлами понапрасну.

    Другой подход - это применение  HTML-шаблонов. Т.е. какая-то заранее заданная последовательность символов в обычной HTML странице заменяется на значение переменной. Например, пусть последовательность "[header]" заменяется на прочитанное из БД поле PAGE_HEADER, которое равно, к примеру, "Shop Information".

часть HTML-шаблона: результат:
<h1>[header]</h1> <h1>Shop Information</h1>
ASP-страница читает и обрабатывает нужный шаблон, а затем посылает результат клиенту одной  командой Response.Write().

    Этот подход лучше предыдущего, поскольку позволяет полностью разделить ASP и HTML,  причем HTML-файлы остаются удобными для редактирования дизайнером. Однако неспособность выводить таблицы с неизвестным заранее числом строк делает этот подход малоприменимым (ASP+, судя по имеющимся сведениям, имеет встроенные решения этой проблемы).


JScript (JavaScript)


    Одним из новаторских изобретений фирмы Netscape стал скриптовый язык JavaScript. Его синтаксис официально основывается на чрезвычайно популярном сейчас языке Java. А это, в свою очередь, говорит о схожести с C и C++. Особый интерес в JavaScript представляет оригинальная система динамического создания объектов, что позволят применять объектно-ориентированный подход. Несмотря на отсутствие инкапсуляции, странное наследование и неограниченный полиморфизм (проверки типов в JavaScript нет), применение объектов выводит программирование ASP-страниц на более высокий уровень, позволяя использовать стандартные паттерны проектирования, упрощая код и делает его более логичным, расширяемым и переносимым. Можно, например, создавать  оболочки (которые еще называют адаптерами или врапперами) COM-объектов (того же ADODB), более удобные для применения, и абстрагирующие основной ASP-код от этого-самого COM-объекта (позволяя, при необходимости, легко заменить его на другой объект 2nd tier).

    JScript является клоном языка JavaScript от Microsoft. Отличия JavaScript и JScript минимальны, однако их следует знать (отличия описаны в документации по JScript) и обходить (или абстрагировать), поскольку применение JScript в ASP открывает уникальную перспективу - возможность простого перевода Web-приложения, почти без переписывания основного кода, на технологию JSP (Java Server Pages). Это не означает, что мы собираемся бросать ASP и срочно переходить на JSP. Просто, в современных условиях, быть не привязанным к единственной платформе и/или технологии - очень ценное свойство проекта, которое, быть может, спасет его от гибели. Если грамотно абстрагировать ASP- и Windows-зависимый код в общие библиотеки,  то, переписав только эти библиотеки, мы, теоретически, получим JSP-сайт. (JSP страницы могут быть написаны на JavaScript, а конструкции <%...%>, <%=...%>  схожи и в ASP и в JSP). Это, однако, не относится к объектам 2nd tier, которые переводить придется отдельно.

    Достоинством JavaScript можно считать  его применение на стороне клиента (DHTML в Web-броузере), что позволяет создать "корпоративный стандарт". Т.е. программист-разработчик может применять единую технологию на стороне клиента и сервера (VBScript также можно использовать на стороне клиента, для броузера Internet Explorer, но это, обычно, не практикуется). Если разработки в компании ведутся на Java, С или С++, то JavaScript весьма гармонично впишется в рабочую среду. Недостатком конкретной реализации JScript можно считать отсутствие аналога VBScript-функции chrB(), что ограничивает возможность работы с однобайтовыми потоками данных. Что, с другой стороны, стимулирует вынесение сложного кода в объекты 2-nd tier.



Независимость ASP кода от источника данных, снятие нагрузки с IIS, упрощение ASP кода...


Мы уже рассмотрели, как вынести HTML в отдельный файл и отдать его дизайнеру. Но остался еще один кандидат на то, чтобы убрать его из ASP.  Это - SQL. Он усложняет ASP-код,  жестко связывает его с конкретным сервером базы данных, загружает IIS излишними вычислениями, снижает возможность масштабирования. Проще говоря - он превращает 3-х уровневое приложение в 2-х уровневое (клиент-сервер). 

    Рассмотрим архитектуру 3-х уровневого web-приложения, применительно к IIS, ASP и COM

    Где на данной схеме расположить ASP c ADODB?  Поскольку используется прямая запись SQL и однозначное обращение к конкретной базе данных, то можно утверждать, что ASP обращается непосредственно к 3rd tier, а ADODB является сервисом доступа к данным, и принадлежит 3rd tier. В 3-х уровневой архитектуре прямая связь 1st tier c 3rd tier недопустима. Говоря об SQL, можно утверждать, что он однозначно связывает код с выбранным типом сервера базы данных (будь то Oracle, MSSQL или что либо еще). То, что можно писать только на ANSI SQL, который будет работать везде, скорее всего миф. В любом случае это очень сложно реализовать.

    Посмотрим, что можно предложить.



Общая объектно-ориентированная библиотека: Project ASP API


    Для упрощения использования любой новой технологии (и для описанных выше конвертаций ASP проекта в проект JSP/PHP) жизненно необходимо абстрагировать системные средства ASP в библиотеку общего пользования. Эту библиотеку можно специально приспособить под конкретный проект, для значительного упрощения ее применения. Рассмотрим типичных кандидатов на помещение в общую, объектно-ориентированную, библиотеку. 

    Config - это объект, который хранит все настройки, общие для сайта в целом. Реально, они будут записываться при старте сайта в ASP коллекцию Application(), например из конфигурационного XML или INI-файла или просто из global.asa.  Но весь остальной ASP-код работает с конфигурационными параметрами исключительно через этот объект и к способу хранения/чтения этих параметров никак не привязан. Параметры конфигурации могут быть представлены, в объекте Config, как атрибуты объекта и/или как методы getStr("Section","Entry","Default"), getInt(...),  getBool(...), и т.д. 

    ConfigUser - этот объект хранит переменные текущей сессии пользователя. Абстрагируя переменные сессии в этот объект, мы получаем независимость от способа хранения переменных сессии. Мы можем как угодно менять и комбинировать способы хранения этих переменных: коллекция Session(), Cookies, Database, поля HTML формы - что угодно. При этом основной код будет оставаться неизменным. Доступ к переменным можно организовать аналогично объекту Config, но уже с возможностью модификации этих переменных - getStr(...)/setStr(...), и т.д.

    Тут возникает весьма неоднозначный вопрос - об именовании объектов. Как в ASP, так и в сервлетах/JSP общую конфигурацию рекомендуется  записывать в коллекцию Application (application), а пользовательскую - в Session (session). Проблема в том, что концепции коллекций Application и Session подразумевают то, что туда можно записать все, что угодно и время жизни этих коллекций ограничено. Они универсальны, и не приспособлены специально для хранения конфигураций. 



    Поэтому назвать наши конфигурационные объекты надо как-то по-другому (не application и session). Например: ConfigApp/ConfigUser, AppProperties/UserProperties, GlobalProperties/SessionProperties и т.п. Еще один фактор - это сокращения в названиях. Это не очень хороший стиль, поскольку многие сокращения могут оказаться непрозрачны для некоторых разработчиков. 

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

    ObjectFactory - если задуматься о будущем, то запись Server.CreateObject(...) в основном ASP коде покажется весьма ограничивающим фактором. Если понадобится использовать J2EE CAS COM Bridge (мост от Sun для использования EJB и обычных Java-классов через COM),  то процесс создание объекта уже будет отличатся, и запишется так (JScript):

_factory = Server.CreateObject("J2EECAS.JavaClassLoaderFactory");

_classloader = _factory.CreateClassLoader();

_class = _classloader.FindClass("java.lang.String");

objJavaStr = _class.New();

И основной ASP код придется переписывать. Для мостов CORBA придется использовать другую запись. Нет, конечно, может быть не все так плохо, и возможно для данного конкретного проекта никогда не понадобится связь ни с EJB, ни с CORBA, ни что-нибудь еще (пострашнее :) ). Но кто знает...  

    Если вы все же решились абстрагировать и эту сторону проектной реальности, то ObjectFactory, минимально должен предоставлять 2 метода - создание и удаление объекта. Не важно что обычно удалением объектов занимался сборщик мусора. Пусть даже наш метод удаления объекта на самом деле ничего не делает - это не важно. Пусть он будет, и пусть он вызывается когда нужно - будущее нас рассудит. 

    У каждого объекта 2-nd tier должен быть псевдоним для его идентификации при обращении к ObjectFactory. Например в основном коде будет запись ObjectFactory.CreateObject("MailSender"); И только ObjectFactory на самом деле будт знать что это COM объект с AppId "com.sa.soft.utils.SendMail005". 



    Параллельно ObjectFactory можно нагрузить мелкими функциями  - контролем всяческих пулов и кэшей, проверкой валидности объекта, etc. 

    И, чтобы уж совсем по-модному, доступ к вышеописанным объектам должен осуществляться через единственный объект-ядро:

    Core - этот объект должен присутствовать во всех ASP страницах  основного кода в единственном экземпляре (синглетонами также должны быть объекты Config, ConfigUser и ObjectFactory. а вот DB-объекты - необязательно).  Главная задача объекта Core - создавать и возвращать все описанные выше объекты написанной вами библиотеки, гордо именуемой Project ASP API. 

    Например, Core.getConfigApp() создаст объект ConfigApp и вернет на него ссылку. Повторный вызов не будет создавать объект повторно, а вернет ту же ссылку (чтобы гарантировать, что ConfigApp останется синглетоном). Помимо того, что основной ASP-код станет более понятным, мы получим весьма полезный эффект. Core может запоминать ссылки на все созданные им объекты. И вызвав в конце ASP-страницы метод, например, Core.close(), мы получим гарантированное

закрытие всех объектов, даже если мы забыли закрыть их в основном коде. Для объектов работы с базой данных это особенно критично.

    Конечно, стандартным должен быть и объект для работы с источником данных, но о нем позднее.


Общие сравнительные характеристики:


ASP Script: VBScript JScript PerlScript
Объктно-ориентированное программирование Нет Да Да
2nd tier на COM Да Да Да
2nd tier на CORBA Только через мосты Только через мосты Да
2nd tier на EJB Только через мосты Только через мосты Только через мосты
Схожие языки (для формирование корпоративного стандарта) MS Visual Basic, any Basic JavaScript, Java, C, C++  Perl, PHP
Проект может мигрировать на другую Web-технологию Нет Да, на JSP Да, на PHP или Perl CGI/ISAPI
Проект может мигрировать на другую платформу Нет Да, JSP на любой JSP-совместимой платформе Да, PHP/Perl на любой PHP/Perl-совместимой платформе

    Критериями выбора скрипта разработки для ASP могут стать:

возможности этого скрипта, как языка программирования

корпоративные стандарты компании 

возможности простой миграции проекта на конкурирующие технологии

простота поддержки

    В свете вышеописанных критериев, можно порекомендовать выбор в пользу JScript.  



Перемещаем SQL из ASP-файлов в SQL-шаблоны


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

    И, если слишком многое хардкодить в компилируемых объектах 2nd tier, то скриптовость ASP мы утратим.  Здесь уместно предложить разумный компромисс. Сложные операции клиента мы согласимся хардкодить в объекты 2nd tier. А вот со всеми SELECT и с единичными INSERT,UPDATE,DELETE (не требующих участия в сложных транзакциях) поступим проще. Вынесем их во внешние файлы, называемые SQL-шаблонами. С ними будет работать один унифицированный объект 2nd tier. 

    SQL-шаблоны могут иметь любую структуру - XML, INI, etc. Они, теоретически, могут храниться как угодно, и не обязательно в файлах. Главный принцип в том, что каждому SQL-выражению задается сущность, тип (SELECT, UPDATE, INSERT, DELETE) и присваивается имя. И основной код 1st tier обращается к уникальному SQL по комбинации: сущность+тип+имя шаблона. Затем  передаются входящие параметры и (в случае SELECT) получаются исходящие. Т.е., основной код 1st tier и клиентских Session-объектов с SQL больше никак не связан. 

    По сути SQL-шаблоны - это очень простой способ реализовать все объекты сущностей (Entity Beans) быстро и унифицированно. 

    



PerlScript (ActivePerl)


Появившись как язык для написания UNIX-скриптов, Perl обрел новое призвание в Web-разработках благодаря своей простоте, уникальным возможностям для работы со строками, большому количеству библиотек и своей бесплатности. Использовать Perl как PerlScript в ASP несколько эффективнее, чем в качестве CGI или ISAPI, поскольку открывает доступ к стандартным и весьма полезным ASP-объектам (Server, Application, Session, etc.). Perl поддерживает объектно-ориентированное программирование, что дает ему преимущества, описанные выше для JavaScript. Недостатком (условным) использования PerlScript является необходимость его установки на сервер (усложнение deployment), а также то, что это свободно-распространяемый  продукт, безо всяких гарантий. С другой стороны, для стран СНГ эта характеристика присуща большинству программных продуктов, и потому этот недостаток неактуален. Решать, кто дольше просуществует и будет продолжать выпускать обновленные версии своих продуктов - Microsoft или ActiveState (разработчик ActivePerl) - тоже дело неблагодарное. Поэтому стоит изначально закладывать в проект возможность для перехода на конкурирующую технологию. Для ASP+PerlScript эта технология - PHP. Так же, как ASP+JScript можно перевести на JSP, так и  ASP+PerlScript можно перевести на PHP, поскольку скриптовый язык PHP по синтаксису близок к Perl. Этот переход видится немного более сложным, чем ASP+JScript->JSP, но вполне осуществимым. 

    К неоднозначным фактам можно отнести наличие у Perl большого количества библиотек. Несмотря на кажущееся явное преимущество, концентрация слишком большой функциональности в ASP-страницах (к чему склоняют Perl-библиотеки) приводит к нарушению баланса распределенной системы. Вместо того, чтобы выносить функциональность в 2nd tier объекты, разработчики размещают сложный и ресурсоемкий код в ASP-файлы, в результате система становится немасштабируемой, а IIS - перегруженным. Решением этой проблемы выглядит разработка 2nd tier COM-объектов на том же Perl (возможность писать COM-объекты на Perl  ActiveState предлагала, на момент написания статьи, за 110$). Опять же, мы имеем корпоративный стандарт - ASP+PerlScript & COM-объекты на Perl, со всеми преимуществами корпоративных стандартов.



Послесловие


   Не привязываемся к Microsoft, не привязываемся к языку скриптов, не привязываемся к

стандартным ASP-объектам, не привязываемся к MTS... (о стремлении к абсолютной свободе)

    Смысл этой статьи сводится к тому что проекту, при желании, можно придать довольно большую степень свободы. Даже без особых дополнительных затрат. Свободу от аппаратной/программной платформы, web-технологии, источника данных, инфраструктуры 2nd tier, от проблем с web-мастерами не разбирающимися в программировании и с бездарными в web-дизайне программистами (к коим относит себя автор статьи). Свободу от прихотей Microsoft, Sun, Oracle, Borland, Allaire, BEA и т.д. , которые привнесли в нашу жизнь все эти занимательные технологии  и часто приятно, и не очень, удивляют нас своими выходками.

    В 80-х годах SQL дал программистам относительную свободу от конкретной реализации сервера реляционной БД. В 90-х появился, например, J2EE, претендующий на предоставление свободы от конкретной реализации сервера приложений. Но почему бы не быть свободыми и от SQL, и от J2EE, даже строя проект на этих технологиях? :)

Август - Октябрь 2001

//



API для использования SQL-шаблонов


    Работу с SQL-шаблонами  будет осуществлять специально написанный унифицированный объект 2nd tier. А в ASP стандартная библиотека Project ASP API будет предоставлять удобную оболочку (адаптер) для этого объекта. Пусть этот адаптер называется EntityManager. Упрощенно, он должен предоставлять все те же 4 DML функции: SELECT, UPDATE, INSERT, DELETE.  Часто предлагается  использовать другие названия, чтобы подчеркнуть, что это уже не SQL, а абстрактная сущность уровня бизнес-логики.  "SELECT" называют, например, "Get", "UPDATE" -> "Amend", "INSERT" -> "New" или "Create", "DELETE" -> "Remove". Ограничения, также, накладывает используемый язык программирования. Например,  в JScript "delete" является зарезервированным словом и его нельзя использовать как имя. 

    "SELECT" можно разделить на 2 метода. Один возвращает единичную строку выборки. А второй возвращает объект-итератор, через который можно получить многострочную выборку. В JScript и PerlScript достаточно просто реализуется динамическое создание нового типа объекта и его возврат как результат метода. В обоих случаях "SELECT"-методов, атрибуты возвращаемого объекта будут соответствовать OUT-переменным SQL-шаблона. А, в случае многострочной выборки, объект будет дополнительно содержать  стандартные, для итератора, методы next() и eof(). Никто не запрещает также предложить другие удобные в работе методы. 

    Вот пример списка методов класса EntityManager: 

selectOne(entity_name, template_name, arg1, arg2, ..., argN) Этот метод получает имя сущности, имя шаблона и список входных параметров шаблона. Как результат, возвращается объект, содержащий  выходные параметры шаблона в виде своих атрибутов. В JavaScript это реализуется при помощи функции eval(): например так:

s = "this.sFirstName='"+sFirstName+"';\n";

s += "this.sLastName=...

...

obj = eval("new function(){ "+s+" } ");

return obj;

Все эти механизмы скрывает адаптер EntityManager.

Он взаимодействует с 2nd tier объектом, который может  передавать сразу все полученные атрибуты в виде пригодной для eval() строки. Таким образом будет экономится время на вызовы 2nd tier (все атрибуты будут получены одним вызовом) и на формирование JScript-объекта.

selectSet(entity_name, template_name, arg1, arg2, ..., argN) Этот метод возвращает объект-итератор, атрибуты которого аналогичны описанным для selectOne(), а методы стандартны для итераторов: next(), eof(), и close()
selectAttr(entity_name, template_name, arg1, arg2, ..., argN) Возвращает значение первого выходного параметра шаблона (для простейших SELECT-выражений, выбирающих 1 значение)
exists(entity_name, template_name, arg1, arg2, ..., argN) Возвращает TRUE/FALSE при наличии/отсутствии результирующих строк у SQL-шаблона типа SELECT
update(entity_name, template_name, arg1, arg2, ..., argN) Реализует DML-команду UPDATE 
insert(entity_name, template_name, arg1, arg2, ..., argN) Реализует DML-команду INSERT 
remove(entity_name, template_name, arg1, arg2, ..., argN) Реализует DML-команду DELETE 
close() деструктор объекта EntityManager
<


    Вышеописанные методы имеют переменное число аргументов, что совсем не является проблемой для Perl,  а в JavaScript массив аргументов метода объекта (внутри этого метода) получается через запись this.имя_метода.arguments;    

    Очень часто более сложную бизнес-логику для операции с сущностями абстрактно можно свести к одной из 4х вышеупомянутых DML команд  - SELECT, INSERT, UPDATE, DELETE. И так же нужно передать входные и получить выходные параметры. А это означает, что мы можем использовать одинаковую форму записи вызовов к SQL-шаблонам и к сложным операциям бизнес-логики 2nd tier. Корректную переадресацию вызовов инкапсулирует EntityManager, действуя в зависимости от имени шаблона.  Например, вызов SQL-шаблона сущности Shop, типа INSERT, c именем "NewShop", может переадресовываться сложному объекту 2nd tier, создающему новый экземпляр сущности Shop и выполняющий соответствующие действия.  Основной ASP-код не будет никак от этого зависеть, т.к. форма вызова остается унифицированной.    

    Приведенный пример достаточно прост. В нем отсутствуют, в частности, средства управления транзакциями и т.п. Однако даже описанным API (который разрабатывается достаточно быстро) можно решать вполне широкий круг задач ASP проектов.

    

разделения монолитной ASP-страницы


Вам, вероятно, интересно будет взглянуть на то, что станет с обычной ASP-страницей, переработанной по описанным методикам.  Вот "притянутый за уши" пример:

Монолитный ASP-файл: showcustomers.asp Разделенные файлы:

<%@Language=JScript%> <html> <head> <title>Customers</title> </head> <body> <% dtBefore = Session("Date_Before"); dtAfter = Request.Cookies("Date_After"); if ((dtBefore==null) (typeof(dtBefore)=="undefined")){ // use default value if empty dtBefore = "01.01.2200"; } if ((dtAfter==null) (typeof(dtAfter)=="undefined")){ // use default value if empty dtAfter= "01.01.1950"; }

%> <h4>All Customers between <%=dtAfter%> and <%=dtBefore%> </h4>

<%

objADOConn = Server.CreateObject("ADODB.Connection");

objADOConn.Open("dsn=DSN_DBSAMPLE;");

rst = objADOConn.Execute(" \ select FIRST_NAME, LAST_NAME   \ from CUSTOMER    \ where REGISTER_DATE <= "+dtBefore+" \ and REGISTER_DATE >= "+dtAfter+" \ order by LAST_NAME, FIRST_NAME \ ");%>

<table><tr><td>First Name</td> <td>Last Name</td></tr> <% while (!rst.EOF) { %> <tr><td><%=rst.Fields("FIRST_NAME").Value%></td> <td><%=rst.Fields("LASE_NAME").Value%></td></tr> <% rst.MoveNext(); } %> </table> <% rst.Close(); objADOConn.Close(); %> </body> </html>

showcustomers.asp

<%@Language=JScript%>

<!--#include file="/libs/Core.inc"-->

<%

dtBefore = Core.getSession().getDate("Date_Before",

"01.01.2200"); // default value if empty

dtAfter = Core.getSession().getDate("Date_After",

"01.01.1950"); // default value if empty

objEM = Core.getEntityManager();

iter = objEM.selectSet("Customer","AllBetweenDates",

dtAfter, dtBefore);

%>

<!--#include file="_t_showcustomers.htm.asp"-->

<%

Core.close();

%>

HTML-шаблон: _t_showcustomers.htm.asp

<html> <head> <title>Customers</title> </head> <body> <h4>All Customers between <%=dfAfter%> and <%=dfBefore%> </h4> <table> <tr><td>First Name</td> <td>Last Name</td></tr>

<% while (!iter.eof()) { %>

tr><td><%=iter.sFirstName%></td> <td><%=iter.sLastName%></td></tr>

<% iter.next(); } %> </table> </body> </html>

SQL-шаблон (INI format): /entities/Customer/select/

AllBetweenDates.ora8

[Params] name=AllBetweenDates type=select entity=Customer INnames=dtAfter,dtBefore INtypes=d,d OUTnames=sFirstName,sLastName OUTtypes=s,s

[SQL] select FIRST_NAME, LAST_NAME    from CUSTOMER            where REGISTER_DATE <= ::dtBefore and REGISTER_DATE >= ::dtAfter order by LAST_NAME, FIRST_NAME

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



структуры SQL-шаблонов


Рассмотрим один из вариантов организации структуры SQL-шаблонов в виде каталогов и файлов.

Допустим, корневой каталог структуры называется просто "entities" ("сущности"). 

В этом каталоге находятся подкаталоги, соответствующие сущностям базы данных. На рисунке их четыре: Agent, Customer, Goods, Shop. (имена сущностей принято записывать в единственном числе).

В каждом каталоге сущности находятся 4 подкаталога, соответствующих типам DML SQL-выражений: SELECT, UPDATE, INSERT, DELETE. 

В каталогах DML-типов находятся сами SQL-шаблоны. Это просто файлы, у которых имя соответствует  назначению SQL-шаблона, а расширение, например, определяет тип DB сервера под который они написаны. (т.е. можно иметь несколько одинаковых SQL-шаблонов оптимизированных под разные типы SQL-серверов: Oracle, MSSQL, MySQL, etc.).

Дополнительно, в корневом каталоге "entities" может находиться общий конфигурационный файл доступа к DB. А в каталогах сущностей - опциональные конфигурационные файлы доступа к DB для каждой сущности. Таким образом, мы можем обеспечить инфраструктуру для хранения различных сущностей на различных DB-серверах. 

    Приведем пример файла SQL-шаблона в *.INI-подобном формате (исключительно для простоты, т.к. XML-формат файла в данном случае гораздо удобнее).  Это шаблон сущности Customer, типа SELECT, который производит выборку имен и фамилий всех клиентов, зарегистрировавшихся в промежутке между двумя заданными датами

[Parameters]

name=AllBetweenDates

type=select

entity=Customer

INnames=dtAfter,dtBefore

INtypes=d,d

OUTnames=sFirstName,sLastName

OUTtypes=s,s

[SQL]

select FIRST_NAME, LAST_NAME   

from CUSTOMER   

where REGISTER_DATE <= ::dtBefore

and REGISTER_DATE >= ::dtAfter

order by LAST_NAME, FIRST_NAME

В секции параметров шаблона мы видим 3 параметра(name,type,entity), ответственных за позиционирование шаблона (в случае, если файл попадет не в нужный каталог, эту ошибку легко будет обнаружить). 

Затем идут имена и типы входных параметров(INnames, INtypes). Входные параметры доступны в SQL выражении через синтаксис "::"+<имя параметра>. Типы задаются символами. Например, дата/время - d, строка - s, целое число - i, и т.д. 

Затем идут имена и типы выходных параметров - атрибутов(OUTnames, OUTtypes).

И, наконец, в секции [SQL] записано само SQL выражение. Его результат доступен 1st tier по именам, записанным в OUTnames.

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

Но на практике, это не слишком большая проблема.

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



Приоритеты в Business Web Application


Так или иначе, нам придется делать выбор - что важнее в Business Web Application с точки зрения пользователя (как источника прибыли), в условиях ограниченных ресурсов на разработку( временных, финансовых, кадровых и т.д.). Предлагаемая иерархия такова:

1. Функциональная адекватность

    - Приложение корректно выполняет свои функции в идеальных условиях (один пользователь в единицу времени, идеальное функционирование аппаратуры и т.д.).

2. Надежность в условиях ограниченной загрузки

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

3. Удобство работы, интуитивно понятный интерфейс

    - пользователям удобно работать с системой;

4. Возможность обслужить максимальное расчетное количество одновременно работающих пользователей

    - система готова надежно обслужить всех потенциальных пользователей;

5. Высокая скорость работы

    - короткое время отклика системы;

6. Красивый дизайн

    - дизайн системы эстетически приятен большинству пользователей;

    Это спорная схема. Если первейшая задача - продемонстрировать что-то несведущим инвесторам, что пункты 5 и 6, например, можно вынести на первые места.  Но для долгосрочного Business Web-проекта приведенная схема, вероятно, близка к реальности. И она хорошо показывает, почему существует так много медленных и не очень эстетичных Web-приложений. Cовременные условия разработки интернет-проектов - это спешка. А в спешке существует склонность не задумываться о вторичных приоритетах до момента выполнения первичных. Требуется "побыстрее что-то сделать". А когда это сделано, оказывается, что надо все переписывать - либо с нуля, либо по частям. Однако, даже если, допустим, пункты 3-6 не имеют изначально большого приоритета, о них все равно следует помнить, и закладывать в проект возможности их решения в будущем. Тогда это решение будет на порядок меньшим по затратам. 

    Большинство аналитиков сходятся в том, что только распределенная архитектура способна решить все вышеперечисленные приоритеты  в условиях ограниченных ресурсов. Рассмотрим эту архитектуру подробнее.



Проблема ASP-приложений: смесь HTML, SQL и VBScript, с трудом поддающаяся осмыслению


    Появление Active Server Pages(ASP) для многих стало знаменательным событием. Технологии-конкуренты  - Personal (в начале подразумевался Perl) Home Pages(PHP), Java Server Pages(JSP), ColdFusion Markup Language(CFML) и PL/SQL Server Pages (PSP) появились позднее и, частично, носили подражательный характер (что не уменьшает их достоинств). Наиболее интересными и полезными качествами, которыми привлекала технология ASP, можно считать:

удобный способ объединение Server-Side Script c HTML;

скриптовый  подход (интерпретируемый язык) - т.е. файл с исходным кодом ASP одновременно является его исполняемым файлом, что упрощает процессы разработки и поддержки;

концепция "Session"  - переменные для каждого пользовательского соединения, как удачное решение вечной проблемы stateless-протокола HTTP;

возможность организации распределенной архитектуры на основе инфраструктуры COM (Component Object Model), DCOM, COM+. Дополнительные возможности, предоставляемые Microsoft Transaction Server (MTS) - такие, например, как контекст объектов, пул и т.д.;

удобный набор объектов-утилит: Server, Application, Request, Response, Session, ObjectContext.

    Главной идеей и достоинством ASP сразу виделась возможность встраивать программный код, обрабатываемый сервером, непосредственно в HTML страницу (а не наоборот, как предлагал CGI). При всех больших возможностях этого подхода, он скрывает серьезную ловушку. ASP-файл, написанный подобным образом, может поддерживать только человек, хорошо владеющий как ASP-программированием, так и HTML. Что накладывает жесткие требования на подбор кадров. Встроенный в ASP-страницы SQL усугубляет ситуацию, усложняя код и делая его непереносимым на другой источник данных.

    К тому же, полученный подобным образом ASP-файл, в конкретный момент времени, может править только 1 человек. Грубо говоря, это означает, что если работает программист - то дизайнер спит. А если файл правит дизайнер - то спит программист. Т.е. наблюдается невозможность распределения труда там, где она потенциально возможна, и могла бы ускорить разработку.



    Применяемый, в быстро развивающемся Internet, такой неоднозначный подход, как  Rapid Application Development (RAD), еще больше обостряет ситуацию. Проекты часто разрабатываются на скорую руку - лишь бы что-то быстро показать инвесторам, а затем уже пересматривается архитектура, приглашаются профессиональные дизайнеры. Но эти дизайнеры  совершенно бессильны что-либо исправить в той кошмарной смеси HTML, SQL и VB/J/PerlScript которая, в общем-то,  разрабатывалась как прототип, и была, почему-то, принята за конечный продукт (по горячему желанию руководства), в котором надо всего лишь "немного улучшить дизайн". 

    Вышеописанное несколько напоминает распространенную ситуацию с популярными средствами  RAD в области разработки обычных Standalone-приложений. Такими как Delphi, C++Builder, Centura Team Developer, VisualBasic, и т.д. Когда код бизнес-логики оказывается "размазанным" по различным обработчикам элементов пользовательского интерфейса. Навсегда связывая проект с имеющейся технологией  и архитектурой  и затрудняя поддержку и расширение кода. Масштабирование(т.е., например, вынесение бизнес-логики в объекты 2nd tier - COM,CORBA,EJB), фактически, становится невыполнимым, поскольку код бизнес-логики придется, в буквальном смысле, "соскабливать" с различных ComboBoxes, TextFields, Buttons, и т.п.

    Таким "размазыванием" страдают и современные разработки на ASP. С другой стороны,  представляется вполне возможным избежать подобной ситуации. Просто осознавая с самого начала возможные неприятности в будущем, и закладывая в проект дополнительные степени свободы. Например, всегда хорошо иметь, про запас, возможность быстро сменить инфраструктуру Web-приложения  - т.е., например, перейти с ASP на JSP или PHP, без переписывания основного кода.  И эта возможность - вполне реальный эффект (причем м.б. даже побочный) хорошей организации проекта.



    Для начала, сформулируем проблемы, присущие многим ASP-проектам, с которыми мы будем бороться:

Смесь бизнес-кода и HTML приводит к трудностям поддержки и того и другого;

Наличие большого количества DB-зависимого кода в ASP-страницах привязывает их к источнику данных;

Перегруженность ASP-страниц функциональностью приводит к перегрузкам IIS (хотя это можно решить кластеризацией IIS);

Смысловая перегрузка ASP-страниц затрудняет их поддержку;

Хранение бизнес-логики в ASP-страницах в "размазанном" виде приводит к затруднению ее вынесения в объекты 2-nd tier (при необходимости масштабирования и поддержки разных видов 1st tier-клиентов);

Полная зависимость кода проекта от самой технологии ASP

Что предлагается делать в этой статье (кратко):

Вынести HTML из ASP-страниц в отдельные файлы;

Вынести SQL из ASP-страниц;

Абстрагировать Microsoft ASP-специфические возможности в объекты общей библиотеки;

Организовать все часто используемые функции в виде методов общей объектно-ориентированной библиотеки;

Использовать JScript или PerlScript  и отслеживать пути быстрого перехода на JSP/PHP, при возникновении подобной необходимости.




Распределенная архитектура, как наиболее подходящая для Business Web Application


    В этой схеме собраны почти все варианты  названий трех уровней распределенного приложения. Чтобы избежать путаницы, будем именовать уровни так: 1st tier, 2nd tier и 3rd tier, по причине краткости написания. Выбирать названия по другим критериям слишком сложно. Называть 3-х уровневую архитектуру N-уровневой вероятно не стоит, т.к. эти N уровней, обычно, появляются как более детальное изображение той же 3-х уровневой схемы, не внося принципиально новых идей. N-уровневые схемы удобны чтобы показать систему с точки зрения развертывания и администрирования. Но с точки зрения разработки эти дополнительные уровни  малоинтересны. 

    Три уровня (с позиции программирования) - это хранение, обработка

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

2-x уровневая архитектура (Клиент-Сервер)


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

3-x уровневая архитектура (идеальный вариант)

3-х уровненая архитектура подразумевает четкое выделение бизнес-логики.

Интересно отметить, что  сервисы для вызова бизнес-логики (2nd tier), относятся к 1st tier. А вот интерфейс с базой данных (или любым источником данных) - сразу к 3rd tier. Такой подход позволяет четко очертить границы уровня бизнес-логики и отличие 3-х уровневого подхода от 2-х уровневого (клиент-сервер).


3-x уровневая архитектура (фактический вариант)


На практике, мы имеем нечто подобное.  По различным причинам бизнес-логика остается на 1st и 3rd tier. Это может быть оправдано, если не приведет к перегрузке уровня, на котором находится ненормированный код. И если этот код легко можно перенести в 2nd tier объекты. Например,  поместив бизнес-логику в хранимые процедуры в 3rd tier, мы можем получить большой выигрыш в производительности. Если же эти хранимые процедуры написаны на Java, то перенести их в 2nd tier будет несложно. А вот бизнес-логика на 1st tier распространена повсеместно(как уже упоминалось), и, обычно, связана с неудачной архитектурой. Вопрос о бизнес-логике в ASP-страницах мы рассмотрим позднее.

<


       

    Теперь, попытаемся выявить основные критерии идеальной распределенной архитектуры:

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

Это означает, что: 1st tier не должен иметь прямого доступа к 3rd tier и наоборот; 2nd и 3rd tiers не должны иметь прямого интерфейса с пользователем; обращение к источнику данных из 1st tier происходит только через объекты 2-nd tier;

Вся сложная бизнес-логика находится в объектах 2nd tier.

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

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

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

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

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

    Проблема ASP заключается в том, что смесь скрипта бизнес-логики, HTML и SQL представляет собой 2-х уровневую архитектуру (клиент-сервер), которая с задачами Web-приложений не справляется.  Поэтому мы предложим способ как разделить ASP на три составляющие - ASP-код с бизнес-логикой, HTML и SQL.    


Response.Write() - проклятие для дизайнера


Мы можем  записать команду генерации динамического HTML в ASP двумя способами:

1) <%=sShopKey%>

2) Response.Write(sShopKey);

Работают они, с внутренней точки зрения, одинаково. Однако они записываются с несколько различным синтаксисом. Сравните два варианта записи:

1)  <a href="javascript: alert('Name: <%=obj.getName()%>');">

2) <% Response.Write("<a href=\"javascript: alert('Name: "+obj.getName()+"');\">"); %>

    Первый вариант еще выглядит как HTML и доступен для осмысления дизайнером-непрограммистом. Второй вариант, из-за обилия кавычек, конкатенаций и бэкслешей, обычно осмыслению не поддается никак. :( Более того, при достижении определенного уровня сложности, его не могут осмыслить даже авторы через неделю после написания. Поэтому - не стоит использовать Response.Write(...), если это не критично.



VBScript


К немногим преимуществам (весьма неоднозначным) VBScript можно отнести то, что он прост для использования VisualBasic-программистами. Если объекты 2nd tier пишутся как COM-объекты на VisualBasic, это может служить оправданием использования VBScript. Т.к. образуется некий "корпоративный стандарт". Язык Basic, сам по себе, является прекрасным языком для обучения программированию, на котором выросло не одно поколение программистов. Однако, непосредственно VBScript, не способствует появлению хорошего стиля программирования и потому стимулирует крах проектов средней и большой сложности.  Наверное худшим ограничением является отсутствие возможности объектно-ориентированного программирования, что очень критично для крупных проектов.  Если, все-таки, решено использовать VBScript, следует уделить тщательное внимание аккуратности при написании кода, комментариям, отступам, понятным названиям процедур, функции и переменных, хорошему документированию. Это важно при любом программировании, но для VBScript это актуально вдвойне. Не следует также пользоваться независимостью от регистра языка VBScript. 



Вынесение цельной HTML-страницы в отдельный файл


Учитывая недостатки вышеперечисленных способов, можно предложить следующее решение. Разделим ASP-файл на 2 файла. Первый будет содержать только ASP-код и никаких посылок результатов клиенту. В этом файле должно полностью отсутствовать 

HTML-форматирование. Файл будет включать в себя  (SSI) второй файл, который будет цельной HTML-страницей с минимальным количеством четко выделенного ASP-кода. Второй файл, по сути, будет тем же HTML-шаблоном, только вместо, например "[header]" будет написано "<%=obj.getHeader()%>", что не затруднит работу дизайнера. Но в нем также будет возможность выводить более сложные программные структуры. Идея в том, чтобы код во втором файле (где HTML) был действительно минимален и четко выделен. Т.е. все крупные куски кода инкапсулируются в функции и объекты, которые создаются в первом файле (чистый ASP-скрипт). Второй файл (HTML) лишь запускает эти функции и методы объектов. В этом плане JavaScript дает несомненные преимущества. Не пожалейте часа-двух, и изучите разные способы создания объектов в JavaScript - это позволит вам создавать удивительно красивые по своей простоте и логике архитектурные конструкции. 

Общая схема разделения ASP/HTML  
file1.asp file2.htm.asp
Business Logic Level:

Чтение/Обработка данных. Создание объектов (например объекта objExample), которые будет использовать Файл #2

(file2.htm.asp). 

<html>...

<h1><%=objExample.getHeader()%></h1>

<% while (!objExample.eof()) { %>

Text: <%=objExample.getText()%>

<% objExample.next();

}

%>

...</html>

Presentation Level:

<!--#include file="file2.htm.asp"-->

Finalization Level:

Закрытие DB-connections, etc.

    Отметим также, что в случае перехода на JSP, такой HTML-шаблон можно совсем не менять. 

    Сформулируем основные принципы вышеописанного подхода:

ASP-страница состоит из 2х файлов

1й файл содержит чистый серверный скрипт, и в нужном месте включает 2й файл, (например через SSI)




1й файл не посылает информацию клиенту

1й файл не содержит команд HTML

1й файл инкапсулирует весь код в функции и/или методы объектов, которые затем могут быть вызваны из 2-го файла

2й файл содержит полную HTML-страницу, от <html> до </html>

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

1й и 2й файлы не содержат команд Response.Write() (по причинам, описанным ниже)

1й файл имеет расширение .asp, т.к. это чистый ASP-файл

2-му файлу можно дать расширение .htm.asp, чтобы подчеркнуть, что это все-таки HTML по содержанию. Имя 2-го файла может быть то же что у 1-го, или с каким нибудь префиксом, для удобства поиска. Например: shopinfo.asp

и _t_shopinfo.htm.asp.

    К побочным эффектам подобного разделения можно отнести достаточно простую возможность поддержки генерации не-HTML страниц. Например - WAP или XML. Для этого надо только написать другой файл шаблона (файл #2). Файл с серверным скриптом (#1) останется тем же.

    Теперь остановимся еще на двух часто встречающихся деталях.