← Назад к вопросам

Что такое Hexagon?

3.0 Senior🔥 131 комментариев
#SOLID и паттерны проектирования

Комментарии (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)
  • Работает много команд параллельно
  • Долгосрочный проект с частыми изменениями требований
Что такое Hexagon? | PrepBro