Внешняя структура корпоративного проекта глазами программиста Java EE

Корпоративный проект глазами кодера JAVA EEПомня о главной цели — реализации бизнес-сценариев, — спустимся с небес на землю и обратим внимание на реальные корпоративные проекты. В этой статье мы узнаем, какие методы помогут нам правильно описать бизнес в архи­тектуре приложения.



 

Структура бизнеса и группы разработчиков

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

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

Закон Конвея гласит: «Организации, проектирующие системы <... > вынужДе­ны создавать проекты, которые являются копиями коммуникационных структур этих организаций».

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

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

 

Содержимое программных проектов

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

 

Исходный код приложения

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

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

 

Программные структуры

Исходный код программного проекта упорядочен в виде определенных структур. В Java-проектах есть возможность объединять компоненты и задачи в Java-пакеты и модули проекта (рис. 1).

Структура проекта 

Рис. 1 Структура проекта

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

В этой статье мы обсудим преимущества проблемно-ориентиро­ванного проектирования, описанного в книге Эрика Эванса (Eric Evans), и узнаем, почему и как следует разбивать код на пакеты в соответствии с бизнес-логикой.

Пока просто запомним, что мы группируем согласованные компоненты, так что логи­ческие функции будут объединяться в логические пакеты или модули проекта.

Java SE 9 поставляется с возможностью установки модулей в формате Java 9. Эти модули похожи на JAR-файлы с возможностью декларации зависимостей и использования ограничений других модулей. Поскольку мой блог посвящен Java EE 8, а модули Java 9 еще не получили широкого распространения в реальных проектах, рассмотрим только пакеты Java и модули проекта.

Следующим элементом в структуре программных проектов является более мелкая единица программных компонентов — класс Java. Классы и их функции заключают в себе определенный функционал. В идеале они слабо связаны между собой и отличаются высокой целостностью.

Есть много литературы о практиках чистого кода и представлении функцио­нальности в исходном коде. Например, Книга "Чистый код" «Чистый код»1 (Clean Code) Роберта К. Мартина (Robert C. Martin) описаны такие методы, как правильное именование и рефакторинг, которые позволяют получить добротный исходный код в виде пакетов, классов и методов.

 

Системы контроля версий

Поскольку большинство программных проектов требуют координации при изме­нении кода, выполняемом одновременно несколькими программистами, исходный код хранится в системе контроля версий. Системы контроля версий (version control systems, VCS) зарекомендовали себя как обязательные средства, необходимые для надежной координации и отслеживания изменений в программных системах. Они же помогают понять, в чем заключаются эти изменения.

Существует множество вариантов систем контроля версий, таких как Git, Subversion, Mercurial и CVS. За последние годы распределенные системы кон­троля версий, в частности Git, были широко признаны самыми современными инструментами. Для хранения и разрешения конфликтов между отдельными версиями в них используется так называемое хеш-дерево, или дерево Меркля (Merkle tree), что обеспечивает эффективные различия и слияния.

Распределенная VCS позволяет разработчикам работать с репозиториями проекта в распределенной среде, не требуя постоянного подключения к сети. Каждая рабочая станция имеет собственный репозиторий, который включает в себя полную историю изменений и периодически синхронизируется с централь­ным репозиторием проекта.

На момент написания этой статьи в подавляющем большинстве программных проектов для контроля версий применяли систему Git.

 

Исполняемый код

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

В мире Java это означает, что исходный код Java компилируется в перено­симый байт-код и обычно упаковывается в виде архива веб-приложений (Web Application Archive, WAR, или Java Archive, JAR, соответственно). WAR- и JAR- файлы содержат все классы и файлы, структурные зависимости и библиотеки, необходимые для установки приложения. В итоге виртуальная машина Java (Java Virtual Machine, JVM) выполняет байт-код, а вместе с этим и бизнес-функции.

В корпоративных проектах артефакты развертывания, представляющие собой WAR- или JAR-файлы, либо развертываются в контейнере приложений, либо предоставляют сам контейнер. Контейнер приложений необходим, поскольку, помимо реализации собственно бизнес-логики, корпоративные приложения решают дополнительные задачи, такие как поддержка жизненного цикла при­ложения или обеспечение всевозможных форм коммуникации. Например, веб­приложение, которое реализует определенную логику, но не передает данные по HTTP, вряд ли будет полезным. В Java Enterprise за такую интеграцию отвечает контейнер приложения. Упакованное приложение содержит только бизнес-логику и развертывается на сервере, который заботится обо всем остальном.

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

