Ответ
Я разделяю приложение на слои по принципу единой ответственности и направлению зависимостей. Классическая многослойная архитектура (n-layer) включает:
- Presentation Layer (UI/API): Отвечает за взаимодействие с пользователем или внешними системами. Это могут быть REST-контроллеры, GraphQL резолверы, CLI-команды или компоненты фронтенда.
- Application Layer (Service): Содержит сценарии использования (use cases) приложения. Координирует работу доменного слоя для выполнения конкретных задач пользователя. Здесь живет бизнес-логика приложения (application business logic), но не бизнес-правила предметной области.
- Domain Layer (Business): Ядро системы. Содержит сущности (Entities), объекты-значения (Value Objects), доменные сервисы (Domain Services) и бизнес-правила (Business Rules). Этот слой не зависит от внешнего мира (баз данных, фреймворков, UI).
- Infrastructure Layer: Реализует технические детали: доступ к данным (репозитории), отправку email, вызов внешних API, работу с файловой системой. Этот слой зависит от доменного слоя.
Принцип зависимостей: Зависимости направлены внутрь. Presentation → Application → Domain. Infrastructure реализует интерфейсы, определенные в Domain/Application.
Пример для функции регистрации пользователя:
// Domain Layer: Сущность и бизнес-правило
public class User {
private UserId id;
private Email email;
private PasswordHash passwordHash;
public static User register(Email email, PlainPassword password) {
// Доменное правило: email должен быть уникален (проверяется на уровне приложения)
// Доменная логика: хеширование пароля
return new User(UserId.generate(), email, PasswordHash.create(password));
}
}
// Application Layer: Сценарий использования
public class RegisterUserUseCase {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
public void execute(RegisterUserCommand command) {
// 1. Проверка бизнес-правила (уникальность email) - это логика приложения
if (userRepository.existsByEmail(command.getEmail())) {
throw new EmailAlreadyExistsException();
}
// 2. Создание доменного объекта
User newUser = User.register(command.getEmail(), command.getPassword());
// 3. Сохранение через инфраструктуру
userRepository.save(newUser);
// 4. Публикация доменного события (опционально)
domainEventPublisher.publish(new UserRegisteredEvent(newUser.getId()));
}
}
// Presentation Layer (REST Controller)
@RestController
public class UserController {
@PostMapping("/users")
public ResponseEntity<UserResponse> register(@RequestBody RegisterRequest request) {
var command = new RegisterUserCommand(request.getEmail(), request.getPassword());
registerUserUseCase.execute(command);
return ResponseEntity.created(...).build();
}
}
// Infrastructure Layer: Реализация репозитория для JPA
@Repository
public class JpaUserRepository implements UserRepository {
@PersistenceContext
private EntityManager em;
@Override
public void save(User user) {
em.persist(user);
}
}
Преимущества такого разделения:
- Тестируемость: Домен и логику приложения можно тестировать изолированно, без базы данных или веб-сервера.
- Гибкость: Можно легко заменить способ хранения данных (с SQL на NoSQL) или интерфейс (с REST на gRPC), не затрагивая бизнес-логику.
- Читаемость: Четкие границы ответственности упрощают понимание кода новой командой.