|
Разработка сложных Web-приложений на примере Microsoft Active Server Pages
Проблема ASP заключается в том, что смесь скрипта бизнес-логики, HTML и SQL представляет собой 2-х уровневую архитектуру (клиент-сервер), которая с задачами Web-приложений не справляется. Поэтому мы предложим способ как разделить ASP на три составляющие - ASP-код с бизнес-логикой, HTML и SQL. 1.3 ASP-программирование: сравнение VBScript, JScript, PerlScriptASP-страницы, почему-то, всегда связывают со скриптом VBScript. Хотя это, наверное, худший из возможных вариантов. ASP могут быть написаны на любом WSH (Windows Scription Host)-cовместимом скриптовом языке. Рассмотрим 3 варианта - VBScript, JScript, PerlScript. Первые два - VBScript & JScript поддерживаются Miscrosoft, и не требуют дополнительных инсталляций. PerlScript автоматически устанавливается при инсталляции ActivePerl. Сравним эти три варианта. VBScriptК немногим преимуществам (весьма неоднозначным) VBScript можно отнести то, что он прост для использования VisualBasic-программистами. Если объекты 2nd tier пишутся как COM-объекты на VisualBasic, это может служить оправданием использования VBScript. Т.к. образуется некий "корпоративный стандарт". Язык Basic, сам по себе, является прекрасным языком для обучения программированию, на котором выросло не одно поколение программистов. Однако, непосредственно VBScript, не способствует появлению хорошего стиля программирования и потому стимулирует крах проектов средней и большой сложности. Наверное худшим ограничением является отсутствие возможности объектно-ориентированного программирования, что очень критично для крупных проектов. Если, все-таки, решено использовать VBScript, следует уделить тщательное внимание аккуратности при написании кода, комментариям, отступам, понятным названиям процедур, функции и переменных, хорошему документированию. Это важно при любом программировании, но для VBScript это актуально вдвойне. Не следует также пользоваться независимостью от регистра языка VBScript. JScript (JavaScript)Одним из новаторских изобретений фирмы Netscape
стал скриптовый язык JavaScript. Его синтаксис официально основывается
на чрезвычайно популярном сейчас языке Java. А это, в свою очередь,
говорит о схожести с C и C++. Особый интерес в JavaScript представляет
оригинальная система динамического создания объектов, что позволят применять
объектно-ориентированный подход. Несмотря на отсутствие инкапсуляции,
странное наследование и неограниченный полиморфизм (проверки типов в
JavaScript нет), применение объектов выводит программирование ASP-страниц
на более высокий уровень, позволяя использовать стандартные паттерны
проектирования, упрощая код и делает его более логичным, расширяемым
и переносимым. Можно, например, создавать оболочки (которые еще
называют адаптерами или врапперами) COM-объектов (того же ADODB), более
удобные для применения, и абстрагирующие основной ASP-код от этого-самого
COM-объекта (позволяя, при необходимости, легко заменить его на другой
объект 2nd tier). 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, но вполне осуществимым. Общие сравнительные характеристики:
Критериями выбора скрипта разработки для ASP могут стать:
В свете вышеописанных критериев, можно порекомендовать выбор в пользу JScript.
|
часть HTML-шаблона: | результат: |
<h1>[header]</h1> | <h1>Shop Information</h1> |
ASP-страница читает и обрабатывает нужный шаблон, а затем посылает
результат клиенту одной командой Response.Write().
Этот подход лучше предыдущего, поскольку позволяет
полностью разделить ASP и HTML, причем HTML-файлы остаются удобными
для редактирования дизайнером. Однако неспособность выводить таблицы
с неизвестным заранее числом строк делает этот подход малоприменимым
(ASP+, судя по имеющимся сведениям, имеет встроенные решения этой проблемы).
Учитывая недостатки вышеперечисленных способов, можно предложить следующее решение. Разделим 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-шаблон можно совсем не менять.
Сформулируем основные принципы вышеописанного подхода:
К побочным эффектам подобного разделения можно отнести достаточно простую возможность поддержки генерации не-HTML страниц. Например - WAP или XML. Для этого надо только написать другой файл шаблона (файл #2). Файл с серверным скриптом (#1) останется тем же.
Теперь остановимся еще на двух часто встречающихся деталях.
Мы можем записать команду генерации динамического 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(...), если это не критично.
Очень хочется развенчать совершенно кошмарный пример, который обычно приводят в туториалах по ASP. Это - объединение HTML-формы и обрабатывающего ее кода в одной ASP-странице. Поначалу это смотрится круто, и кажется что снижение количества файлов проекта дает прекрасный упрощающий эффект. Поверьте - карма человека, придумавшего этот пример из туториала, запятнана навсегда. А ведь все могло быть так просто - форма в обычном HTML-файле, а обработчик - в 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, который будет
работать везде, скорее всего миф. В любом случае это очень сложно реализовать.
Посмотрим, что можно предложить.
Объекты 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-шаблонах.
Одним из главных достоинств 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) быстро и унифицированно.
Рассмотрим один из вариантов организации структуры 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 выражения по некоторому условию. Все это можно решить. Мы привели лишь простой пример.
Работу с 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-файл: 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 |
Допустимо считать, что три простых файла, с явно выделенной функциональностью, поддерживать значительно проще, чем один сложный. Однако, конечно же, все зависит от конкретного проекта и его задач.
Не привязываемся к Microsoft, не привязываемся
к языку скриптов, не привязываемся к
стандартным ASP-объектам, не привязываемся к MTS... (о стремлении к
абсолютной свободе)
Смысл этой статьи сводится к тому что проекту, при
желании, можно придать довольно большую степень свободы. Даже без особых
дополнительных затрат. Свободу от аппаратной/программной платформы,
web-технологии, источника данных, инфраструктуры 2nd tier, от проблем
с web-мастерами не разбирающимися в программировании и с бездарными
в web-дизайне программистами (к коим относит себя автор статьи). Свободу
от прихотей Microsoft, Sun, Oracle, Borland, Allaire, BEA и т.д. , которые
привнесли в нашу жизнь все эти занимательные технологии и часто
приятно, и не очень, удивляют нас своими выходками.
В 80-х годах SQL дал программистам относительную
свободу от конкретной реализации сервера реляционной БД. В 90-х появился,
например, J2EE, претендующий на предоставление свободы от конкретной
реализации сервера приложений. Но почему бы не быть свободыми и от SQL,
и от J2EE, даже строя проект на этих технологиях? :)
Август - Октябрь 2001
helloworld.ru © 2001-2021 Все права защищены |
|
|