Статьи

Архитектура веб-приложения — Spring MVC — стек AngularJs

Spring MVC и AngularJ вместе создают действительно продуктивный и привлекательный внешний интерфейс для создания веб-приложений с интенсивной формой. В этом сообщении в блоге мы увидим, как можно создать веб-приложение с интенсивной формой с использованием этих технологий, и сравним такой подход с другими Доступные Варианты. Полнофункциональный и защищенный образец веб-приложения Spring MVC / AngularJs можно найти в этом репозитории github. Мы рассмотрим следующие темы:

  • Архитектура одностраничного приложения Spring MVC + Angular
  • Как структурировать веб-интерфейс с помощью Angular
  • Какие библиотеки Javascript / CSS хорошо дополняют Angular?
  • Как создать бэкэнд REST API с помощью Spring MVC
  • Защита REST API с помощью Spring Security
  • Как это соотносится с другими подходами, которые используют полный подход на основе Java?

Архитектура одностраничного веб-приложения Spring MVC + Angular

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

SpringMVCAngular2

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

Как построен бэкэнд?

Бэкэнд корпоративного веб-приложения может быть очень естественным и похожим на веб-интерфейс, как REST API. Эта же технология может использоваться для предоставления веб-сервисов сторонним приложениям — во многих случаях устраняется необходимость в отдельном стеке веб-сервисов SOAP.

С точки зрения DDD модель предметной области остается на бэкэнде, на уровне сервисов и постоянства. По сети проходят только DTO , но не модель домена.

Как структурировать веб-интерфейс с помощью Angular

Интерфейс должен быть построен вокруг конкретной модели представления (которая не является моделью предметной области) и должен обрабатывать только логику представления, но не бизнес-логику. Это три слоя внешнего интерфейса:

Уровень просмотра

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

01
02
03
04
05
06
07
08
09
10
11
<form ng-submit="onLogin()" name="form" novalidate="" ng-controller="LoginCtrl"
    <fieldset>
    <legend>Log In</legend>
    <div class="form-field">
         <input ng-model="vm.username" name="username" required="" ng-minlength="6" type="text">
    <div class="form-field">
         <input ng-model="vm.password" name="password" required="" ng-minlength="6" pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{6,}" type="password">
    </div></div></fieldset>
    <button type="submit">Log In</button>
    <a href="/resources/public/new-user.html">New user?</a>
 </form>

Уровень контроллера

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
angular.module('loginApp', ['common''editableTableWidgets']) 
    .controller('LoginCtrl', function ($scope, LoginService) {
  
        $scope.onLogin = function () {
            console.log('Attempting login with username ' + $scope.vm.username + ' and password ' + $scope.vm.password);
  
            if ($scope.form.$invalid) {
                return;
            }
  
            LoginService.login($scope.vm.userName, $scope.vm.password);
  
        };
  
    });

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

Любые проверки внешнего интерфейса должны повторяться в бэкэнде на уровне сервисного уровня по соображениям безопасности, поскольку проверки внешнего интерфейса можно легко обойти.

Уровень услуг внешнего интерфейса

