Какая архитектура была на проекте?
Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Архитектура проекта «Монолитный E-коммерс с элементами модульности и событийной шины»
На последнем крупном проекте мы использовали условно-монолитную архитектуру с чётким разделением на слои и внедрением шаблонов DDD (Domain-Driven Design) в ключевых бизнес-доменах. Система была построена на Laravel, но с серьёзными кастомными наработками, выходящими за рамки стандартного MVC фреймворка.
Ключевые принципы и слои
1. Доменный слой (Domain Layer):
- Содержал сущности (Entities), объекты-значения (Value Objects) и агрегаты (Aggregates) для ядра бизнес-логики:
Заказ (Order),Корзина (Cart),Товар (ProductSku),Покупатель (Customer). - Бизнес-правила (например, проверка лимита товара на складе перед резервированием) были инкапсулированы внутри методов сущностей, а не вынесены в сервисы.
<?php
namespace Domain\Order;
class Order
{
private OrderStatus $status;
/** @var OrderLine[] */
private array $lines;
public function confirm(): void
{
if (!$this->canBeConfirmed()) {
throw new OrderStateException('Order cannot be confirmed');
}
$this->status = OrderStatus::CONFIRMED;
$this->raise(new OrderConfirmedEvent($this->id));
}
private function canBeConfirmed(): bool
{
return $this->status === OrderStatus::PENDING;
}
}
2. Слой приложения (Application Layer):
- Содержал Сценарии (Use Cases) или Сервисы приложения, которые координировали работу доменных объектов и инфраструктуры.
- Каждый сценарий отвечал за одну бизнес-транзакцию (например,
ConfirmOrderHandler). Здесь использовался паттерн Command/Handler в связке с Mediator (Laravel Bus).
<?php
namespace App\Order\Commands;
class ConfirmOrderHandler
{
public function __construct(
private OrderRepositoryInterface $repository,
private EventDispatcherInterface $dispatcher
) {}
public function handle(ConfirmOrderCommand $command): void
{
$order = $this->repository->findOrFail($command->orderId);
$order->confirm(); // Доменная логика
$this->repository->save($order);
// Отправка событий уровня приложения (не домена)
$this->dispatcher->dispatch(new OrderWasConfirmed($order));
}
}
3. Инфраструктурный слой (Infrastructure Layer):
- Реализовывал абстракции, определённые во внутренних слоях: репозитории (
EloquentOrderRepository), клиенты внешних API (PaymentGatewayClient), отправка email. - Это место, где фреймворк (Laravel) и сторонние библиотеки интегрировались в наше приложение.
4. Слой представления (Interface/Adapter Layer):
- Включал HTTP-контроллеры, консольные команды и работников очередей (Queue Workers).
- Их задача — преобразовать внешние запросы (HTTP, CLI) в вызовы сценариев приложения и вернуть ответ. Контроллеры были максимально «тонкими».
Дополнительные архитектурные компоненты
- Событийная шина (Event Bus): Широко использовалась как на уровне домена (Domain Events), так и на уровне приложения. Это позволяло организовать слабое связывание (loose coupling) между модулями. Например, событие
OrderConfirmedEventмогло триггерить отправку чека, обновление аналитики и списание бонусов через отдельные слушатели. - Модульная структура: Хотя код физически находился в одном репозитории, логически он был разделён на модули/домены (
Order,Catalog,Payment,Notification). Каждый модуль имел свою внутреннюю структуру слоёв, что облегчало командную работу и потенциальное выделение в микросервисы в будущем. - Порты и Адаптеры (Гексагональная архитектура): Ключевые зависимости (доступ к данным, платежные шлюзы) описывались через интерфейсы в доменном/прикладном слое, а их реализации «внедрялись» из инфраструктурного слоя через Dependency Injection.
Преимущества выбранного подхода
- Сосредоточенность на бизнес-логике: Домен был изолирован от фреймворка и инфраструктуры, что делало его тестируемым и устойчивым к внешним изменениям.
- Гибкость и сопровождаемость: Чёткие границы ответственности. Изменение, например, платёжного провайдера затрагивало только конкретный адаптер в инфраструктурном слое.
- Масштабируемость: Событийная модель и модульность позволили системе гибко расти. Нагрузочные части (обработка изображений, отправка почты) легко выносились в асинхронные очереди.
- Обучаемость: Архитектура служила «картой» для новых разработчиков, показывая, куда помещать новый код.
Выводы и рефлексия
Эта архитектура потребовала больше усилий на старте по сравнению с классическим Laravel MVC, но окупилась на стадии активного роста и усложнения продукта. Она не догма, а набор инструментов. Для административных CRUD-панелей мы иногда отступали от строгих правил в пользу скорости. Главный урок — архитектура должна служить бизнесу, а не наоборот, и её строгость должна быть пропорциональна сложности и критичности конкретного домена.