Сталкивался ли с микросервисами, построенными по принципу гексагональной архитектуры?

«Сталкивался ли с микросервисами, построенными по принципу гексагональной архитектуры?» — вопрос из категории Архитектура, который задают на 24% собеседований PHP Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Да, работал над несколькими проектами, где микросервисы были построены по принципу гексагональной архитектуры (Ports & Adapters). Основная цель — изолировать бизнес-логику (ядро) от внешних зависимостей (БД, API, фреймворки).

Реализация в PHP-микросервисе (упрощенная структура):

src/
├── Domain/              # Ядро
│   ├── Model/
│   │   └── Order.php    # Сущность с бизнес-правилами
│   └── Repository/      
│       └── OrderRepositoryInterface.php # Порт
├── Application/         # Сценарии использования
│   └── Service/
│       └── OrderService.php
└── Infrastructure/      # Адаптеры
    ├── Persistence/
    │   └── DoctrineOrderRepository.php # Адаптер для Doctrine
    └── Api/
        └── PaymentGatewayAdapter.php   # Адаптер для внешнего API

Конкретный пример порта и адаптера для работы с платежами:

// Domain/Port/PaymentServiceInterface.php (Порт)
interface PaymentServiceInterface {
    public function charge(Order $order, Money $amount): TransactionId;
}

// Infrastructure/Adapter/StripePaymentAdapter.php (Адаптер)
class StripePaymentAdapter implements PaymentServiceInterface {
    private StripeClient $stripe;

    public function charge(Order $order, Money $amount): TransactionId {
        $charge = $this->stripe->charges->create([
            'amount' => $amount->getCents(),
            'currency' => $amount->getCurrency(),
            'source' => $order->getPaymentToken(),
            'description' => sprintf('Order #%s', $order->getId())
        ]);

        return new TransactionId($charge->id);
    }
}

// Application/Service/OrderProcessor.php (Сценарий использования)
class OrderProcessor {
    private PaymentServiceInterface $paymentService;

    public function process(Order $order): void {
        // Бизнес-логика не зависит от Stripe
        $transactionId = $this->paymentService->charge($order, $order->getTotal());
        $order->markAsPaid($transactionId);
    }
}

Преимущества в контексте микросервисов:

  • Тестируемость: Ядро можно тестировать юнит-тестами с моками адаптеров.
  • Замена инфраструктуры: Переход с Stripe на PayPal потребует только нового адаптера, без изменений в бизнес-логике.
  • Чистые границы: Четкое разделение ответственности между командами, работающими над разными сервисами.

Сложности:

  • Большой объем шаблонного кода для простых CRUD-операций.
  • Требует высокой дисциплины от команды, чтобы не допускать "утечек" инфраструктуры в доменный слой.