Перед тим як почати писати код, важливо визначити архітектурний стиль застосунку, його основні компоненти та їх взаємодію. Будь-який досвідчений програміст підтвердить, що погано спроєктована архітектура подібна до дому без фундаменту: може й стояти, але недовго.
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для відсутнього ресурсу.
Проєктування endpoint-ів
Плануємо зробити базовий 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"));
}
}
На цьому етапі архітектура застосунку готова. Далі ми зануримось у декомпозицію задач проєкту, щоб зрозуміти, як краще організувати роботу над рештою модулів.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