Friday, August 22, 2008

Из архивов: Слоеные пироги приложений

Вот, вроде бы и не считаешь себя таким уж старым пнем, а выясняется, что уже наплодил архивов, о которых успел позабыть. :)
Мой основной компьютер неожиданно дал дуба, похоже, в районе блока питания, и пришлось пересесть на запасной, то бишь мой же, но предыдущий компьютер, ныне используемый женой. И там обнаружилась собрание заготовок к книге по веб-разработке на Java, которую я собирался писать еще в 2003 или 2004 году. Впрочем, когда-то я делал перевод User Guide по Struts 1.0 и даже владел доменом и сайтом struts.ru :)
Но, почитав эти заготовки, я усомнился в том, писал ли это я сам или где-то взял, как-то не очень знакомо выглядит, кое с чем я не согласен :). Короче, поискав Яндексом, вроде бы плагиата не обнаружил, значит, писал сам ("...водки у нас много - значит, поехали на задание" (С)). Копи-паст - и представляю на суд общественности.

Слои в приложениях
О вреде классики
Очень часто приложения пишутся таким образом, что в едином модуле (здесь “модуль” понимается в смысле исходного кода) смешаны различные функции – содержится код SQL-запросов, элементы интерфейса пользователя, элементы бизнес-логики. Например, такой подход часто применяют программисты на Delphi, когда на одной форме находятся и компоненты, отвечающие за работу с базой данных, и визуальные компоненты, а бизнес-логика находится прямо в обработчиках событий кнопок. Некоторые очень метко, но достаточно зло называют такие приложения “винегретами”. На рисунке 1 изображена условная схема модуля подобного приложения.

Рисунок 1 Приложения, смешивающие в одном модуле различные функции

Такой подход удобен для разработки небольших приложений, написанных одномоментно, которые не предполагается развивать. Если необходимо решить простую задачу, чтобы быстро заткнуть “дыру”, то приложение создается именно так – смешением всех ингридиентов до получения приемлемого результата.
Несмотря на кажущуюся быстроту реализации простых задач, такой подход, который иногда не слишком уважительно называют “метанием компонентов”, не может являться надежной основой для разработки крупного и даже среднего проекта. То есть, невозможно представить сложную задачу в виде совокупности таких простых задач, которые можно было бы быстро решить.
Вообще говоря, разработать приложение, в котором будет, например, 200 или более подобных форм, содержащих смесь логики, SQL-запросов, обработки событий и визуальных интерфейсов, вполне реально. Но при этом такое приложение превратится в настоящий кошмар для программиста, в результате чего мы получим следующие проблемы:

– Нет расширяемости. Приложение станет очень трудно расширять. Чтобы встроить какую-то новую функциональность, которая хотя бы краешком затронет функции всех 200 форм, придется поправить все эти 200 форм.

– Много рутины. Расширение приложения будет на 80% состоять из рутинной работы. В “смешанном” приложении практически невозможно выделить какую-то часть, пригодную для повторного использования, поэтому каждый раз при создании нового типа документов, нового бизнес-процесса, придется в сотый раз реализовывать до боли знакомую задачу – и в сотый раз наступать на те же грабли, что и ранее – так как человек не в состоянии помнить все мелочи и детали, которые возникают при разработке. При этом разработчики будут неизбежно терять интерес к разработке новых функций и относится к ним как к неприятной обязанности, ведущей к неизбежному “удару граблями”.

– Очень трудно разобраться. В этом приложении сможет разобраться только его автор. При отсутствии какой-либо строгой и стандартной системы построения архитектуры приложения программист-автор системы изобретает какую-то свою, обычно неявную и глубоко интуитивную систему. Компания, допустившая разработку приложений-”винегретов”, становится сначала зависимой от программиста-разработчика, и когда тот меняет работу, оказывается перед выбором: писать систему заново или терпеть ошибки, которые не могут устранить оставшиеся/новые разработчики.

– Выход из под контроля и “заплаточное” программирование. Если приложение живет достаточно давно и сменило несколько хозяев-программистов, которые постепенно доделывали его, то приложение может полностью выйти из-под контроля – то есть, ни сам программист, ни тем более пользователь не сможет внятно объяснить поведение программы или причину ошибки. Исправления ошибок и добавлении функциональности делаются путем наложения “заплаток” на расползающийся “тришкин кафтан” приложения, которые еще больше запутывают программу. Примерно так и рождаются легенды о искусственном разуме :).

