Какую архитектурную роль играют слои в Spring-приложении?

«Какую архитектурную роль играют слои в Spring-приложении?» — вопрос из категории Spring, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Слоистая архитектура (Layered Architecture) в Spring — это стандартный подход к организации кода, который разделяет ответственность по функциональным областям. Каждый слой имеет строго определенную роль, что делает приложение более модульным, тестируемым и сопровождаемым.

Основные слои и их ответственность:

  1. Controller (Веб-слой):

    • Роль: Обработка HTTP-запросов и ответов.
    • Задачи: Валидация входных данных (DTO), маппинг на объекты, вызов сервисов, возврат данных (JSON/XML) или представлений.
    • Аннотации: @RestController, @Controller, @RequestMapping, @GetMapping, @PostMapping.
  2. Service (Слой бизнес-логики):

    • Роль: Содержит основную бизнес-логику приложения.
    • Задачи: Оркестрация вызовов репозиториев, применение бизнес-правил, управление транзакциями.
    • Аннотации: @Service, @Transactional.
  3. Repository / DAO (Слой доступа к данным):

    • Роль: Абстракция для работы с хранилищем данных (БД, кэш, внешний API).
    • Задачи: Выполнение CRUD-операций, специфических запросов.
    • Аннотации: @Repository, интерфейсы, расширяющие JpaRepository, CrudRepository.
  4. Model / Entity (Слой доменной модели):

    • Роль: Представление бизнес-сущностей и структур данных.
    • Задачи: Описание объектов, их связей и правил валидации.
    • Аннотации: @Entity, @Table, @Id, @Column.

Пример кода, демонстрирующий взаимодействие слоев:

// 1. МОДЕЛЬ (Entity)
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String email;
    // Геттеры, сеттеры, конструкторы
}

// 2. РЕПОЗИТОРИЙ (Repository)
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByEmail(String email);
}

// 3. СЕРВИС (Service)
@Service
@Transactional
public class UserService {
    @Autowired
    private UserRepository userRepository;

    public User createUser(String username, String email) {
        if (userRepository.findByEmail(email).isPresent()) {
            throw new IllegalArgumentException("Email already exists");
        }
        User newUser = new User(username, email);
        return userRepository.save(newUser); // Бизнес-логика + транзакция
    }
}

// 4. КОНТРОЛЛЕР (Controller)
@RestController
@RequestMapping("/api/users")
public class UserController {
    @Autowired
    private UserService userService;

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public User createUser(@RequestBody @Valid UserCreateRequest request) {
        // Валидация DTO происходит автоматически через @Valid
        return userService.createUser(request.getUsername(), request.getEmail());
    }
}

Преимущества такого разделения:

  • Слабая связанность (Loose Coupling): Слои общаются через четкие интерфейсы. Можно заменить реализацию одного слоя (например, БД), не затрагивая другие.
  • Простота тестирования: Сервисы можно тестировать с моками репозиториев, контроллеры — с моками сервисов.
  • Единая ответственность (Single Responsibility): Каждый класс решает одну задачу.
  • Безопасность: Бизнес-логика изолирована от веб-слоя, контроллеры остаются «тонкими».