Как сделать проект на Java Spring Boot?

Kate

Administrator
Команда форума
Эта статья ориентирована на начинающих Back-end программистов, в частности на Java, т.к. здесь будет рассмотрен процесс планирования и программирования сервера на Java Spring Boot с базой данных PostgreSQL

Репозиторий с кодом

Предметом разбора будет выступать моя командная проектная работа за 2-ой курс ВУЗа:

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


Планирование​

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

Для реализации этих требований мы решили показать пользователю следующие экраны:

  • регистрацию/логин
  • страницу аккаунта
  • каталог фильмов
  • страницу с фильмом с плеером
  • чат для обсуждения
И предусмотреть следующее взаимодействие: добавление фильмов в избранное, комментарии и лайки/дизлайки у комментариев

Дальше вместе с напарником обсудили:

  1. сущности фильма и пользователя
  2. необходимые запросы к сущностям
  3. безопасность
Проговорив всё это, не терпелось приступить к создании архитектуры БД. Для этого понадобилось только знание о типах связей между сущностями [1:1, 1:N, M:N]

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

Программирование​

Я пойду по своему коду в хронологическом порядке

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

TangoApplication​

Точка входа в приложение. В начале тут ничего нет, однако, стоит заметить 1 любопытную деталь: очень многое в Spring Boot делается через аннотации

@SpringBootApplicaion - аннотация, которая создаётся автоматически и конфигурирует настройки по умолчанию

Помимо метода main В этом классе можно определить commandLineRunner, чтобы сделать какие-либо действия при старте сервера, в данном случае там закидываются стартовые значения в базу

@Bean — указывает Spring'у, чтобы он учитывал этот метод у себя под капотом

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

f3a09ce3775bb5f00d954a4f519becfa.jpeg


Контроллеры​

Это API, к которой будет общаться специалист на Front-end через запросы. Её функционал был определен на этапе планирования

Контроллер на картинке из себя представляет самый обычный класс, со специальной аннотацией @RestController(path="api/common")

Внутри этого класса определяются методы, к которым можно обратиться с определённой целью, на сленге они называются "ручки". Чтобы определить ручку, используется ряд аннотаций для разных типов запросов: @GetMapping,@PostMapping итд

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

  • типы запросов
  • принятие аргументов и параметров запроса
  • получение тела запроса
Простенький контроллер для того, чтобы вернуть весь список жанров, или тэгов, или ролей
Простенький контроллер для того, чтобы вернуть весь список жанров, или тэгов, или ролей
Стоит вспомнить, что специалист с Front-end пока ничего не получил. И это неудивительно, не была создана ни база данных, ни сущности. Тем не менее, для создания функционала с его стороны не обязательны настоящие данные

Фальшивые данные в нужном формате называются Mock'ом. Можно возвращать заранее сгенерированный объект или, если ответ не большой, поля в подобном виде:

Map<String, Object> response = new HashMap<>();
response.put("message", "Test data");
response.put("number", 1)
return ResponseEntity.ok(response);
Не сложно сделать такие заглушки для хотя бы какой-то части API. Развернув сервер, Back-end и Front-end станут менее зависимы друг от друга. Сленг: развёртывание == деплой

Пускай, API известно, однако, фронтендер не станет лезть в код сервера для выяснения вопроса: "Как им пользоваться?"

Нам было удобно держать API в Postman, где я создал коллекцию, в которой содержались запросы со всеми аргументами и их телом

Пример ответа API на запрос, определённый выше по статье, в commonController
Пример ответа API на запрос, определённый выше по статье, в commonController

Models​

Переходим к самому главному, сущностям и БД

В первую очередь подключаемся к БД. Определяем нужные поля в application.properties и затем, при помощи вкладки Database справа в IntelliJ к самой базе. В community-версии IntelliJ придётся прибегать к помощи других статей/видео для решения этой проблемы

Здесь всё, что нужно для подключения БД
Здесь всё, что нужно для подключения БД
На картинке ниже представлена сущность пользователя

Часть аннотаций здесь от библиотеки Lombok, которая призвана сократить код объекта, тем самым сделав его более читабельным

@Data - определяет getter и setter к каждой сущности, добавляет методы toString, equals, hashcode

@NoArgsConstructor — создаёт конструктор без аргументов

Время разобраться, что здесь происходит
Время разобраться, что здесь происходитК сущности:
@Entity — обозначается сущность
@Table — таблица
@Data @NoArgsConstructor — аннотации Lombok. Реализуют некоторые методы
@JsonIncude(JsonInclude.NON_NULL) — при сериализации в JSON попадают все поля, которые не NULL