– Непереносимость. Приложение почти невозможно перенести на другую СУБД, операционную систему или язык. Перенос будет означать полную переделку приложения, так как придётся поправить все взаимосвязанные куски кода – фактически, все приложения.

– Затруднённость командной разработки. Если над приложением работает команда, то она будет либо подчинена одному ведущему разработчику, который действительно разбирается в поведении приложения, а остальные при этом будут у него “на побегушках”, либо будет раздроблена на отдельные суверенные личности, которые ревниво охраняют свои куски кода и отказываются лезть в чужие. Ясно, что и то, и другое не прибавят команде разработчиков эффективности в работе и тем более не будут способствовать творчеству..

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

Начинаем печь пирог
Итак, как вы поняли, основная идея состоит в том, чтобы разделить части кода приложения, отвечающие за различные функции. Существуют несколько подходов к разделению кода приложения по различным признакам, но мы начнем с самого общего подхода, который описывается шаблоном или паттерном (pattern) проектирования Layers.
Идея шаблона проектирования Layers формулируется следующим образом: необходимо разделить приложение на слои согласно выполняемым им функциям, и поместить эти слои один над другим, так, чтобы функции, выполняемые внутри слоев, могли взаимодействовать только с функциями того же слоя и нижнего слоя. При этом клиент взаимодействует только с самым верхним уровнем:


Рисунок 2 Общая схема шаблона Layers

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



Рисунок 3 Обобщенное представление приложения согласно паттерну Layers

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


Что такое “слой”?


Вопрос очень непростой, ответ на который зависит от того, какой методологии мы будем следовать при разработке приложения – будем ли использовать только объектно-ориентированный подход или, как это часто бывает, остановимся на компромиссе между ООП и процедурно-ориентированным подходом. Помимо этого, существуют также шаблоны для проектирования тех или иных слоев. Также понятие слоя может быть связано с применением пакетов Java или namespaces С++ и т.д.
Чтобы не вдаваться в ненужные в данный момент подробности, дадим такое определение:

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

Рассмотрим подробнее это понятие. Как следует из определения, по поводу структуры слоя – обычно он делится на 2 части: “интерфейс” (не в смысле пользовательского интерфейса) и “реализацию”. Интерфейс меняется лишь при изменении функций самого слоя и не меняется при изменении функций нижележащих слоев, а вот реализация меняется и при изменениях данного слоя, и при изменениях нижележащего слоя. Такая конструкция защищает верхние слои от каскадных изменений в реализации нижних слоев.
Теперь давайте более подробно рассмотрим каждый слой на нашей схеме и приведем примеры его использования, а также укажем, какую именно конкретную реализацию мы будем рассматривать в данной книге. Начнем с самого “нижнего” слоя, который, тем не менее, не является самым простым или менее всего важным.

Ресурсный слой

Под ресурсным слоем понимается то, что обеспечивает наше приложение данными. Это может быть СУБД, (LDAP) Lightweight Directory Access Protocol , внешний по отношению к данному приложению application-сервер, или просто обычные текстовые файлы или файлы XML. Именно поэтому мы использовали название “ресурсный слой”, а не “СУБД” или “хранилище данных”.
Конечно, большинство читателей будет применять именно ту или иную базу данных в качестве ресурсного слоя, однако с точки зрения остальных слоев не должно быть никакой разницы, с чем конкретно мы работаем.
Здесь большинство читателей может воскликнуть: “Это невозможно!”.
Итак, ресурсный слой представляет нам совершенно конкретный API (например, JDBC или ODBC), который будет использоваться для работы с данными. При изменении методики работы с данными нам придется (очевидно) изменять этот слой, а также его непосредственного “соседа сверху” - интегрирующий слой.
В качестве ресурсного слоя в большинстве случае используются реляционные СУБД.
Но мир хранения данных отнюдь не заканчивается на строго структурированных данных, которые обычно хранятся в реляционных базах данных. Те, кто знаком с разработкой web-сайтов, хорошо знают, что одной из самых распространенных задач “сайтостроения” является обеспечение полнотекстового поиска по материалам, хранящимся на сайте в самом разнообразном виде. Для решения этой задачи может использоваться внешняя система, вроде Lucene (это почти идеальное решение для кроссплатформенных приложений, которым необходим полнотекстовый поиск).
Совместное использование СУБД и полнотекстового механизма поиска, которое мы продемонстрируем, будет являться хорошим подтверждением тезиса о пользе разделения приложения на изолированные уровни.