Двоичные файлы создаются в процессе сборки программного обеспечения, что позволяет надежно воссоздать все двоичные файлы из исходного кода, храняще­гося в репозитории. Поэтому двоичные файлы не должны храниться в системе контроля версий. То же самое касается и сгенерированного исходного кода. Раньше, например, классы JAX-WS, необходимые для SOAP-коммуникации, обычно генерировались из файлов дескрипторов. Генерируемый исходный код — созданный в процессе сборки, он не должен храниться в системе контроля версий. Хранить в репозитории следует только чистый исходный код, но не получаемые из него рабочие продукты.

 

Системы сборки

Процесс сборки в первую очередь обеспечивает компиляцию исходного кода программного проекта Java в байт-код. Это происходит каждый раз, когда в про­ект вносят изменения. Все современные системы сборки поставляют с удобными начальными установками, чтобы свести к минимуму дополнительную настройку.

В корпоративном мире с его многочисленными структурами и библиотеками важными этапами являются организация и определение всех зависимостей от API и реализаций. Системы сборки, такие как Apache Maven и Gradle, упростили разработчикам жизнь, предоставив им эффективные механизмы разрешения за­висимостей. Система сборки записывает все зависимости (с соответствующими версиями), необходимые для компиляции и запуска приложения. Это упрощает настройку проекта для нескольких разработчиков, а также позволяет создавать повторяемые сборки.

Упаковка скомпилированных классов и их зависимостей в артефакты развер­тывания также является частью процесса сборки. В зависимости от используемой технологии артефакты упаковываются в WAR- или JAR-файлы. 

Далее, в разделах «Gradle» и «Apache Maven», мы подробно обсудим реализацию и различия этих двух основных систем сборки.

 

Одно- и многомодульные проекты

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

На первый взгляд, причины разделения проекта на модули вполне понятны. Объединение Java-кода и пакетов во взаимосвязанные модули позволяет создать более четкое представление для разработчиков, обеспечивает лучшую структуру и повышает согласованность.

Еще одна причина построения многомодульной структуры — это скорость сборки. Чем сложнее программный проект, тем дольше он будет компилироваться и упаковываться в артефакт. Разработчики, как правило, в каждый момент вре­мени затрагивают лишь отдельные части проекта. Поэтому идея состоит в том, чтобы каждый раз перестраивать не весь проект, а только модули, необходимые для внесения желаемых изменений. Именно это преимущество предоставляет система сборки Gradle, обещая сэкономить время за счет того, что перестраива­ются только те части, которые были изменены.

Еще одним аргументом в пользу этой практики является возможность по­вторного использования подмодулей в других проектах. Создавая подпроекты и выделяя их в самостоятельные артефакты, теоретически можно взять субарте­факт из одного проекта и включить в другой. Например, обычным делом является создание модуля модели, который содержит все сущности, относящиеся к области бизнеса, обычно в виде автономных простых объектов Java (plain old Java objects, POJO). Такая модель упаковывается в JAR-файл и повторно задействуется как зависимость в других корпоративных проектах.

Однако у этой практики есть ряд недостатков, или, скорее, она внушает опре­деленные иллюзии.

 

Иллюзии повторного использования

Напомню: программное обеспечение создает команда разработчиков, и структура проекта соответствует их коммуникационным структурам. Поэтому повторное при­менение модулей в рамках нескольких проектов требует некоторой координации.

 

Технические зависимости

Модуль проекта, предназначенный для повторного использования, должен соот­ветствовать определенным критериям. Прежде всего технология общих модулей должна соответствовать новому проекту. Это кажется очевидным, поскольку влияет на детали реализации. Особенно сильно связаны с подключенными моду­лями и зависят от конкретной технологии применяемые библиотеки и структуры. Например, классы моделей в Java EE обычно содержат аннотации от API, такие как JPA, которые должны быть доступны во всех зависимых модулях.

