#Архитектура

Советы с тегом "Архитектура"

1 совет

Паттерн Repository в Битрикс через RepositoryInterface

Паттерн Repository в Битрикс через RepositoryInterface

Разработчики Битрикс часто работают напрямую с DataManager, размазывая логику доступа к данным по всему проекту. Это создаёт жёсткую связанность кода с ORM, затрудняет тестирование и нарушает принцип единой ответственности. В модуле main появились интерфейсы RepositoryInterface и SoftDeletableRepositoryInterface, которые позволяют строить чистую архитектуру.

Интерфейс Bitrix\Main\Repository\RepositoryInterface определяет три базовых метода:

interface RepositoryInterface
{
    public function getById(mixed $id): ?EntityInterface;
    public function save(EntityInterface $entity): void;
    public function delete(mixed $id): void;
}

Для реализации репозитория сущность должна имплементировать Bitrix\Main\Entity\EntityInterface:

use Bitrix\Main\Entity\EntityInterface;

class Order implements EntityInterface
{
    public function __construct(
        private ?int $id,
        private int $userId,
        private string $status,
        private float $amount
    ) {}

    public function getId(): mixed
    {
        return $this->id;
    }

    public function setId(int $id): void
    {
        $this->id = $id;
    }

    // Геттеры и сеттеры для остальных свойств...
}

Теперь создаём репозиторий с инкапсулированной логикой работы с БД:

use Bitrix\Main\Repository\RepositoryInterface;
use Bitrix\Main\Repository\SoftDeletableRepositoryInterface;
use Bitrix\Main\Repository\Exception\PersistenceException;
use Bitrix\Main\Entity\EntityInterface;

class OrderRepository implements RepositoryInterface, SoftDeletableRepositoryInterface
{
    public function getById(mixed $id): ?Order
    {
        $row = OrderTable::getById($id)->fetch();
        if (!$row) {
            return null;
        }

        return new Order($row['ID'], $row['USER_ID'], $row['STATUS'], $row['AMOUNT']);
    }

    public function save(EntityInterface $entity): void
    {
        $data = [
            'USER_ID' => $entity->getUserId(),
            'STATUS' => $entity->getStatus(),
            'AMOUNT' => $entity->getAmount(),
        ];

        if ($entity->getId()) {
            $result = OrderTable::update($entity->getId(), $data);
        } else {
            $result = OrderTable::add($data);
            if ($result->isSuccess()) {
                $entity->setId($result->getId());
            }
        }

        if (!$result->isSuccess()) {
            throw new PersistenceException('Ошибка сохранения', errors: $result->getErrors());
        }
    }

    public function delete(mixed $id): void
    {
        $result = OrderTable::delete($id);
        if (!$result->isSuccess()) {
            throw new PersistenceException('Ошибка удаления', errors: $result->getErrors());
        }
    }

    public function softDelete(mixed $id): void
    {
        OrderTable::update($id, ['DELETED' => 'Y']);
    }
}

Использование в сервисном слое:

class OrderService
{
    public function __construct(
        private RepositoryInterface $orderRepository
    ) {}

    public function createOrder(int $userId, float $amount): Order
    {
        $order = new Order(null, $userId, 'NEW', $amount);
        $this->orderRepository->save($order);

        return $order;
    }
}

Репозиторий изолирует бизнес-логику от деталей хранения данных. Код становится тестируемым — достаточно подставить mock-репозиторий. PersistenceException предоставляет унифицированную обработку ошибок через метод getErrors().