Модуль 1С-Битрикс часто начинается с пары обработчиков и административной страницы. Затем появляются платежи, агенты, журнал, публичные компоненты и интеграции. Если все сценарии живут в entry point-файлах и статических методах, любое изменение затрагивает несколько подсистем, а unit-тесты требуют установленного ядра.
Ниже — подход, использованный в zr.paidaccess. Он подходит не только для биллинга: те же правила полезны в интеграционных и B2B-модулях.
Слои важнее каталогов
Сначала нужно определить ответственность и разрешённые зависимости:
admin pages / components / tools
-> application services
-> domain services
-> repositories / D7 ORM
-> gateway contractsEntry point проверяет права и запрос, вызывает сервис и формирует ответ. Он не рассчитывает тариф и не меняет несколько таблиц вручную. Доменный сервис реализует сценарий, а детали банка или другого API находятся за контрактом gateway.
Например, адаптер банка вправе проверить подпись webhook и вернуть нормализованный DTO. Но открывать доступ, активировать подписку и писать в ledger должен сервис модуля. Тогда замена банка не меняет правила доступа.
Практичная структура модуля
local/modules/vendor.module/
├── admin/ # тонкие страницы админки
├── install/ # установка, миграции, компоненты
├── lib/
│ ├── Admin/ # orchestration административных сценариев
│ ├── Domain/ # предметные сервисы
│ ├── Gateway/ # контракты, DTO и провайдеры
│ ├── Public/ # presenters и view services
│ ├── Repository/ # работа с хранением
│ └── Tables/ # D7 DataManager
├── tests/
├── tools/ # тонкие webhook и CLI entry points
├── autoload.production.map.php
└── include.phpНазвание каталогов можно изменить, но их смысл должен оставаться однозначным. DataManager описывает поля и связи таблицы, а не содержит биллинг. Admin не становится общим слоем для публичных компонентов. Utility не превращается в склад бизнес-методов.
В zr.paidaccess отдельно выделены доступ, подписка, платежи, фонд и документы. Платёжный слой зависит от контрактов gateway, но не от конкретного T-Bank-провайдера. Публичный namespace называется PublicUi, потому что public — ключевое слово PHP.
D7 ORM как инфраструктура
Класс таблицы должен описывать хранение:
final class PaymentTable extends \Bitrix\Main\ORM\Data\DataManager
{
public static function getTableName(): string
{
return 'vendor_module_payment';
}
public static function getMap(): array
{
return [
(new \Bitrix\Main\ORM\Fields\IntegerField('ID'))
->configurePrimary()
->configureAutocomplete(),
new \Bitrix\Main\ORM\Fields\StringField('STATUS'),
];
}
}Запросы удобно скрывать за репозиторием. Это уменьшает число мест, где код зависит от особенностей D7, и позволяет тестировать доменные сервисы с in-memory реализацией.
Production autoload без сюрпризов
В Битрикс нельзя полагаться только на Composer dev-autoload: модуль должен работать после копирования на сайт. В zr.paidaccess единым источником служит autoload.production.map.php, который подключает include.php:
$classes = require __DIR__ . '/autoload.production.map.php';
\Bitrix\Main\Loader::registerAutoLoadClasses('vendor.module', $classes);Каждый production-класс должен присутствовать в карте и указывать на существующий файл. Для расширяемых провайдеров допустим отдельный loader по строгому соглашению имён, но механизм обнаружения также нужно тестировать.
Architecture test может пройти по карте, проверить файлы и сопоставить namespace с путём. Это обнаруживает забытый класс и ошибку в регистре имени до развёртывания на Linux.
Набор правил зависимостей
- домен не импортирует
Admin, шаблоны и HTTP entry points; - публичный UI не зависит от административного UI;
- общий платёжный слой не импортирует конкретный provider;
- provider не меняет подписку, доступ и фонд напрямую;
- в
DataManagerнет бизнес-сценариев; - установщик идемпотентен и безопасен при повторном запуске;
- новый production-класс добавляется в autoload и сопровождается тестом.
Правила стоит хранить рядом с кодом в STRUCTURE.md и BOUNDARIES.md. Если нужен новый слой, сначала обновляется документ, затем реализация. Так в lib/ не появляются случайные каталоги с неясной ответственностью.
Порядок разработки нового сценария
- Описать сценарий языком предметной области.
- Выделить входные DTO и результат.
- Создать сервис без HTTP и HTML.
- Скрыть D7-запросы и внешние API за зависимостями.
- Добавить unit-тест сценария.
- Подключить сервис к компоненту, событию или endpoint.
- Обновить production map и архитектурный тест.
Практический вывод
Хорошая D7-архитектура не требует отдельного фреймворка поверх Битрикс. Достаточно направить зависимости от UI к сценариям, а от сценариев — к контрактам хранения и внешних систем. D7 ORM при этом остаётся адаптером базы, а не центром предметной модели.
Если существующий модуль уже трудно развивать, безопаснее выделять границы по одному сценарию, не переписывая всё сразу. Для аудита и такой поэтапной переработки подходит доработка проекта на 1С-Битрикс.