Еще важнее с технической точки зрения зависимости от продуктов опре­деленных версий сторонних производителей, необходимые для правильного функционирования совместно используемого модуля. Эти зависимости должны быть доступны при выполнении и не должны вызывать конфликтов с другими зависимостями или версиями. Коллизии с уже доступными на сервере зависимо­стями могут вызвать массу проблем. То же самое относится к деталям реализации, которые содержат неявные зависимости.

Типичным примером, иллюстрирующим сказанное, являются библиотеки ото­бражения JSON, такие как Jackson и Gson. Многие сторонние зависимости задей­ствуют определенные версии этих библиотек, которые могут вызывать конфликты с другими зависимостями или версиями при выполнении приложения. Другим примером являются реализации журналирования, такие как Logback и Log4j.

В целом совместно используемые модели должны быть как можно более само­достаточными или хотя бы содержать стабильные зависимости, которые не вы­зовут описанных проблем. Хорошим примером очень стабильной зависимости является Java EE API. Благодаря обратной совместимости с Enterprise Edition применение этого API и обеспечиваемая им функциональность не будут нару­шены при появлении новой версии.

Но даже если Java EE API является единственной зависимостью общих модулей, это привязывает модель к определенной версии и уменьшает свободу внесения изменений.

 

Организационные проблемы

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

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

 

Критерии повторного использования

Повторное использование — это всегда компромисс между простотой и возмож­ным дублированием. Выбор в пользу того или другого определяется уровнем самодостаточности. Как правило, затраты на координацию зависимостей, версий и технологий перевешивают преимущества, полученные за счет избегания из­быточности.

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

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

 

Артефакты проекта

Теперь немного отвлечемся и переключимся на артефакты развертывания нашего корпоративного приложения. Как правило, приложение создает один артефакт, который и будет запускать программное обеспечение. Даже многомодульный проект в итоге сводится к одному-двум артефактам. Таким образом, в большин­стве случаев вся эта структура снова сводится к одному JAR- или WAR-файлу. С учетом того, что повторно использовать модули не всегда возможно, возникает вопрос: так ли нужна многомодульность в проекте? В конце концов, создание и управление подпроектами, вертикальными или горизонтальными, потребует от программистов определенных усилий.

Действительно, разделение кода способно ускорить сборку, если перестраива­ются только те подпроекты, в которые вносились изменения. Однако в разделах «Apache Maven» и «Gradle» мы увидим, что сборка единого разумно построенного проекта в один артефакт выполняется довольно быстро и обычно есть другие причины, влияющие на скорость создания сборок.

 

Один проект — один артефакт

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

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

Есть и другие причины для появления многомодульных проектов. Системные тесты, проверяющие внешнее поведение развернутого корпоративного при­ложения, не всегда зависят от кода готового продукта. В некоторых ситуациях имеет смысл представить эти тесты в виде отдельных модулей многомодульного проекта.

Другим примером являются клиентские технологии, которые попросту сла­бо связаны с серверной частью приложения. При использовании современных клиенториентированных фреймворков JavaScript связь с серверной частью также ослабевает. Рабочий процесс и жизненный цикл разработки клиентской части могут отличаться от таковых для серверной части приложения. Поэтому имеет смысл разделить технологию на несколько подпроектов или даже на несколько программных проектов. Подробнее мы обсудим эту тему в разделе «Структури­рование для современных клиентских технологий».


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

 

Сборка систем в Java EE

Модули проекта описаны в виде модулей системы сборки. Независимо от того, сколько в системе проектов — один или несколько, например код продукта и системные тесты, — эти части строятся и выполняются как часть процесса сборки.

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

Сборка проекта должна быть надежной и воспроизводимой. Несколько сборок одного и того же исходного проекта с одинаковой конфигурацией должны давать одинаковые результаты. Это важно для реализации конвейеров непрерывной поставки (Continuous Delivery, CD), позволяющих создавать воспроизводимые сборки. При этом система сборки должна запускаться на сервере непрерывной интеграции (Continuous Integration, CI), таком как Jenkins или TeamCity. Для этого нужно, чтобы программное обеспечение управлялось через интерфейс командной строки, особенно в системах на основе Unix.

