Skip to content

Тема 36. Spring MVC

Alesey edited this page Nov 15, 2022 · 13 revisions

Материал находится на стадии "Черновик"

Содержание:

  1. Паттерн MVC
  2. Spring-контроллеры
  3. Шаблонизаторы
  4. Обзор и подключение Swagger
  5. Обработка ошибок
  6. Список литературы/курсов

Паттерн MVC

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

MVC расшифровывается как модель-представление-контроллер (от англ. model-view-controller). Это способ организации кода, который предполагает выделение блоков, отвечающих за решение разных задач. Один блок отвечает за данные приложения, другой отвечает за внешний вид, а третий контролирует работу приложения.

Компоненты MVC:

Модель (model) — этот компонент отвечает за данные, а также определяет структуру приложения. Например, если необходимо создать To-Do приложение, код компонента model будет определять список задач и отдельные задачи. Это самая независимая часть системы. Настолько независимая, что она не должна ничего знать о модулях Вид и Контроллер. Модель настолько независима, что ее разработчики могут практически ничего не знать о Виде и Контроллере.

Представление (view) — этот компонент отвечает за взаимодействие с пользователем. То есть код компонента view определяет внешний вид приложения и способы его использования. Основное предназначение Представления — предоставлять информацию из Модели в удобном для восприятия пользователя формате. Основное ограничение Представления — он никак не должен изменять Модель.

Контроллер (controller) — этот компонент отвечает за связь между model и view. Код компонента controller определяет, как сайт реагирует на действия пользователя. По сути, это мозг MVC-приложения. Основное предназначение Контроллера — обрабатывать действия пользователя. Именно через Контроллер пользователь вносит изменения в Модель. Точнее в данные, которые хранятся в Модели.

Разберем паттерн MVC на примере магазина сэндвичей.

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

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

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

Покупку бутерброда можно описать через MVC:

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

Из всего этого можно сделать вполне логичный вывод. Сложную систему нужно разбивать на модули.

Рассмотрим пример:

Ключевая идея MVC состоит в том, что любое приложение с пользовательским интерфейсом в первом приближении можно разбить на 2 модуля: модуль, отвечающий за реализацию бизнес-логики приложения, и пользовательский интерфейс.

В первом модуле будет реализован основной функционал приложения. Данный модуль будет ядром системы, в котором реализуется Модель предметной области приложения. В концепции MVC данный модуль будет Моделью.

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

Основная цель такого разделения — сделать так, чтобы ядро системы (Модель в терминологии MVC) могла независимо разрабатываться и тестироваться.

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

Далее необходимо используя шаблон Наблюдатель, добиться еще большей независимости Модели, а также синхронизации пользовательских интерфейсов.

Здесь мы преследуем 2 цели:

  • Добиться еще большей независимости Модели.
  • Синхронизировать пользовательские интерфейсы.

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

Предположим, мы покупаем билет в кино через интернет и видим количество свободных мест в кинотеатре. Одновременно с нами покупать билет в кино может кто-то еще. Если этот кто-то купит билет раньше нас, нам бы хотелось увидеть, что количество свободных мест на наш сеанс уменьшилось.

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

На сайте 2 пользователя одновременно выбирают место.

Первый пользователь купил билет. Второму пользователю необходимо отобразить на странице эту информацию.

Как это происходит:

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

Чтобы достичь этого, необходимо реализовать шаблон Наблюдатель. С его помощью Модель рассылает уведомления об изменениях всем подписчикам. Интерфейс, являясь таким подписчиком, получит уведомление и обновится.

Шаблон Наблюдатель позволяет Модели с одной стороны информировать интерфейс (представление и контроллер) о том, что в ней произошли изменения, а с другой — фактически ничего о них “не знать”, и тем самым оставаться независимой. С другой стороны, это позволит синхронизировать пользовательские интерфейсы.

Разделение интерфейса на Представление и Контроллер Продолжаем делить приложение на модули, но уже на более низком уровне иерархии. На этом шаге пользовательский интерфейс (который был выделен в отдельный модуль на шаге 1) делится на Представление и Контроллер.

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

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

Здесь скорее речь идет о функциональном разделении. Основная задача пользовательского интерфейса — обеспечить взаимодействие пользователя с системой. Это означает, что у интерфейса всего 2 функции:

  • выводить и удобно отображать пользователю информацию о системе;
  • вводить данные и команды пользователя в систему (передавать их системе);

Данные функции и определяют то, как нужно делить интерфейс на модули.

