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

Какая архитектура была на проекте?

1.3 Junior🔥 202 комментариев
#Архитектура и паттерны

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Архитектура проекта «Монолитный 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-панелей мы иногда отступали от строгих правил в пользу скорости. Главный урок — архитектура должна служить бизнесу, а не наоборот, и её строгость должна быть пропорциональна сложности и критичности конкретного домена.