Система сборки, используемая инженерами-программистами, должна под­держивать те фреймворки и операционные системы, в которых они работают. Для систем сборки на основе JVM такая переносимость обычно предусмотрена. Иногда проекты имеют особые требования, такие как необходимость построения собственного кода для какого-то конкретного окружения. Однако в корпоратив­ных приложениях Java это обычно не требуется.

В целом процесс сборки должен выполняться как можно быстрее. Загрузка и настройка системы сборки не должны быть продолжительными. Чем больше времени требует сборка, тем дольше придется ждать инженерам обратной связи в процессе разработки.

На момент написания статьи самой распространенной системой сборки была известная большинству Java-программистов Apache Maven.

Maven — это система сборки на базе Java, настраиваемая посредством XML. Проекты в ней определяются в рамках так называемой объектной модели проекта (project object model, POM). Maven основана на принципе программирования по соглашениям, что сводит к минимуму дополнительную настройку. Стандартная конфигурация хорошо подходит для Java-приложений.

Еще один распространенный инструмент сборки — Gradle. Он включает в себя основанный на Groovy предметно-ориентированный язык (Domain-Specific Language, DSL), предназначенный для настройки полностью расширяемых сборок проектов с поддержкой сценариев. Поскольку Groovy является полноценным языком программирования, сценарии сборки Gradle, естественно, являются эффективными и гибкими.

И Gradle, и Maven включают в себя расширенное управление зависимостями и хорошо подходят для сборки Java-проектов. Конечно, есть и другие системы сборки, такие как SBT, но Gradle и Maven являются наиболее распространенными и будут рассмотрены в следующих разделах.

 

Apache Maven

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

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

На рис. 2 показан типичный пример структуры проекта Maven.

Структура проекта Marven 

Рис. 2. Структура проекта Maven

Эта структура знакома большинству разработчиков корпоративных Java-проектов. Здесь приведен пример веб-приложения, упакованного в виде WAR- файла.

Одним из недостатков Apache Maven является несколько непрозрачный спо­соб определения нужных расширений сборки и их зависимостей. Применение стандартного соглашения без явного указания версий для расширений, таких как Maven Compiler Plugin, может вызвать нежелательные изменения используемых версий. Это нарушает принцип повторяемости сборки. Из-за этого в проектах, требующих воспроизводимости, часто явно указывают и переопределяют версии зависимостей расширений в POM. В этом случае проекты каждый раз собираются с использованием одних и тех же версий, даже если меняются версии расширений по умолчанию.

Еще один распространенный способ определения точных версий расшире­ний — применение Super POM. Объектные модели проекта могут наследовать­ся от родительских проектов и тем самым сокращать шаблонные определения расширений. Программисты могут задействовать эффективные представления POM, отражающие результирующую объектную модель, с учетом стандартной конфигурации и возможного наследования.

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


В следующем фрагменте кода показаны минимальные требования POM для проекта Java EE 8:

<project xmlns="http://maven.apache.org/POM/4.0.0"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
      http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>

   <groupId>com.example.cars</groupId>
   <artifactId>car-manufacture</artifactId>
   <version>1.0.1</version>
   <packaging>war</packaging>
   <dependencies>
      <dependency>
         <groupId>javax</groupId>
         <artifactId>javaee-api</artifactId>
         <version>8.0</version>
         <scope>provided</scope>
      </dependency>
   </dependencies>

   <build>
      <finalName>car-manufacture</finalName>
   </build>

   <properties>
      <maven.compiler.source>1.8</maven.compiler.source>
      <maven.compiler.target>1.8</maven.compiler.target>
      <failOnMissingWebXml>false</failOnMissingWebXml>
   <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
   </properties>

</project>

Приложение для производства автомобилей car manufacture собрано в WAR- артефакт. FinalName переопределяет неявное имя WAR-файла, и получается файл car-manufacturing.war.

API Java EE 8 — единственная зависимость, необходимая в готовом коде простого корпоративного решения.

Тег properties устраняет необходимость явной настройки расширений сборки. Он используется для настройки параметров расширений Maven в каждой кон­фигурации. Указание этих параметров позволяет перенастроить расширение без явного объявления полных определений.

 