В итоге, архитектура системы выглядит так:

Итак, у нас появилось приложение из трех модулей, которые называются Модель, Представление и Контроллер.

Таким образом:

  1. Следуя принципам MVC, систему нужно разделять на модули.
  2. Самым важным и независимым модулем должна быть Модель.
  3. Модель — ядро системы. Нужна возможность разрабатывать и тестировать ее независимо от интерфейса. Для этого на первом шаге сегрегации системы нужно разделить ее на модель и интерфейс. Далее, с помощью шаблона Наблюдатель, укрепляем модель в ее независимости и получаем синхронизацию пользовательских интерфейсов. Третьим шагом делим интерфейс на Контроллер и Представление. Все, что на ввод информации от пользователя в систему — это в Контроллер. Все что на вывод информации от системы к пользователю — это в Представление.

Взаимосвязь Представления и Контроллера с Моделью Когда пользователь вводит информацию через Контроллер, он тем самым вносит изменения в модель. По крайней мере, пользователь вносит изменения в данные Модели.

Когда пользователь получает информацию через элементы интерфейса (через Представление), пользователь получает информацию о данных модели.

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

Модель представляет тесно связанный между собой набор классов, к которым, по-хорошему, ни у Представления, ни у Контроллера не должно быть доступа.

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

Схематично, в итоге, все будет выглядеть так:

Преимущества MVC

Основная цель следования принципам MVC — отделить реализацию бизнес-логики приложения (Модели) от ее визуализации (Представления).

  1. Такое разделение повысит возможность повторного использования кода.
  2. Польза применения MVC наиболее наглядна в случаях, когда пользователю нужно предоставлять одни и те же данные в разных формах. Например, в виде таблицы, графика или диаграммы (используя различные виды). При этом, не затрагивая реализацию видов, можно изменить реакции на действия пользователя (нажатие мышью на кнопке, ввод данных).
  3. Если следовать принципам MVC, можно упростить написание программ, повысить читаемость кода, сделать легче расширение и поддержку системы в будущем.

Spring-контроллеры

Шаблонизаторы

Шаблонизаторы являются отличным инструментом для создания веб-приложений с более чистым и простым кодом. Рассмотрим наиболее популярные.

Mustache.JS Mustache.JS - это логический шаблонный синтаксис. Его можно использовать для HTML, конфигурационных файлов, исходного кода - чего угодно. Он работает путем расширения тегов в шаблоне, используя значения, предоставленные в хэше или объекте.

Transparency Transparency - это минимальный шаблонизатор для jQuery. Он отображает объекты JSON на элементы DOM с нулевой конфигурацией. Просто вызовите .render().

Hogan.js Hogan.js - это шаблонизатор JS 3.4k, разработанный в Twitter. Используйте его как часть вашего упаковщика ресурсов для предварительной компиляции шаблонов или включите его в браузер для обработки динамических шаблонов. Если вы разрабатываете с Node.js, просто используйте NPM, чтобы добавить пакет Hogan.

HandlebarsJS HandlebarsJS - обеспечивает необходимую мощность, позволяющую эффективно создавать семантические шаблоны без каких-либо разочарований. Handlebars в значительной степени совместим с шаблонами Mustache. В большинстве случаев можно поменять Mustache с помощью Handlebars и продолжить использовать текущие шаблоны.

Jade Language Jade - это движок HTML шаблонов, в основном используемый для серверных шаблонов в NodeJS.

Nunjucks Nunjucks - это богатый и мощный язык шаблонов с наследованием блоков, автоэкранированием, макросами, асинхронным управлением и многим другим.

Обзор и подключение Swagger

Swagger - это стандартизированная и полная структура для генерации, описания, вызова и визуализации веб-сервисов RESTful. Это удобный инструмент для создания документации API, который помогает разработчикам сэкономить время. Он предлагает несколько решений для интеграции в проект и формирования интерактивной версии документации, с которой будет удобно взаимодействовать другим разработчикам, внешним пользователям, клиентам.

Swagger — это набор инструментов, которые помогают описывать API. Благодаря ему пользователи и машины лучше понимают возможности REST API без доступа к коду. С помощью Swagger можно быстро создать документацию и отправить ее другим разработчикам или клиентам.

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

Основные подходы Swagger предлагает два основных подхода к генерированию документации:

  • Автогенерация на основе кода.
  • Самостоятельная разметка-написание.

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

