Перед тем, как начать писать код, важно определить архитектурный стиль приложения, основные его компоненты и их взаимодействие. Любой опытный программист подвтердит, что плохо спроектированная архитектура подобна дома без фундамента: может и стоять, но недолго.
1. Архитектурный обзор
Выбор архитектурного стиля
Мы разрабатываем приложение в стиле монолитной архитектуры. Да-да, никакого хайпа микросервисов на данном этапе. Почему? Потому что наше приложение будет сравнительно небольшим, и переключаться на микросервисы нет смысла. Всё будет собрано в одном проекте, но мы разделим его на логические модули.
Компоненты приложения
Составляющие нашего приложения можно разделить на несколько ключевых блоков:
- REST API: будем предоставлять интерфейс взаимодействия через HTTP-запросы для внешних пользователей (например, фронтенда).
- Сервисный слой (бизнес-логика): здесь мы сосредоточим основной функционал приложения, такой как обработка данных, валидация и управление транзакциями.
- База данных: для хранения информации.
- Безопасность: защитим доступ к нашим данным и функционалу.
Вот пример схемы компонента:
+-------------------+
| REST API |
|-------------------|
| Контроллеры |
+-------------------+
|
v
+-------------------+
| Сервисный слой |
|-------------------|
| Бизнес-логика |
+-------------------+
|
v
+-------------------+
| Доступ к данным |
|-------------------|
| JPA Репозитории |
+-------------------+
|
v
+-------------------+
| База данных |
|-------------------|
2. Дизайн REST API
Для начала вспомним, что REST API — это интерфейс, позволяющий взаимодействовать с приложением через HTTP-запросы. Основные принципы REST:
- Ресурсоориентированность: всё в нашем приложении рассматривается как ресурс. Например, пользователь — это ресурс
/users. - Методы HTTP: для разных операций используются разные методы:
GETдля получения данных.POSTдля создания новых данных.PUTдля обновления данных.DELETEдля удаления данных.
- Статусные коды HTTP: Сервер возвращает статус выполнения операции. Например:
200 OKдля успешного ответа.201 Createdдля успешного создания ресурса.404 Not Foundдля отсутствующего ресурса.
Проектирование эндпоинтов
Мы планируем создать базовый CRUD для пользователей. Вот предполагаемые эндпоинты:
| Метод | URL | Описание |
|---|---|---|
| GET | /users |
Получить список пользователей |
| GET | /users/{id} |
Получить пользователя по ID |
| POST | /users |
Создать нового пользователя |
| PUT | /users/{id} |
Обновить данные пользователя |
| DELETE | /users/{id} |
Удалить пользователя |
Вот как это выглядит в контроллере:
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping
public List<User> getAllUsers() {
// Возвращает список всех пользователей
}
@GetMapping("/{id}")
public User getUserById(@PathVariable Long id) {
// Получает пользователя по id
}
@PostMapping
public User createUser(@RequestBody User user) {
// Создает нового пользователя
}
@PutMapping("/{id}")
public User updateUser(@PathVariable Long id, @RequestBody User user) {
// Обновляет данные пользователя
}
@DeleteMapping("/{id}")
public ResponseEntity
deleteUser(@PathVariable Long id) {
// Удаляет пользователя
}
}
3. Безопасность приложения
Реализация аутентификации с использованием Spring Security
Вы уже знаете, что Spring Security позволяет нам добавлять правила аутентификации и авторизации.
1. Подключение зависимости: В pom.xml добавляем:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2. Создание конфигурации безопасности:
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/users/**").authenticated()
.anyRequest().permitAll()
.and()
.httpBasic(); // Используем базовую аутентификацию
}
}
Здесь мы защищаем эндпоинты /users/**, а остальные оставляем доступными для всех.
4. Работа с базой данных
Проектирование базы данных
Для нашего приложения нам потребуется таблица users. Пример структуры (ER-диаграмма):
| Поле | Тип данных | Описание |
|---|---|---|
| id | BIGINT | Идентификатор |
| name | VARCHAR | Имя пользователя |
| VARCHAR | Электронная почта | |
| password | VARCHAR | Пароль |
Имплементация сущностей
Создаём сущность User с использованием JPA-аннотаций:
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Column(nullable = false, unique = true)
private String email;
@Column(nullable = false)
private String password;
// Геттеры и сеттеры
}
Репозиторий для работы с базой
Используем Spring Data JPA для работы с данными:
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
}
Теперь мы можем выполнять CRUD-операции с пользовательскими данными через этот интерфейс.
Конфигурация подключения
В application.properties настраиваем подключение к базе:
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.hibernate.ddl-auto=update
Взаимодействие компонентов
Итак, как всё это соединяется?
- REST-запрос приходит в контроллер.
- Контроллер вызывает соответствующий метод из сервисного слоя.
- Сервисный слой взаимодействует с репозиториями и возвращает результат.
- Контроллер возвращает ответ клиенту.
Вот пример цепочки:
@RestController
public class UserController {
private final UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/{id}")
public User getUserById(@PathVariable Long id) {
return userService.getUserById(id);
}
}
@Service
public class UserService {
private final UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User getUserById(Long id) {
return userRepository.findById(id).orElseThrow(() -> new RuntimeException("User not found"));
}
}
На этом этапе архитектура приложения готова. Далее мы погрузимся в декомпозицию задач проекта, чтобы понять, как лучше организовать работу над оставшимися модулями.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