Указанные свойства позволяют построить проект с применением Java SE 8, причем считается, что все исходные файлы созданы в кодировке UTF-8. WAR-файлу не требуется передавать дескриптор развертывания web.xml, именно поэтому мы даем Maven указание не прерывать сборку при отсутствии дескрип­тора. Прежде для Servlet API требовались дескрипторы развертывания, чтобы настроить и сопоставить сервлеты приложения. После появления Servlet API версии 3 дескрипторы web.xml больше не нужны — сервлеты настраиваются с помощью аннотаций.

Процесс сборки в Maven состоит из нескольких этапов: компиляции, тести­рования и упаковки. То, какие операции выполняются, зависит от выбранного этапа. Например, если выбрать этап упаковки, то будут скомпилированы ис­ходные коды main и test, проведены контрольные тесты, после чего все классы и ресурсы будут упакованы в артефакт.

Команды сборки Maven запускаются через IDE или в виде командной строки mvn. Например, команда mvn package запускает этап упаковки, и на выходе созда­ется упакованный артефакт. Подробнее об этапах и функциях системы Apache Maven вы можете узнать из ее официальной документации.

 

Gradle

На момент написания этой статьи система Gradle применялась в корпоративных Java-проектах реже, чем Apache Maven. Возможно, это связано с тем, что разработчики корпоративных приложений зачастую незнакомы с динамическими JVM-языками, такими как Groovy, который используется в Gradle в качестве языка сценариев сборки. Однако для того, чтобы создавать файлы сборки Gradle, глубокое знание Groovy не требуется.

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

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

Однако такая оптимизация может и не потребоваться. Это определяется слож­ностью проекта и используемыми зависимостями.

На рис. 3 показана структура сборки проекта в Gradle. Как видите, есть боль­шое сходство с проектами Maven, с той разницей, что готовые двоичные файлы после сборки по умолчанию помещаются в каталог build.


Обычно проекты Gradle включают в себя сценарий оболочки для тех опера­ционных сред, в которых не установлена система Gradle.

Структура проекта Gradle

Рис. 3. Структура проекта Gradle

Следующий код представляет собой пример файла build.script

plugins {
   id 'war'
}
repositories {
   mavenCentral()
}
dependencies {
   providedCompile 'javax:javaee-api:8.0'
}

Задачи сборки в Gradle запускаются из командной строки с использованием команды gradle или посредством предоставляемых сценариев оболочки. Напри­мер, команда gradle build является аналогом mvn package и запускает компиляцию исходного кода, выполнение тестов и сборку артефакта.

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

 

Однако эти возможности порождают опасность чрезмерного технического усложнения сборки. Как уже отмечалось, негибкость Apache Maven иногда может быть преимуществом: возможность легко настраивать сценарии сборки позволяет создавать определения, присущие только данному проекту. Здесь, в отличие от Maven, перегруженные специфическими деталями сборки могут стать препят­ствием для программистов, незнакомых с проектом.

Опыт показывает, что подавляющее большинство сборок корпоративных проектов очень похожи. Поэтому возникает вопрос: так ли уж нужна гибкость Gradle? Для проектов, не предъявляющих особых требований к сборке, в от­личие от, например, разработки продукта, вполне достаточно такой системы сборки, как Maven.

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

 

Структурирование для современных клиентских технологий

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

Это делается довольно просто. Клиентская часть веб-приложений в большин­стве случаев представляет собой создаваемые на сервере HTML-страницы со сценариями на JSP или JSF. HTML-страницы генерируются по мере необходи­мости, то есть по запросу клиента, и предоставляются ему. Для этого на сервере и располагаются^?- илиJSF-страницы. Все корпоративные приложения должны поставляться и развертываться как единый артефакт.

 

Введение в JavaScript-фреймворки

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

Таким образом, чем более клиентоориентированными и функциональными становятся фреймворки JavaScript, тем дальше взаимодействие между клиентской и серверной частью приложения уходит от тесно связанных запросов и ответов и приближается к стилю API, обычно в формате JSON через HTTP. Это также означает, что серверная сторона все меньше зависит от клиентской. Например, при передаче данных только через RESTful-подобные API в формате JSON соб­ственные и мобильные клиенты, такие как смартфоны, могут применять тот же API, что и у клиентской части.