Интегрирующий слой
Этот слой в случае с базами данных также называют persistent layer. Наиболее известное описание того, что такое persistent layer дано в работе S.Ambler [6], которая послужила основой для многих реализаций подобных слоев.
Основной задачей интегрирующего слоя в структуре приложений является то, что он должен полностью скрывать от слоя бизнес-логики реализацию ресурсного слоя. Он предоставляет некий API (application programming interface), который в идеале, постоянен для всех ресурсных слоев.
Фактически, этот API реализует функции сохранения тех объектов/документов, которые есть в слое бизнес-логики, функции извлечения одного или группы документов согласно условиям. Обычно этот API предоставляет собственный язык запросов, обертывающий, например, SQL.
В мире Java наиболее известной реализацией persistent layer является Hibernate.

Слой бизнес-логики
Под бизнес-логикой понимается масса различных вещей. Это понятие действительно охватывает очень широкий спектр понятий, и попытки дать ему точное определение приводят к появлению пары абзацев текста, содержащих массу весьма расплывчатых предложений. Чтобы не впадать в подобную ловушку, дадим максимально простое определение:
Бизнес-логика – это понятия, которыми мыслит пользователь нашей системы, плюс действия с этими понятиями.
Например, пользователь бухгалтерской системы мыслит проводками, счетами, накладными и т.д., а действия – провести, создать, списать и т.д. Пользователь складского приложения мыслит учетными карточками, остатками и так далее.
Вообще говоря, это сама по себе весьма непростая задача – выяснить, что на самом деле понимает пользователь и как он мыслит, когда выполняет то или иное действие. В идеале, этим должны заниматься аналитики, которые на основе интервьюирования пользователей должны формализовать изучаемую логику и представить ее в очевидном недвусмысленном виде – с помощью Use Cases в UML или диаграмм IDEF и так далее.
Однако часто аналитик в штате проекта отсутствует (или является некомпетентным, что еще хуже), и программист-разработчик, помимо кодирования, вынужден также заниматься и анализом предметной области.
Как бы то ни было, предметная область в конечном итоге отобразиться в конкретные программные структуры, которые мы и будем называть объектами бизнес-логики.
Слой бизнес-логики должен реализовывать в виде программных объектов понятия (сущности) бизнес-логики и действия между ними. Очевидно, что виды и способы представления бизнес-объектов могут быть самыми различными – некоторые разработчики высказывают подозрение, что их число может превысить число самих объектов предметной области.
В то же время, несмотря на огромное разнообразие предметных областей и объектов в них, есть инструмент, достаточно гибкий, чтобы выразить практически все понятия предметной области и действия между ними.

XML
“— О”, может воскликнуть читатель, “XML тут, XML там! И этот (автор) туда же!”.
Ну а что же остается делать, если XML позволяет гибко выразить понятия предметной области? Отказаться от его использования в знак протеста против рекламной шумихи вокруг XML? Вряд ли это будет мудрым решением.
А пока вернемся к слою бизнес-логики и завершим его краткое описание парой конкретных примеров.
Итак, если мы например, пишем складское приложение, то пользователь, несомненно, потребует возможности получать сведения об остатках товаров на складе.
Поэтому очевидно, что в слое бизнес-логики должен быть метод вида getRemainsByProduct(), возвращающий остатки товара на складе.
В данном случае это пример бизнес-метода, который неявно предполагает наличие бизнес-объектов Product (товар), Stock. (склад) и т.д.
Ну и пример бизнес-процесса – выписка приходной накладной. Если на склад пришел некий товар, то это необходимо учесть. Поэтому нам будет нужен бизнес-процесс “Выписка приходной накладной”, в котором будут вызваны соответствующие бизнес-методы по получению наименования товара, его количества, ввода поставщика и т.д.
Важное замечание: Употребление слов “бизнес-объект”, “бизнес-метод”, “бизнес-процесс” и т.д. никоим образом не указывает на то, что мы будем подразумеваем наличие в слое бизнес-логики классов (в смысле языка программирования) вида Product, у которого есть методы getRemainsByProduct(), и что вызов бизнес-метода/процесса будет выглядеть как myProduct.getRemainsByProduct(). Реализация слоя бизнес-логики далеко не сводится к прямому отображению понятий и сущностей предметной области на классы (иначе было бы достаточно только интегрирующего слоя).

