Так как в идеи Spring Framework входит упрощение типовых задач, то и структура проекта имеет стандартный вид. Вам не нужно изобретать велосипед, когда вы работаете со Spring. В этой статье мы посмотрим, как должна выглядеть структура проекта. В таком её виде она будет помогать, а не мешать разработке. Всё будет на своих местах.
Все папки проекта
Всего в корне SpringBoot-проекта должны иметь место 6 важных папок:
В папке model располагаются классы-сущности, объекты которых представляют собой строки таблиц из базы данных. Класс – таблица, объект этого класса – строка этой таблицы.
Эти объекты вы получаете из БД в своём проекте при помощи классов из папки repository, управляя логикой работы с объектами сущностей с помощью классов из папки service. Классы service имеют в себе классы repository, чтобы выполнять свою работу при помощи них.
Непосредственные http-запросы получают и отвечают на них классы из папки controller. Они пользуются внутри себя классами service.
Папки dto и mapper опциональны, но чаще всего они нужны. Они ответственны за конвертацию объектов сущностей в более простые их варианты.
В проекте могут быть и другие пакеты, классы, но эти – базовые. Дальше мы рассмотрим цель каждой из этих папок и что там хранится.
Сущности
Сущность (Entity) в Spring-разработке – это объект, который отражён в базе данных. Сущностью может быть пользователь User. Это может быть комментарий пользователя Comment или его роль UserRole. Также сущностью может стать заказ Order и предметы OrderItem, которые заказаны в заказе. Да ладно: это может быть всё, что угодно, что вы решите хранить и использовать.
Сущности используются для хранения и манипулирования данными в базе данных с использованием JPA (Java Persistence API) или других подобных технологий. Классы сущностей помечаются аннотацией @Entity и выглядят примерно так:
1 2 3 4 5 6 7 8 9 10 11 |
@Entity @Table(name = "users") public class User { @Id private Long id; private String username; private String email; private List<Post> posts; ... } |
Сущности в структуре проекта Spring хранятся в папке model. Важный момент: для каждой сущности создаётся свой отдельный репозиторий, свой сервис, свой контроллер и так далее. Например: для сущности User создаются UserRepository, UserService, UserController, UserDto и UserMapper. Бывают исключения, когда напрямую читать, изменять и т.д. сущность в БД не надо.
Для упрощения работы с сущностями, а также с остальными классами, можно использовать библиотеку Lombok. Она позволяет сильно сократить шаблонный код.
Управление сущностями
Недостаточно просто иметь классы-представления таблиц из БД. Нужно их как-то оттуда получать, записывать и удалять, а также проводить более сложные операции.
Слой repository в структуре проекта отвечает за операции, непосредственно связанные с базой данных. Вследствие них происходит чтение, запись, изменение или удаление сущностей, которые представлены объектами. Пример репозитория:
1 2 3 |
public interface UserRepository extends JpaRepository<User, Long> { //может содержать дополнительные методы для запросов к бд } |
Слой service отвечает за более сложные операции, чем просто управление данными в БД. Однако в простых Spring-приложениях обычно слой service копирует функциональность методов слоя repository. Класс сервиса может выглядеть так:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@Service public class UserService { private final UserRepository userRepository; public UserService(UserRepository userRepository) { this.userRepository = userRepository; } @Override public User getUserById(Long id) { return userRepository.getUserById(id); } } |
Взаимодействие с http-запросами
Вот мы и подобрались к http-запросам. Ведь кто-то будет посылать нам просьбы о данных на сервер. А мы должны что-то отвечать. За это ответственен слой controller. Он принимает всяческие get-, post- и другие запросы, а затем посылает на них ответы.
Так как мы пишем чистый back-end без представления данных, мы будем помечать классы контроллеров не аннотацией @Controller, а аннотацией @RestController. В таком случае на запросы будет возвращаться не готовая страничка сайта, а только запрошенные данные. Как эти данные дальше использовать – ответственность запрашивающего.
Пример класса контроллера:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
@RestController public class UserController { private final UserService userService; public UserController(UserService userService) { this.userService = userService; } @GetMapping("/users/{userId}") public UserDto getUserById(@PathVariable Long userId) { return userService.getUserById(userId); } //другие методы } |
Конвертация сущностей
Конвертация сущностей представляется в Spring-проекте специальными объектами DTO – Data Transfer Object. DTO используются для передачи данных. Они представляют собой упрощённые объекты, которые не обязательно соответствуют структуре базы данных.
Порой слой dto может быть вовсе не нужен, не использован в проекте. Он не такой обязательный, как предыдущие слои. Но, как правило, конвертация сущностей всё-таки нужна. Зачем?
Да потому, что не всегда нужно передавать всю информацию из класса-сущности целиком. Более того, сущности зачастую ссылаются друг на друга. Когда объекты имеют ссылки друг на друга, возникает потенциальная проблема рекурсии при попытке преобразования их в JSON или другой формат для передачи этих объектов или сохранения их в базе данных.
Таким образом, конвертация сущностей отражается в двух папках проекта: dto и mapper. Папка dto содержит непосредственные классы упрощённых сущностей. А папка mapper, в свою очередь, содержит классы, которые умеют конвертировать entity в dto и dto в entity. Можно прописывать эти mapper-ы ручками, однако я предпочитаю использовать библиотеку MapStruct, которая делает это за меня.
Итого, пример dto:
1 2 3 4 5 6 7 |
public class UserDto { private Long id; private String username; private String email; //геттеры, сеттеры } |
А вот какой пример mapper-а при использовании библиотеки MapStruct:
1 2 3 4 5 6 7 |
@Mapper public interface UserMapper { public UserMapper INSTANCE = Mappers.getMapper(UserMapper.class); public UserDto toDto(User user); public User fromDto(UserDto userDto); } |
Это основные принципы структурирования Spring-проекта. Следуя этим шаблонам, вы создадите поддерживаемый, расширяемый и, надеюсь, чистый код. Его смогут понять другие Spring-разработчики, диагонально просмотрев названия папок и файлов в проекте. Вдобавок, вам будет легко найти подходящую информацию в интернетах, так как эта структура – стандартная.