Мы наблюдаем эту тенденцию во многих корпоративных проектах. Тем не ме­нее еще неизвестно, что лучше, переносить все больше логики на сторону клиента или создавать гибридные решения, часть которых реализована на стороне сер­вера, а часть — на стороне клиента. Не вдаваясь в детали, рассмотрим несколько ключевых моментов.

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

Сложные клиентские технологии часто включают в себя логику навигации, которая использует так называемые шебанг-страницы. Примером URL ше- банг-страницы является /car-manufacturing/#!/Cars/1234. Такие страницы, например car 1234, не размещаются на сервере, а генерируются на стороне клиента и существуют только там. URL-адрес такой подстраницы определяется после символа решетки и не учитывается при запросе ресурсов через HTTP. Это означает, что клиент запрашивает начальную входную страницу, которая затем реализует всю навигационную логику, включая генерацию подстраниц. Таким образом, с одной стороны, значительно уменьшается количество за­просов, но с другой — сервер не обеспечивает подготовку и предварительную визуализацию содержимого — все происходит на стороне клиента. Именно по этой причине некоторые крупные компании, такие как Twitter, поначалу поддерживавшие такой подход, впоследствии отказались от него. В частности, просмотр этих страниц на мобильных устройствах сопряжен с определенными трудностями. Из-за ожидаемо медленных мобильных соединений и меньшей вычислительной мощности визуализация и выполнение сложной клиентской логики на этих устройствах занимают больше времени, чем отображение пред­варительно визуализированных HTML-страниц.

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

 

Структура современных клиентских приложений

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

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

Если клиентская часть может быть развернута независимо от серверной части, за исключением использования HTTP, то структура проекта довольно проста. Проект можно собрать и развертывать на веб-сервере отдельно и задействовать одну или несколько серверных частей со стороны клиента. Такой проект состоит только из статических ресурсов, например файлов HTML, JavaScript или CSS, которые передаются клиенту и выполняются на его стороне. Здесь нет жестких технических зависимостей от применяемых серверных технологий, за исключе­нием HTTP API.

Этот момент, очевидно, должен быть тщательно оговорен во время разработки, а также описан в документации серверной части. Как правило, серверная часть определяет HTTP-ресурсы, предоставляющие требуемое содержимое в формате JSON, которое при необходимости может быть отфильтровано в соответствии с параметрами запроса. Причиной столь широкого распространения формата JSON является то, что клиентский код JavaScript может использовать ответ сервера непосредственно в виде объектов JavaScript, без дополнительных пре­образований.

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

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

Для того чтобы это можно было реализовать, в Servlet API предусмотре­ны статические ресурсы, не только упакованные в архив, но и содержащиеся в JAR-файлах. Ресурсы, находящиеся в разделе META-INF/resources JAR-файла, который хранится в WAR-файле, также поставляются в контейнере Servlet. Проект клиентской части содержит все необходимые интерфейсные технологии, фреймворки и инструменты и собирается в отдельном JAR-файле. Это позволяет программистам отделять клиентскую часть от серверной, чтобы адаптироваться к их разным жизненным циклам.

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

Вас заинтересует / Intresting for you:

Значение Java EE для построени...
Значение Java EE для построени... 734 просмотров Светлана Комарова Fri, 14 Dec 2018, 06:32:09
Моделирование бизнес-процессов...
Моделирование бизнес-процессов... 947 просмотров Денис Sat, 30 Mar 2019, 05:06:44
Клиент-серверная архитектура
Клиент-серверная архитектура 2506 просмотров Fortan Thu, 21 Feb 2019, 16:38:33
Выбор технологий Data Mining д...
Выбор технологий Data Mining д... 2742 просмотров Administrator SU Thu, 26 Apr 2018, 17:16:12
Войдите чтобы комментировать

ildergun аватар
ildergun ответил в теме #9334 25 янв 2019 11:11
Просто супер статья! Многие краеугольные камни реализации крупного корпоративного проекта очерчены с потрясающей точностью! Об этом нужно помнить, и это нужно знать до того, как "вляпываться" в серьезные проект со всеми вытекающими...