Слой управления
Слой управления редко выделяется в классических монолитных приложениях, и обычно перемешан с пользовательским интерфейсом и бизнес-логикой. Примеры элемента управляющего слоя – это меню программы, выбор пользователем вида создаваемого объекта в процессе исполнения какого-либо мастера (wizard), перенаправление вывода на экран с ошибкой и т.д.
Обычно подобный управляющий код скрыт в массе операторов ветвления или погребен в каскадных вызовах обработчиков исключений, которые разбросаны по коду смежных слоев.
Почему необходимо выделять слой управления? По той же причине, что и надо отделять остальные слои друг от друга – для изоляции бизнес-логики от пользовательского интерфейса. Бизнес-метод выписки накладной должен работать без исправления в нем единой строчки кода как с использованием пользовательского интерфейса на базе JSP, так и с использованием Swing или даже без использования native Java – например, на Delphi.
Как же достичь такой гибкости? В данном случае следует полагаться на две мощные разработки, одна из которых обеспечит нам поддержку решения с web-интерфейсом, который будет основным вариантом пользовательского интерфейса; а вторая поможет нам расширить рамки нашего приложения с помощью протокола SOAP (Simple Object Access Protocol).

Наш выбор – Struts и Axis
Jakarta Struts – один из самых популярных проектов организации Apache Software Foundation (ASF). Цель этого проекта – разработка Open Source среды для разработки web-приложений с помощью технологий Java Servlet и Java Server Pages (JSP). Разработка приложений с использованием Struts основана на шаблоне проектирования Model-View-Controller, в ее модификации, известной как Model 2.
Axis также является проектом ASF, и представляет собой реализацию протокола SOAP. Использование Axis позволит нам воспользоваться возможностями WEB-служб для реализации частей пользовательского интерфейса или даже бизнес-логики не на Java, а на других языках программирования. .

Интерфейсный слой
Пользовательский интерфейс (user interface – UI) является важнейшей частью приложения. Очень часто великолепная по функциональности программа не нравилась заказчику по причине непонятного и неудобного интерфейса. В разработке UI очень сложно найти золотую середину. С одной стороны, пользователей пугает обилие многочисленных кнопочек и окошечек, а с другой стороны, он желает (особенно после накопления некоторого опыта работы с программой), чтобы “все было как на ладони”, и чтобы “никуда не надо было лезть”. Продумывать нужно – от цветового оформления до количества пунктов в меню.
К сожалению, тщательное продумывание тоже не спасает от изменений в требованиях пользователя. Ведь на каждое изменение бизнес-логики обязательно происходит и изменение UI – ведь пользователь должен как-то вызывать бизнес-функцию и получать результаты ее работы.
Как уберечься от массы неинтересной работы по разработке, и главное, по поддержке пользовательского интерфейса? Ну, самый простой ответ – это перепоручить ее другим. Дизайнерам, веб-мастерам и другим людям, которые обожают заниматься пользовательским интерфейсом. Вопрос в том, что необходимо предоставить возможность этим коллегам работать параллельно, с минимальными пересечениями между их работой и разработкой параллельных слоев – управления и бизнес-логики.
Но разделение труда – это лишь часть возможных решений в области UI. Дело в том, что большое количество элементов UI похожи друг на друга как две капли воды. Это дает нам ключ к следующему решению – разработке параметризованного пользовательского интерфейса.
Идея параметризованного UI очень проста – по определениям из нижних слоев (например, по XMLSchema из слоя бизнес-логики) генерировать модули пользовательского интерфейса, которые затем использовать в качестве элементарных кирпичиков для формирования нужного UI.

При разработке слоя UI нам опять придет на помощь Struts – с его мощными библиотеками тэгов, возможностями автоматической проверки вводимых даных и обработки ошибок. А также мы будем использовать XSLT для получения повторно используемых модулей пользовательского интерфейса.
Post a Comment