Существует множество способов создания программного обеспечения. На самом деле, существует даже множество способов создания хорошего программного обеспечения. Когда дело доходит до разработки серверов приложений, один из них выдержал испытание временем, и на то есть веская причина: Java Enterprise Edition.
Java EE – это гораздо больше, чем просто библиотека программного обеспечения. Это также архитектура и философия. JEE не для слабонервных; если вы создаете одноразовый прототип, вы пошли не тем путем. Но если вы пришли почитать об архитектуре, которая может обслуживать крупные предприятия и поддерживать масштабные приложения, то вы на правильном пути. JEE – это тяжелая артиллерия разработки программного обеспечения. И это чертовски круто.
В этой статье мы рассмотрим архитектурную сторону JEE. Скоро последует статья о внедрении.
Небольшое примечание. Когда я говорю о Java Enterprise в этой статье, я имею в виду не конкретно библиотеку, а архитектуру и философию. Официальные библиотеки Java EE – это всего лишь один из способов реализации этого стека.
Итак, вы готовы к серьезной разработке программного обеспечения? Отбросьте любые нетипизированные языки, причудливые сценарии, разработку, основанную на шумихе, и хипстерские технологии, давайте станем серьезными и напишем программное обеспечение, которое будет работать в течение следующих 20 с лишним лет. Вот архитектура Java EE в двух словах:
Ладно, согласен, тебе нужна большая ореховая скорлупа. Давайте начнем с некоторых первоначальных наблюдений:
- Каждое общение с внешним миром строго основано на запросе-ответе .
- Входящий запрос проходит через несколько слоев , прежде чем он достигнет вашего кода приложения. Каждый слой может отказаться , перенаправить или изменить просьба. Мотивацией, лежащей в основе этих слоев, является разделение проблем .
- Многие из слоев уже реализованы и вам просто нужно ими воспользоваться.
- JEE – это все, что позволяет разработчикам сосредоточиться только на бизнес-функциональности . 99% всего остального было сделано за вас.
Когда запрос поступает на наш серверный компьютер по сети, он сначала передается в операционную систему . Операционная система определит, какому приложению переслать запрос, на основе порта оно было отправлено по адресу. В этом случае приложением является Виртуальная машина Java . JVM внутренне запускает контейнер приложений (например, Apache Tomcat или Стеклянная рыба ). Контейнер приложения реализует Java Servlet API . Контейнер приложения выполняет несколько функций:
- Он управляет одним или несколькими приложениями, которые доставляются в виде сервлетов . На практике большинство контейнеров содержат только один сервлет, но теоретически один Tomcat может содержать произвольное количество сервлетов.
- Он предоставляет реализацию servlet API. Это позволяет содержащимся приложениям взаимодействовать с контейнером. Наиболее заметным использованием этой функции является создание цепочки фильтров (подробнее об этом позже).
- Он обеспечивает интеграцию с Services API операционной системы. Таким образом, приложение, запущенное внутри контейнера, может быть запущено, завершено и перезагружено как служба уровня операционной системы. По этой причине, несмотря на то, что Java является мультиплатформенной, многие контейнеры приложений содержат код, специфичный для конкретной платформы (поэтому содержащиеся в них приложения остаются независимыми от платформы).
- Он перенаправляет входящие запросы в правильное приложение на основе сопоставления путей . Нередко можно увидеть один зарегистрированный сервлет для статического содержимого (привязанный к /static ) и один для динамического API вашего сервера приложений (привязанный к /api ). Он управляет пулом потоков для запросов и привязывает каждый запрос к потоку. Поскольку поток содержит
- контекст запроса , не рекомендуется вручную запускать новые потоки в среде JEE (если вы не знаете именно то, что вы делаете).
Традиционно приложения Java EE развертываются с помощью архивных файлов, известных как WAR files (для W eb AR chive) или EAR files (для E nterprise Архив ). Внутренняя файловая структура этих архивов стандартизирована. Контейнеры приложений извлекают содержащиеся файлы при запуске и запускают содержащиеся сервлеты. При этом контейнер привязывает ваш сервлет к указанному порту (либо указанному в коде, либо в файле конфигурации).
Обычно при работе в архитектуре JEE вы не будете реализовывать все с нуля. Многие задачи абсолютно одинаковы от одного приложения JEE к другому, поэтому имеет смысл использовать подходящий фреймворк. В основном, это фактическая эталонная реализация JEE и Spring framework. Я не могу много сказать о “ванильном” JEE, так как до сих пор я использовал исключительно Spring framework. Мы обсудим это более подробно в следующей статье.
Каждый входящий запрос, прежде чем он будет передан вашему приложению, должен пройти серию так называемых Фильтров сервлетов , которые образуют цепочку фильтров . Как только запрос проходит первый фильтр, включается второй фильтр и так далее. У каждого фильтра есть возможность заблокировать запрос. Контейнеры приложений позволяют настраивать цепочку фильтров с помощью Servlet API. Реализации JEE framework используют цепочку фильтров для многих задач, включая управление сеансами и безопасность. Фильтры также могут иметь побочные эффекты; если есть задача, которую вы хотите выполнить для каждого запроса, вы часто увидите реализацию в виде фильтра сервлета. Кроме того, если вам нужно привязать некоторую информацию к самому запросу, фильтры сервлетов являются обычным местом для этого.
Уровень представления – это то место, где ваш фактический код приложения впервые отвечает входящему запросу. Этот запрос прошел цепочку фильтров сервлетов, поэтому сеанс пользователя настроен и готов к работе, и вся аутентификация уже выполнена. В первые дни JEE уровень представления был местом, где происходила генерация HTML-страниц на стороне сервера. В настоящее время уровень представления состоит из набора контроллеров REST, которые предлагают различные конечные точки, составляющие ваш REST API. Если вы сталкиваетесь со старыми приложениями, вы также столкнетесь с веб-службами XML на уровне представления. Обычно на уровне представления выполняется проверка пользовательского ввода на стороне сервера и общая проверка запросов. Точно так же, как вы никогда не должны писать SQL-запросы в своем графическом коде, уровень представления не должен пытаться получить прямой доступ к базе данных. Классу на уровне представления разрешено взаимодействовать только с другим классом уровня представления, классом уровня обслуживания или элементом модели данных, который был возвращен уровнем обслуживания.
Уровень сервиса – это то место, где находится ваш фактический код приложения. Это место для того, чтобы воплотить ваши бизнес-правила в код. Сервисный уровень – это место, где вы перемещаете данные в своей модели данных, создаете новые элементы, удаляете старые и т.д. В зависимости от вашего варианта использования уровень сервиса может быть таким же маленьким, как “перенаправить этот вызов на уровень хранилища”, или чрезвычайно сложным процессом. Классы уровня сервиса могут взаимодействовать только с другими службами или с классами уровня репозитория.
Это последний уровень в вашем коде, который изменяет ваши данные перед тем, как они попадут в базу данных. Преобладающим элементом на этом уровне являются Репозитории (также известные как Объекты доступа к данным или DAO*s). Эти классы просто предлагают ряд методов, которые позволяют вам * сохранять , загружать , удалять и запрос ваши данные в базе данных. Здесь важно то, что вы никогда не должны позволять какой-либо специфике вашего хранилища данных выходить за пределы уровня репозитория – сама его цель состоит в том, чтобы убедиться, что вы можете обменять хранилище данных на другое (возможно, даже базу данных SQL с хранилищем NoSQL!). Внутренне ваши методы репозитория будут содержать фактические инструкции запроса. Если вы работаете со стандартным стеком JEE, то у вас будет Java Persistence API (JPA) Провайдер, такой как Hibernate на месте. JPA позволяет вам конвертировать вашу модель домена в таблицы SQL и обратно с относительной легкостью. В нем все еще есть много подводных камней, и он заслуживал бы отдельной статьи. Как вы, наверное, уже догадались, классы уровня репозитория не вызывают никаких других классов за пределами своего собственного уровня, за исключением классов JPA.
Модель данных представляет данные в вашем домене . Это единственный архитектурный элемент, который будет использоваться всеми тремя слоями вашего приложения. Поэтому крайне важно , чтобы классы модели предметной области не имели НИКАКИХ ссылок на какие-либо другие классы, за исключением классов, которые находятся внутри самой модели предметной области. В отличие от классов уровня представления, обслуживания и сохранения, модель предметной области с сохранением состояния . Как правило, вы не хотите иметь много логики в модели предметной области; в основном она существует для хранения ваших данных и предоставления чистого API, фактические сложные изменения выполняются на бизнес-уровне. Модель предметной области, хотя и не требуется явно в JEE, почти всегда следует шаблону JavaBean. Правильные геттеры и сеттеры здесь не обсуждаются, если вы хотите использовать стандартные фреймворки для простой обработки вашей модели предметной области, такие как проверка Bean и JPA (подробнее об этом позже). Элемент модели предметной области – это ваш типичный POJO – частные поля, конструктор, а также геттеры и сеттеры. Обычно такие фреймворки, как JPA, Jackson и JAXB, дополнительно заставляют вас предоставлять каждому классу конструктор по умолчанию, потому что эти классы должны быть созданы с помощью Java reflection. В отличие от почти всех других классов в архитектуре JEE, имеющих чистую реализацию equals()
и hashCode()
является решающим для POJOS модели предметной области. Обычно для этой цели каждый элемент модели предметной области имеет уникальный идентификатор, который также совпадает с его идентификатором в таблицах базы данных.
/| Запрос всегда привязан к потоку в JEE, который создается и управляется контейнером приложения (обычно в пуле потоков). Это означает, что серверное приложение JEE всегда по своей сути является параллельным, вы не можете избежать этого. Как мы все знаем, правильно работать с параллелизмом сложно . К счастью, архитектура JEE помогает вам, когда дело доходит до параллелизма. Если вы посмотрите на картинку выше, вы увидите четырех пользователей, работающих с приложением параллельно, каждый из которых представлен запросом/ответом, привязанным к потоку. Стоит отметить одну конкретную деталь: потоки никогда не пересекаются . Приложение не выполняет синхронизацию, а вместо этого предоставляет ее компоненту, который действительно хорош в этом: базе данных.
Как это возможно? Как мы можем иметь все эти уровни над базой данных без необходимости учитывать многопоточность? Вспомните, когда параллелизм становится проблемой: когда несколько потоков обращаются к одним и тем же данным. Вы хотите избежать этого случая любой ценой в приложении JEE (есть исключения, такие как кэши на уровне приложения). Для этого все классы, принадлежащие уровню репозитория и уровню сервиса , являются без состояния в JEE. У них нет полей, ни частных, ни общедоступных, которые удерживали бы изменяемое состояние. Так что же насчет данных? Данные загружаются для каждого пользователя и по запросу . Когда запрос поступает на уровень сервиса (уровень представления здесь является небольшим исключением), открывается новая транзакция базы данных для исключительного использования этим пользователем. Затем службы собирают запрошенные данные и/или выполняют запрошенные изменения, и все это в рамках одной транзакции. Прежде чем результат будет передан на уровень представления, транзакция фиксируется и закрывается.
Эта архитектура имеет два больших преимущества:
- Сервер имеет статус без состояния , что является хорошим свойством, например, для тестирования. Это помогает сохранить бизнес-логику очень простой и хорошо работает с более функциональным стилем программирования.
- Единственное место, где когда-либо встречаются параллельные модификации, – это база данных, но они специально разработаны для этого.
Стоимость, конечно, заключается в том, что каждый поток создает свое собственное (частичное) представление модели данных. Таким образом, если два пользователя запрашивают один и тот же фрагмент данных, он будет храниться в памяти дважды.
О ДЖИ можно было бы сказать гораздо больше. Я часто чувствую, что она получает много незаслуженной критики просто потому, что ее неправильно понимают. Он действительно хорошо сочетается с современными стилями и языками программирования и помогает создавать очень стабильные приложения. В некотором смысле, JEEP – это не столько то, что он предоставляет вам как программисту, сколько то, от чего он защищает вас (проблемы с параллелизмом, проблемы с целостностью данных, …). Архитектура JEE является ярким примером защитного программирования в этом отношении – в первую очередь речь идет о безопасности. Эта архитектура зарекомендовала себя как хорошо подходящая для крупных проектов и команд.
В следующей статье мы подробнее рассмотрим фактическую реализацию этой архитектуры на конкретном примере – с нашей стороны потребуется намного меньше кода, чтобы все это произошло, чем вы думаете.
Оригинал: “https://dev.to/martinhaeusler/java-enterprise-101-3djl”