Набор сервисов Angular, которые позволяют взаимодействовать с бэкэндом и которые могут быть внедрены в контроллеры Angular:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
angular.module('frontendServices', []) 
    .service('UserService', ['$http','$q', function($http, $q) {
        return {
            getUserInfo: function() {
                var deferred = $q.defer();
  
                $http.get('/user')
                    .then(function (response) {
                        if (response.status == 200) {
                            deferred.resolve(response.data);
                        }
                        else {
                            deferred.reject('Error retrieving user info');
                        }
                });
  
                return deferred.promise;
            }

Давайте посмотрим, какие другие библиотеки нам нужны для запуска и запуска интерфейса.

Какие библиотеки Javascript / CSS необходимы для дополнения Angular?

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

  • Легко настраиваемая чистая библиотека CSS размером всего 4 Кб от Yahoo с именем PureCss . Его Skin Builder позволяет легко создавать темы на основе основного цвета. Это решение BYOJ (Bring Your Own Javascript), которое помогает держать вещи «угловым путем».
  • функциональная библиотека программирования для манипулирования данными. Тот, который кажется наиболее используемым и лучше поддерживаемым и задокументированным в наши дни — это lodash .

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

  • модульная система, подобная requirejs , приятно иметь, но поскольку модульная система Angular не обрабатывает извлечение файлов, это приводит к некоторому дублированию между объявлениями зависимостей requirejs и угловых модулей.
  • Угловой модуль CSRF для предотвращения атак подделки межсайтовых запросов.
  • Модуль интернационализации

Как создать бэкэнд REST API с помощью Spring MVC

Бэкэнд строится с использованием обычных бэкэнд-слоев:

  • Уровень маршрутизатора: определяет, какие точки входа службы соответствуют заданному URL-адресу HTTP и как параметры должны считываться из запроса HTTP
  • Сервисный уровень: содержит любую бизнес-логику, такую ​​как проверки, определяет объем бизнес-транзакций
  • Уровень сохраняемости: сопоставляет базу данных с объектами домена в памяти

Spring MVC в настоящее время лучше всего настраивается с использованием только конфигурации Java. web.xml вряд ли когда-либо понадобится, посмотрите здесь пример полностью сконфигурированного приложения, использующего только конфигурацию Java.

Уровни обслуживания и постоянства создаются с использованием обычного подхода DDD, поэтому давайте сосредоточим наше внимание на уровне маршрутизатора.

Уровень маршрутизатора

Те же аннотации Spring MVC, которые использовались для создания приложения JSP / Thymeleaf, также можно использовать для создания REST API.

Большая разница в том, что методы контроллера не возвращают строку, которая определяет, какой шаблон представления должен быть отображен. Вместо
Аннотация @ResponseBody указывает, что возвращаемое значение метода контроллера должно быть отображено напрямую и стать телом ответа:

1
2
3
4
5
6
7
8
9
@ResponseBody
@ResponseStatus(HttpStatus.OK)
@RequestMapping(method = RequestMethod.GET)
public UserInfoDTO getUserInfo(Principal principal) {
    User user = userService.findUserByUsername(principal.getName());
    Long todaysCalories = userService.findTodaysCaloriesForUser(principal.getName());
  
    return user != null ? new UserInfoDTO(user.getUsername(), user.getMaxCaloriesPerDay(), todaysCalories) : null;
}

Если все методы класса должны быть аннотированы с помощью @ResponseBody , тогда лучше аннотировать весь класс с помощью @RestController .

При добавлении библиотеки JSON Джексона возвращаемое значение метода будет напрямую преобразовано в JSON без дальнейшей настройки. Также возможно преобразовать в XML или другие форматы, в зависимости от значения HTTP-заголовка Accept указанного клиентом.

Смотрите здесь пример пары контроллеров с настроенной обработкой ошибок.

Как защитить REST API с помощью Spring Security

API REST можно защитить с помощью конфигурации Spring Security Java. Хорошим подходом является использование формы входа в систему с откатом к HTTP Basic аутентификации HTTP Basic и включением некоторой защиты CSRF и возможностью обеспечения того, чтобы все методы бэкэнда были доступны только через HTTPS .

Это означает, что серверная часть предложит пользователю форму входа и назначит cookie-файл сеанса при успешном входе в систему для клиентов браузера, но он все равно будет хорошо работать для клиентов, не входящих в браузер, благодаря поддержке отката на HTTP Basic, где учетные данные передаются через заголовок HTTP Authorization ,

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

Это пример того, как настроить безопасность REST API:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
http
      .authorizeRequests()
      .antMatchers("/resources/public/**").permitAll()
      .anyRequest().authenticated()
      .and()
      .formLogin()
      .defaultSuccessUrl("/resources/calories-tracker.html")
      .loginProcessingUrl("/authenticate")
      .loginPage("/resources/public/login.html")
      .and()
      .httpBasic()
      .and()
      .logout()
      .logoutUrl("/logout");
  
  if ("true".equals(System.getProperty("httpsOnly"))) {
      LOGGER.info("launching the application in HTTPS-only mode");
      http.requiresChannel().anyRequest().requiresSecure();
  }

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

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

Сравнение углового стека Spring / MVC с другими распространенными подходами

Такой подход использования Javascript для внешнего интерфейса и Java для внутреннего интерфейса обеспечивает упрощенный и продуктивный рабочий процесс разработки.

Когда серверная часть работает, для достижения полной возможности быстрого развертывания не требуются специальные инструменты или плагины: просто опубликуйте ресурсы на сервере с помощью вашей IDE (например, нажав Ctrl+F10 в IntelliJ) и обновите страницу браузера.

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

Повышение производительности разработки полного стека?

По моему опыту, возможность редактировать Html и CSS напрямую без промежуточных уровней косвенности (см. Здесь высокоуровневое угловое сравнение с GWT и JSF ) помогает уменьшить умственные издержки и делает вещи простыми. Цикл разработки edit-save-refresh очень быстрый и надежный и дает огромный прирост производительности.

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

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

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

Выводы

Spring MVC и Angular вместе открывают дверь для нового способа создания веб-приложений с интенсивным использованием форм. Повышение производительности благодаря этому подходу делает его альтернативой, достойной внимания.

Отсутствие какого-либо состояния сервера между запросами (кроме файла cookie для проверки подлинности) устраняет целую категорию ошибок.

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

Ссылка: Архитектура веб-приложений — Spring MVC — стек AngularJs от нашего партнера по JCG Алексея Новика в блоге The JHades Blog .