Чтобы пользоваться вторым подходом, нужно знать синтаксис Swagger. Описания можно готовить в формате YAML/JSON. Можно упростить эту задачу, используя Swagger Editor. Конечно, второй подход позволяет сделать документацию более качественной и кастомной для каждого конкретного проекта и его особенностей, к тому же все не так сложно как может показаться, это потребует минимальных дополнительных усилий.

Swagger Core Это Java-реализация спецификации. Для ее использования потребуется:

  • Java 8 и выше
  • Apache Maven 3.0.4 и выше
  • Jackson 2.4.5 и выше

Для его внедрения в проект используются две зависимости.

Приведем примеры:

<dependency>
    <groupId>io.swagger.core.v3</groupId>
    <artifactId>swagger-annotations</artifactId>
    <version>2.1.11</version>
</dependency>
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-ui</artifactId>
    <version>1.5.2</version>
</dependency>
Другой способнастроить maven-плагин. Тогда описания при сборке проекта будут генерироваться в файл YAML. Пример:

<plugin>
 <groupId>org.springdoc</groupId>
 <artifactId>springdoc-openapi-maven-plugin</artifactId>
 <version>0.3</version>
 <executions>
   <execution>
   <phase>integration-test</phase>
   <goals>
     <goal>generate</goal>
   </goals>
   </execution>
 </executions>
 <configuration>
   <apiDocsUrl>http://localhost:3080/v3/api-docs</apiDocsUrl>
   <outputFileName>openapi.yaml</outputFileName>
   <outputDir>${project.build.directory}</outputDir>
 </configuration>
</plugin>

Для конфигурации нужны еще два бина (beans). В них мы описываем название приложения, версию API, добавляем другие важные данные.

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

Аннотация Использование
@Operation Для описания операции или метода HTTP
@Parameter Для представления одного параметра в операции
@RequestBody Для представления тела запроса в операции
@ApiResponse Для представления тела ответа в операции
@Tag Для представления тегов операции или определения OpenAPI
@Server Для представления серверов операции или определения OpenAPI
@Callback Для описания набора запросов
@Link Для представления ссылки времени разработки для ответа
@Schema Для определения входных и выходных данных
@ArraySchema Для определения входных и выходных данных для типов массивов
@Content Для представления схемы и примеров для мультимедиа
@Hidden Для скрытия ресурса, операции или свойстваSwagger Codegen

Преимущества Swagger Codegen:

  • Генерирование серверов — позволяет избавиться от рутины, создавая шаблонный код на 20+ языках.
  • Упрощенное генерирование SDK — можно создавать клиентские наборы на 40+ языках для быстрой интеграции с вашим API.
  • Постоянное обновление инструментов.

Чтобы добавить Swagger Codegen в проект, используйте зависимость:

<dependency>
    <groupId>io.swagger.codegen.v3</groupId>
    <artifactId>swagger-codegen-maven-plugin</artifactId>
    <version>3.0.24</version>
</dependency>
Как и в случае с Swagger Core, можно настроить maven-плагин для генерации клиента или мок-сервера.

<plugin>
        <groupId>org.openapitools</groupId>
        <artifactId>openapi-generator-maven-plugin</artifactId>
        <version>3.3.4</version>
        <executions>
          <execution>
            <phase>compile</phase>
            <goals>
              <goal>generate</goal>
            </goals>
            <configuration>
              <generatorName>spring</generatorName>
              <inputSpec>${project.basedir}/src/main/resources/api.yaml</inputSpec>
              <output>${project.build.directory}/generated-sources</output>
              <apiPackage>com.api</apiPackage>
              <modelPackage>com.model</modelPackage>
              <supportingFilesToGenerate>
                ApiUtil.java
              </supportingFilesToGenerate>
              <configOptions>
                <groupId>${project.groupId}</groupId>
                <artifactId>${project.artifactId}</artifactId>
                <artifactVersion>${project.version}</artifactVersion>
                <delegatePattern>true</delegatePattern>
                <sourceFolder>swagger</sourceFolder>
                <library>spring-mvc</library>
                <interfaceOnly>true</interfaceOnly>
                <useBeanValidation>true</useBeanValidation>
                <dateLibrary>java8</dateLibrary>
                <java8>true</java8>
              </configOptions>
              <ignoreFileOverride>${project.basedir}/.openapi-generator-ignore</ignoreFileOverride>
            </configuration>
          </execution>
        </executions>
      </plugin>

