Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Hexagonal Architecture: архитектура шестиугольник
Hexagonal Architecture (или Ports & Adapters) — это архитектурный паттерн, придуманный Алистером Кокберном, который изолирует бизнес-логику приложения от внешних зависимостей (БД, API, UI) через порты и адаптеры.
Основная идея
Шестиугольник символизирует ядро приложения (business logic), окружённое портами (интерфейсы), к которым подключаются адаптеры (реализации для конкретных технологий).
┌─────────────────────────────┐
│ │
│ Business Logic │
│ (Domain Objects) │
│ │
┌─────────────────────────────────────┐
│ Ports (Interfaces) │
└─────────────────────────────────────┘
↗ ↗ ↗ ↘ ↘ ↘
REST gRPC Queue Database Email Cache
Adapter Adapter Adapter Adapter Adapter Adapter
Порты и Адаптеры
Порты — это интерфейсы (контракты) между ядром и внешним миром:
// INPUT PORT (входящий порт)
// Приложение ПОЛУЧАЕТ данные извне
public interface CreateUserPort {
User createUser(String name, String email);
}
// OUTPUT PORT (исходящий порт)
// Приложение ОТПРАВЛЯЕТ данные наружу
public interface UserRepositoryPort {
void save(User user);
Optional<User> findById(Long id);
}
public interface EmailSenderPort {
void sendWelcomeEmail(String email);
}
Адаптеры — это реализации для конкретных технологий:
// INPUT ADAPTER: REST контроллер
@RestController
@RequestMapping("/api/users")
public class UserRestAdapter {
private final CreateUserPort createUserPort;
@PostMapping
public ResponseEntity<UserDTO> create(@RequestBody CreateUserRequest request) {
User user = createUserPort.createUser(request.getName(), request.getEmail());
return ResponseEntity.ok(new UserDTO(user));
}
}
// INPUT ADAPTER: Kafka слушатель
@Component
public class UserKafkaAdapter {
private final CreateUserPort createUserPort;
@KafkaListener(topics = "user-events")
public void handleUserEvent(UserEvent event) {
createUserPort.createUser(event.getName(), event.getEmail());
}
}
// OUTPUT ADAPTER: PostgreSQL репозиторий
@Repository
public class UserDatabaseAdapter implements UserRepositoryPort {
@Autowired
private JpaUserRepository jpaRepo;
@Override
public void save(User user) {
UserEntity entity = new UserEntity();
entity.setName(user.getName());
entity.setEmail(user.getEmail());
jpaRepo.save(entity);
}
@Override
public Optional<User> findById(Long id) {
return jpaRepo.findById(id)
.map(this::toDomain);
}
private User toDomain(UserEntity entity) {
return new User(entity.getId(), entity.getName(), entity.getEmail());
}
}
// OUTPUT ADAPTER: Email сервис
@Component
public class EmailSenderAdapter implements EmailSenderPort {
@Autowired
private JavaMailSender mailSender;
@Override
public void sendWelcomeEmail(String email) {
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(email);
message.setSubject("Welcome!");
message.setText("Thanks for joining!");
mailSender.send(message);
}
}
Ядро приложения (Business Logic)
// Бизнес-логика в ЦЕНТРЕ
// Не знает о REST, БД, Email
public class CreateUserUseCase implements CreateUserPort {
private final UserRepositoryPort userRepository;
private final EmailSenderPort emailSender;
public CreateUserUseCase(UserRepositoryPort userRepository,
EmailSenderPort emailSender) {
this.userRepository = userRepository;
this.emailSender = emailSender;
}
@Override
public User createUser(String name, String email) {
// Валидация
if (name == null || name.isEmpty()) {
throw new InvalidNameException();
}
if (!isValidEmail(email)) {
throw new InvalidEmailException();
}
// Бизнес-логика
User user = new User(null, name, email);
// Используем порты (не знаем, как реально работают!)
userRepository.save(user);
emailSender.sendWelcomeEmail(email);
return user;
}
private boolean isValidEmail(String email) {
return email.contains("@") && email.contains(".");
}
}
Структура проекта
src/main/java/com/example/
├── application/
│ └── CreateUserUseCase.java ← ЯДРО
├── domain/
│ └── User.java ← Entity
├── ports/
│ ├── input/
│ │ └── CreateUserPort.java ← Input port
│ └── output/
│ ├── UserRepositoryPort.java ← Output port
│ └── EmailSenderPort.java ← Output port
└── adapters/
├── input/
│ ├── rest/
│ │ └── UserRestAdapter.java ← REST adapter
│ └── kafka/
│ └── UserKafkaAdapter.java ← Kafka adapter
└── output/
├── persistence/
│ └── UserDatabaseAdapter.java ← DB adapter
└── notification/
└── EmailSenderAdapter.java ← Email adapter
Преимущества
// 1. Легко тестировать - мокируем порты
@Test
public void testCreateUser() {
UserRepositoryPort mockRepo = mock(UserRepositoryPort.class);
EmailSenderPort mockEmail = mock(EmailSenderPort.class);
CreateUserUseCase useCase = new CreateUserUseCase(mockRepo, mockEmail);
User user = useCase.createUser("John", "john@example.com");
verify(mockRepo).save(user);
verify(mockEmail).sendWelcomeEmail("john@example.com");
}
// 2. Легко менять технологии
// Была PostgreSQL? Переходим на MongoDB
// Был REST? Добавляем GraphQL
// Бизнес-логика не меняется!
// 3. Параллельная разработка
// Один разработчик: adapters/input/rest
// Другой: adapters/output/persistence
// Третий: application (бизнес-логика)
Hexagon vs Clean Architecture
Сходства:
- Обе отделяют бизнес-логику от деталей
- Обе используют интерфейсы
- Обе управляют зависимостями
Различия:
Clean Architecture: Hexagonal Architecture:
┌─────────────────────┐ ┌──────────────────┐
│ Frameworks │ │ Adapters │
├─────────────────────┤ ├──────────────────┤
│ Interface Adapters│ │ Ports │
├─────────────────────┤ ├──────────────────┤
│ Use Cases │ │ Business Logic │
├─────────────────────┤ └──────────────────┘
│ Entities │
└─────────────────────┘
4 слоя (концентрические) 2 слоя (core + adapters)
Больше формальная Более гибкая
Реальный пример: платёжная система
// INPUT PORT
public interface ProcessPaymentPort {
PaymentResult processPayment(PaymentRequest request);
}
// OUTPUT PORTS
public interface PaymentGatewayPort {
void chargeCard(String cardNumber, BigDecimal amount);
}
public interface OrderRepositoryPort {
void updateOrderStatus(Long orderId, OrderStatus status);
}
// BUSINESS LOGIC
public class ProcessPaymentUseCase implements ProcessPaymentPort {
private final PaymentGatewayPort gateway;
private final OrderRepositoryPort repository;
@Override
public PaymentResult processPayment(PaymentRequest request) {
try {
// Бизнес-логика
gateway.chargeCard(request.getCardNumber(), request.getAmount());
repository.updateOrderStatus(request.getOrderId(), OrderStatus.PAID);
return PaymentResult.success();
} catch (PaymentException e) {
repository.updateOrderStatus(request.getOrderId(), OrderStatus.FAILED);
return PaymentResult.failure(e.getMessage());
}
}
}
// INPUT ADAPTER: REST
@RestController
public class PaymentRestAdapter {
private final ProcessPaymentPort processPayment;
@PostMapping("/api/payments")
public ResponseEntity<PaymentResult> pay(@RequestBody PaymentRequest request) {
return ResponseEntity.ok(processPayment.processPayment(request));
}
}
// OUTPUT ADAPTER: Stripe
public class StripePaymentAdapter implements PaymentGatewayPort {
@Override
public void chargeCard(String cardNumber, BigDecimal amount) {
// Интегрируемся со Stripe
}
}
// OUTPUT ADAPTER: PostgreSQL
@Repository
public class OrderDatabaseAdapter implements OrderRepositoryPort {
private final JpaOrderRepository jpaRepo;
@Override
public void updateOrderStatus(Long orderId, OrderStatus status) {
// Обновляем БД
}
}
Hexagonal Architecture идеальна для приложений, где:
- Нужна максимальная тестируемость
- Могут меняться способы интеграции (REST, GraphQL, Kafka)
- Работает много команд параллельно
- Долгосрочный проект с частыми изменениями требований