Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# Onion Architecture (Луковая архитектура)
Onion Architecture — это архитектурный паттерн, предложенный Jeffrey Palermo, который организует код в слои с радиальной структурой (как слои луковицы).
Основная идея
Код делится на слои, образующие круги. Каждый внешний слой зависит только от слоёв внутри, но никогда наоборот. Внутренние слои полностью независимы от внешних.
┌─────────────────────────────┐
│ Web API / UI │ Presentation
└──────────────┬────────────────┘
▲ │
│ ▼
┌─────────────────────────────┐
│ Application Services │ Application
└──────────────┬────────────────┘
▲ │
│ ▼
┌─────────────────────────────┐
│ Domain / Business Logic │ Domain
│ (Entities, Value Objects) │ (Core - ничего не импортирует)
└─────────────────────────────┘
▲
│
┌─────────────────────────────┐
│ Infrastructure │ Infrastructure
│ (Database, Email, APIs) │
└─────────────────────────────┘
Четыре слоя Onion Architecture
1. Domain Layer (Ядро)
Самый внутренний слой — чистая бизнес-логика, полностью независим от технологий.
// Domain Entity
public class User {
private final String id;
private final String email;
private final String name;
public User(String id, String email, String name) {
this.id = id;
this.email = email;
this.name = name;
}
// Бизнес-методы
public boolean isValidEmail() {
return email.contains("@");
}
public String getFullInfo() {
return name + " (" + email + ")";
}
}
// Domain Value Object
public class Money {
private final BigDecimal amount;
private final Currency currency;
public Money(BigDecimal amount, Currency currency) {
if (amount.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException("Amount cannot be negative");
}
this.amount = amount;
this.currency = currency;
}
public Money add(Money other) {
if (!currency.equals(other.currency)) {
throw new IllegalArgumentException("Different currencies");
}
return new Money(amount.add(other.amount), currency);
}
}
// Domain Service
public interface UserRepository {
User findById(String id);
}
Правило: Domain слой НЕ импортирует ничего из других слоёв. Никаких зависимостей на фреймворки, БД, REST.
2. Application Layer
Слой бизнес-процессов и use-cases. Здесь координируется работа Domain логики.
// Use Case / Application Service
@Service
public class CreateUserService {
private final UserRepository userRepository;
private final EmailService emailService;
public CreateUserService(UserRepository userRepository, EmailService emailService) {
this.userRepository = userRepository;
this.emailService = emailService;
}
// Orchestration логика
public void execute(CreateUserCommand command) {
// Валидация
if (!isValidEmail(command.getEmail())) {
throw new InvalidEmailException();
}
// Создаём Domain Entity
User user = new User(
generateId(),
command.getEmail(),
command.getName()
);
// Используем Domain Repository
userRepository.save(user);
// Отправляем уведомление
emailService.sendWelcomeEmail(user.getEmail());
}
}
// Ports (Interfaces)
public interface UserRepository {
void save(User user);
User findById(String id);
}
public interface EmailService {
void sendWelcomeEmail(String email);
}
Правило: Application слой зависит только от Domain, не от Infrastructure.
3. Infrastructure Layer
Внешний слой реализации деталей: БД, HTTP клиенты, файловая система.
// Реализация Repository
@Repository
public class JpaUserRepository implements UserRepository {
@Autowired
private JpaUserEntity userEntity;
@Override
public void save(User user) {
UserJpaEntity entity = new UserJpaEntity();
entity.setId(user.getId());
entity.setEmail(user.getEmail());
entity.setName(user.getName());
userEntity.save(entity);
}
@Override
public User findById(String id) {
UserJpaEntity entity = userEntity.findById(id).orElseThrow();
return new User(entity.getId(), entity.getEmail(), entity.getName());
}
}
// Реализация Email Service
@Service
public class SmtpEmailService implements EmailService {
@Autowired
private JavaMailSender mailSender;
@Override
public void sendWelcomeEmail(String email) {
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(email);
message.setSubject("Welcome!");
message.setText("Thanks for registering!");
mailSender.send(message);
}
}
// HTTP Client
@Service
public class PaymentGatewayAdapter {
@Autowired
private RestTemplate restTemplate;
public void processPayment(Payment payment) {
// Вызов внешнего API
restTemplate.postForObject(
"https://api.payment.com/charge",
payment,
PaymentResponse.class
);
}
}
4. Presentation Layer (UI/API)
Внешний слой для взаимодействия с пользователем/системой.
// REST Controller
@RestController
@RequestMapping("/api/v1/users")
public class UserController {
@Autowired
private CreateUserService createUserService;
@PostMapping
public ResponseEntity<UserDto> createUser(@RequestBody CreateUserRequest request) {
CreateUserCommand command = new CreateUserCommand(
request.getEmail(),
request.getName()
);
createUserService.execute(command);
return ResponseEntity.status(HttpStatus.CREATED).build();
}
}
Правила Onion Architecture
-
Зависимости указывают внутрь (не наружу)
❌ Domain импортирует Application/Infrastructure ✅ Application импортирует Domain ✅ Infrastructure импортирует Domain и Application ✅ Presentation импортирует Application -
Domain Layer полностью независим (чистый код, без фреймворков)
-
Инверсия зависимостей (DIP) — используй interfaces
// Application не зависит от конкретной реализации public class CreateUserService { private final UserRepository userRepository; // Interface! public CreateUserService(UserRepository repository) { this.userRepository = repository; } } -
Ports & Adapters — Interface в Application, реализация в Infrastructure
// Ports (Application слой) public interface UserRepository { } // Определена здесь // Adapters (Infrastructure слой) public class JpaUserRepository implements UserRepository { } // Реализована здесь
Преимущества
✅ Независимость от фреймворков — Domain логика не привязана к Spring, JPA, etc
✅ Тестируемость — легко писать unit тесты для Domain
✅ Гибкость — легко заменить реализацию (БД, Email сервис)
✅ Понятность — бизнес-логика отделена от технических деталей
✅ Масштабируемость — новые слои легко добавить
Недостатки
❌ Сложность — требует больше файлов и interfaces
❌ Overhead — для простых приложений может быть оверинжиниринг
❌ Кривая обучения — нужно понимать паттерны и принципы
Сравнение с другими архитектурами
| Onion | Hexagonal | Layered | Clean | |
|---|---|---|---|---|
| Domain центральный | ✅ Да | ✅ Да | ❌ Нет | ✅ Да |
| Инверсия зависимостей | ✅ Да | ✅ Да | ❌ Нет | ✅ Да |
| Ports/Adapters | ✅ Да | ✅ Да | ❌ Нет | ✅ Да |
| Сложность | Средняя | Высокая | Низкая | Высокая |
Пример проекта
src/
├── domain/ # Core business logic
│ ├── entities/
│ │ └── User.java
│ ├── value-objects/
│ │ └── Money.java
│ └── repositories/ # Interfaces (Ports)
│ └── UserRepository.java
│
├── application/ # Use cases & services
│ ├── services/
│ │ └── CreateUserService.java
│ └── commands/
│ └── CreateUserCommand.java
│
├── infrastructure/ # Technical implementations
│ ├── persistence/
│ │ ├── JpaUserRepository.java
│ │ └── UserJpaEntity.java
│ └── external/
│ └── SmtpEmailService.java
│
└── presentation/ # API / Web
└── controllers/
└── UserController.java
Вывод
Onion Architecture — мощный паттерн для создания maintainable, testable и scalable приложений. Ядро (Domain) остаётся чистым от технических деталей, позволяя сосредоточиться на бизнес-логике. Зависимости всегда указывают внутрь, обеспечивая слабую связанность между слоями.