Swagger Codegen предоставляет следующие команды:

  • Config-help — помощь с настройкой выбранного языка.
  • Generate — генерирование кода с выбранным генератором.
  • Help — вывод справочной информации про openapi-generator.
  • List — список доступных генераторов.
  • Meta — генератор набора шаблонов и настроек Codegen. Использует информацию о выбранном языке.
  • Validate — проверка спецификации.
  • Version — отображение используемой версии.
  • Самые полезные команды — generate и validate. Первая позволяет создать клиент, а вторая — проверить его соответствие спецификации.
  • Посмотреть полный список возможностей можно с помощью команды help после запуска jar-файла.

Swagger UI Swagger UI визуализирует ресурсы OpenAPI и взаимодействие с ними без отображения логики реализации. Этот инструмент берет спецификацию и превращает ее в интерактивный проект, где можно выполнять разные запросы для тестирования API.

Преимущества Swagger UI:

  • Нет зависимостей. Интерфейс доступен в любой среде разработки.
  • Полная поддержка браузерами. Удобное взаимодействие для других разработчиков и клиентов. Можно проверить все операции, предоставляемые API, даже не имея специальных знаний.
  • Простая навигация. Ресурсы и конечные точки представлены в виде аккуратно распределенного на категории списка.
  • Можно настраивать стиль и пользовательский интерфейс так, как вам хочется.
  • Пользовательский интерфейс Swagger полностью размещен в SwaggerHub. Вы можете написать и визуализировать API или импортировать существующие определения для создания интерактивного интерфейса, размещенного в облаке. Благодаря встроенной интерактивности SwaggerHub позволяет безопасно предоставлять доступ к документации внешним пользователям или другим разработчикам.

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

ПреимуществаSwagger Editor:

  • Работает в любой среде разработки, локально и в сети.
  • Дает обратную связь — показывает ошибки в синтаксисе по мере написания, проверяя его на соответствие OpenAPI.
  • Позволяет мгновенно визуализировать API через Swagger UI.
  • Помогает писать документацию быстрее благодаря интеллектуальному автозаполнению.
  • Предоставляет гибкие инструменты для настройки конфигурации — от темы оформления до межстрочного интервала.
  • Предлагает создание серверных заглушек и клиентских библиотек для вашего API на всех популярных языках.

Преимущества использования Swagger

  • Swagger может создать интерактивную консоль API, которую разработчики могут использовать для быстрого изучения и опробования API.
  • Swagger может генерировать клиентский SDK-код для реализации на различных платформах.
  • Файлы Swagger могут автоматически генерироваться из комментариев кода на многих различных платформах.
  • Swagger имеет сильное сообщество со многими влиятельными участниками.

Обработка ошибок

Самый простой способ обработки ошибок-это ответить соответствующим кодом состояния.

Некоторые распространенные коды ответов включают в себя:

  • 400 Плохой запрос — Клиент отправил недопустимый запрос — например, отсутствует требуемое тело запроса или параметр.

  • 401 Неавторизованный — Клиенту не удалось пройти аутентификацию на сервере.

  • 403 Forbidden — Клиент аутентифицирован, но не имеет разрешения на доступ к запрошенному ресурсу.

  • 404 Не найден — Запрошенный ресурс не существует.

  • 412 Ошибка предварительного условия — одно или несколько условий в полях заголовка запроса оцениваются как ложные.

  • 500 Внутренняя ошибка сервера — На сервере произошла общая ошибка.

  • 503 Услуга Недоступна — Запрошенная услуга недоступна.

  • Будучи базовыми, эти коды позволяют клиенту понять широкий характер возникшей ошибки. Например, мы знаем, если получаем ошибку 403, что у нас нет разрешений на доступ к запрошенному ресурсу.

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

500 ошибок сигнализируют о том, что при обработке запроса на сервере возникли некоторые проблемы или исключения. Как правило, эта внутренняя ошибка не является делом нашего клиента.

Следовательно, чтобы свести к минимуму такого рода ответы клиенту, мы должны усердно пытаться обрабатывать или улавливать внутренние ошибки и отвечать другими соответствующими кодами состояния, где это возможно. Например, если исключение возникает из-за того, что запрошенный ресурс не существует, мы должны выставить это как ошибку 404, а не 500.

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

Список литературы/курсов

  1. http://leonidas.github.io/transparency/
  2. http://twitter.github.io/hogan.js/
  3. https://handlebarsjs.com/
  4. https://jade-lang.com/
  5. http://mozilla.github.io/nunjucks/

Тема 35. HTTP и REST|Оглавление|Тема 37. Spring Data JPA

Clone this wiki locally