Ответ
Да, работал над несколькими проектами, где микросервисы были построены по принципу гексагональной архитектуры (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-операций.
- Требует высокой дисциплины от команды, чтобы не допускать "утечек" инфраструктуры в доменный слой.