К полю:
@JsonProperty(value="название") — при сериализации будет указывать тебе определённое имя
@Column(name = "колонка") — нужно, чтобы задать название для колонки в базе данных
@Transient — означает, что это поле будет вычисляться во время запроса

К ID:
@Id, @SequenceGenerator, @GeneratedValue — нужно для того, чтобы создать ID

Отношения между сущностями:
@OneToOne, @ManyToOne, @OneToMany, @ManyToMany,
@JsonBackReference, @JsonManagedReference
Про@JoinTable и отношения между сущностями лучше прочитать на Baeldung


А как писать запросы к БД PostgreSQL без SQL?​

→ Современный подход к построению запросов к базе в Spring Boot реализован через специальные интерфейсы - репозитории

@Repository — указывает на то, что это репозиторий

@JpaRepository<сущность, тип_её_ID> — включает все заготовленные запросы, чтобы обращаться к определённой сущности

Чтобы создавать авторские запросы, нужно в специальном формате называть функции, либо же с помощью аннотации @Query писать SQL

204c9eb1e63d9b7fe76699f4adb38f57.png

Чтобы сделать пагинацию, нужно использовать Page<сущность> — контейнер для нескольких экземпляров сущности и добавлять в аргументы функции Pageable pageable


DTO​

→ Data Transfer Object — нужны, чтобы перекидывать уменьшенные по полям сущностями внутри сервера. Например, оперировать ими в запросах API

Их использование обусловлено тем, что язык типизированный и нельзя налету убрать свойство из объектов

// Пример DTO: Тело запроса на регистрацию
@Data
public class SignupRequestDTO {
private String email;
private String username;
private String password;
private LocalDate date_of_birth;
private LocalDate sub_deadline;
private Set<String> roles;
private String avatar;

public User fromWithoutRoles() {
return new User(email, username, password, date_of_birth, sub_deadline);
}
}

Сервисы​

В контроллерах слишком громоздко писать логику, поэтому считается правильным держать её в специальных классах с аннотацией @Service и оттуда уже вызывают нужный метод для определённого запроса

Хорошей практикой считается сделать интерфейс с нужными методами рядом с сущностью, а затем написать класс, реализующий этот интерфейс, с постфиксом Impl

0b45d310b9c7a5fa37c4da7b6ee2a848.jpeg

@Autowired — это одна из ключевых концепций Spring «Dependency Injection»: внутри Spring регистрируются все эти зависимости и затем их можно куда-то вставить через аннотацию

Сервисы используют репозитории, либо же более старые вариации логики для запросов к базе


Тесты​

В 1 момент я почувствовал, что есть функционал, который я не хочу проверять через запросы. Эта мысль стала отправной точкой к написанию тестов

Для запуска из IntelliJ нужно нажать на тест ПКМ
Для запуска из IntelliJ нужно нажать на тест ПКМ

Безопасность​

Тема безопасности невероятно обширна и начать стоит с вопроса: "Какие вообще возможности аутентификации бывают?"

В github репозитории лежит мой вариант имплементации работы с JWT, основанный на добавлении MiddleWare для авторизации

MiddleWare — это функция, в которую попадают запросы, прежде чем оказаться в контроллерах, таким образом, туда удобно положить проверку на JWT-токен


Utils​

→ Логика, которая используется в разных местах, но не связана напрямую с сущностями

В проекте я вынес внутреннюю логику зашифровки/расшифровки JWT-токена, функции для загрузки картинок на Imgur


Где хранить секреты?​

→ В файлике application.properties

Там определяются константы, к которым можно потом обратиться через аннотацию @Value

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

// Где-либо
@Value("${jwt.jwtSecret}")
private String jwtSecret;

@Value("${jwt.jwtExpirationMs}")
private int jwtExpirationMs;

// В application.properties
jwt.jwtSecret=секрет
jwt.jwtExpirationMs=86400000

Заключение​

В github-репозитории остался код для некоторого количества незатронутых тем:

  1. Создание сайта чатика на Front-end при помощи JS, CSS и HTML
  2. Чатик на WebSocket со стороны браузера, который правда, мне сейчас не очень нравится
  3. Безопасность с JWT
  4. Свой класс ошибки
Это был общий взгляд на создание небольшого проекта со стороны Back-end.

 
Сверху