Разработка архитектуры и составление технического задания — это, пожалуй, самый сложный этап работ во всем процессе. Особенно важным этот этап становится при разработке особо крупных и сложных проектов, т.к. на нем закладываются базовые принципы взаимодействия подмодулей системы, гибкость и расширяемость, структуры данных; если что-то на этом этапе не додумать и не доработать, то в будущем это может аукнуться серьезными переработками и массовыми вливаниями средств.
Но, у нас фан-проект 🙂 сурового энтерпрайза тут нет и не предвидится. Да и команды разработки у нас нет, у нас всего один человек в штате 🙂 Так и хочется сказать, что «мы забьем на этот этап», но нет, все же надо заложить какой-то фундамент, чтобы потом от него уже строить код.
Сперва — очевидное.
Раз уж мы разрабатываем модуль для 1С-Битрикс, да который еще и на маркетплейсе будет размещаться, значит мы волей-неволей должны реализовывать ту структуру модуля, которую нам навязывает Битрикс. А значит мы должны построить файловую структуру модуля, описанную в документации, и реализовать инсталлятор/деинсталлятор средствами платформы.
Модуль «стороннего разработчика» по правилам cms должен иметь вендорный префикс. В нашем случае это будет maximaster (по названию компании, в которой я работаю)
Кроме этого, модуль должен иметь собственное название. Один товарищ как-то ляпнул:
В программировании есть две сложных вещи: инвалидация кэша и именование.
Выбрать хорошее название для такого модуля — реально сложно. Можно не заморачиваться и назвать модуль, основываясь на его функционале — coupon_generator или coupon_extension. Но я такие сухие названия не люблю, люблю что-нибудь прикольное и забавное.
А еще меня забавляют панды 🙂 Кого не забавляют панды?
Получается, с одной стороны у нас есть сухие и всем надоевшие купоны, а с другой — милые и смешные панды. Тупо мешаем два слова и получается крутой бренд — CouPanda! И на русском забавно — КуПанда, сразу можно много значений придать 🙂 Пускай будет купанда, а потом может и переиграем, но мне уже нравится 🙂 Логотипчик сам собой напрашивается ))
Казалось бы, нахрена нам название в архитектуре? А все просто — все названия классов нужно строить от вендорного префикса. Все пространства имен должны называться с вендора и имени библиотеки (как в битриксе, так и вообще в принципе), поэтому выбранные слова будут сопровождать нас везде.
Обозначаем базовый вендорный префикс для всех пространств имен — \Maximaster.
Неймспейс имени модуля — \Coupanda.
Значит имена всех классов должны начинаться с \Maximaster\Coupanda 🙂 все просто
Сам модуль с точки зрения битрикса будет именоваться maximaster.coupanda. Директорию с модулем здесь и далее я буду называть сокращенно %module_root%. По правилам Битрикса эта директория может находиться как в /bitrix/modules, так и в /local/modules (после установки из маркетплейса тут её быть физически не может, но мы в теории можем туда руками его залить). Поэтому важно, чтобы на работу модуля не влияло его местоположение в системе.
Погнали все это дело формализовывать.
Схема БД
Внизу поста добавил небольшой UPD к ниже написанному.
Данный модуль будет расширять существующие возможности системы, так что определенная структура таблиц для хранения купонов, пулов с ними у нас уже есть. Однако не все так радужно, как хотелось бы. Данный модуль будет давать возможность генерировать купоны, и по хорошему, надо бы каждый запуск процесса генерации запоминать в БД, чтобы была возможность обратиться к истории. Что я хочу хранить:
- id, автоинкремент
- дата запуска процесса, datetime not null
- дата окончания генерации, datetime null (процесс может не завершиться)
- настройки процесса (выбранные настройки). Наверно пока text, хранить будем в json (да да, я слышал про нормализацию)
- статистика и прогресс (количество обработанных купонов, успешных, неуспешных)
Эти данные буду хранить в таблице maximaster_coupanda_process. Называю я её так, потому что в будущем еще хочу в этой же таблице хранить данные не только о процессах генерации, но и импорта, а может быть потом и о еще каких-нибудь процессах обработки купонов.
Также есть еще один важный момент — нужно каждый сгенерированный купон привязывать к записи из таблицы процессов. Но вот засада — в битриксе нельзя расширять таблицы уже существующих сущностей, при проверке система будет ругаться на то, что описание таблицы в модуле не соответствует структуре таблицы в БД. Поэтому придется для хранения связи между купоном и процессом генерации использовать промежуточную таблицу. Это не очень-то хорошо, и не удобно совсем, но мы сознательно идем на этот шаг, потому что система не предоставляет нам выбора. Благо в битриксе существует возможность для любой сущности создавать произвольные поля, и битрикс сам будет заботиться о связи таблиц при построении запросов. Воспользуемся этой возможностью, и добавим к сущности купона свойство UF_COUPANDA_PID (идентификатор процесса). Код свойства именно такой, т.к. битрикс обязывает ставить префикс UF_ и не дает делать коды длиннее 20 символов.
Для работы с этой таблицей нужно создать ORM сущность — ProcessTable. Данный класс должен содержать описание всех полей таблицы, логику для валидации и подготовки данных при работе с БД.
При работе с купонами нам потребуется расширенная ORM сущность купона. Нужно создать класс DiscountCoupon, наследующийся от \Bitrix\Sale\Internals\DiscountCouponTable и добавляющий в него привязку к новому UF_ свойству.
Набор подсистем
Инсталлятор/деинсталлятор
Как я писал выше — это подсистема, необходимость которой определяет битрикс для модуля. Это простой класс, наследник от \CModule. Имя класса тоже строго определено правилами битрикса, а значит класс должен именоваться \maximaster_coupanda, и должен находиться в файле %module_root%/install/index.php
Интерфейс работы с этим классом тоже заранее определен битриксом (интерфейс не в терминах php, а как набор соглашений), поэтому описывать его не буду.
Печальный факт. Вся эта модульная система тянется с момента зарождения этой модульной системы, и рефакторить её пока не хотят, поэтому тут будет много анти-паттернов. Но куда деваться … мы же бабло пришли зарабатывать, а не ныть о том, как все плохо в битриксе 🙂
Обработчики событий
В Битриксе есть механизм, который позволяет влиять на ход работы других модулей, все его знают — это обработчики событий. В моем модуле будет всего один обработчик:
- Регистрация в меню — OnBuildGlobalMenu. По заданию, нам необходимо встроить собственные пункты меню в уже существующее меню модуля «sale», поэтому будем использовать данный обработчик
Менеджмент ограничений
Мы изначально задали набор ограничений (зависимость от версии модулей, зависимость от версии php и зависимость от наличия других модулей). В битриксе есть класс \Bitrix\Main\ModuleManager, который умеет следить за зависимостью от модулей. Но у него есть пара недостатков — он состоит из статических методов и он final. Поэтому мы создадим небольшую обертку над ним. Зачем? Для того, чтобы инкапсулировать зависимость от ModuleManager в одном месте. Для нашего класса, проверяющего ограничения, мы зададим интерфейс, и будем работать только с ним. Так, все остальные классы, работающие с классом ограничений, всегда смогут опираться на интерфейс при работе с этим классом, и не будут зависеть от ModuleManager. С ростом версий битрикса класс ModuleManager может измениться (и он скорее всего изменится, либо появится новый, более эффективный метод проверки зависимостей), и тогда мы сможем поменять код только в одном месте, чтобы изменить всю систему проверки зависимостей. И наоборот, если нам надо будет в нашем модуле поддержать более древние версии битры, где еще не было ModuleManager, мы сможем это сделать в одном классе.
По большому счету, нужно лишь проверить, совместим ли наш модуль с действующей установкой битрикс, а также иметь возможность узнать причины несовместимостей, если таковые будут.
1 2 3 4 5 6 7 |
<?php interface CompabilityCheckerInterface { public function isCompatible(); public function getCompabilityInfo(); } |
Внутреннюю кухню этого класса описывать не буду, оставлю на откуп «разработчику» 🙂
Генератор последовательностей
Каждый промокод будет состоять из нескольких частей, и в каждом промокоде может быть генерируемая часть. Пример промокода:
PROMO-ASJ&D173!-CODE
Здесь видно, что центральная часть между символами «-» является генерируемой автоматически. На основании этого можно сформулировать частную подзадачу.
Генератор последовательностей должен уметь:
- генерировать строковые последовательности заданной длины
- генерировать заданное количество строковых последовательностей
- генерировать последовательность на основании заданной коллекции символов
- просчитать количество возможных комбинаций
- генерировать строковые последовательности на основании шаблонной строки
На последнем пункте я хотел бы поподробнее остановиться. Мне хочется добавить сразу некий запас гибкости модулю, и не просто генерировать последовательность из набора, а давать возможность влиять на эту последовательность. Поэтому я хочу добавить микроскопический «шаблонный» язык. Хотя это будет не язык, а простая автозамена символов:
- # — заменяем на цифру от 0 до 9
- @ — заменяем на букву русского алфавита
- $ — заменяем на букву английского алфавита
- * — заменяем на любой символ из заданных последовательностей
Я сознательно пока резервирую эти символы под свои нужды. Чуть позже сделаю какой-нибудь парсер шаблона и может быть более продвинутый язык шаблонов с возможностью управления через интерфейс битрикса, но пока так.
Скорее всего, благодаря этому шаблонному языку, можно будет избавиться от указания префикса и суффикса при генерации промокода, т.к. их можно будет задать непосредственно в шаблоне. Единственный минус тут — это отсутствие возможности задать переменную длину для генерируемой части купона, но это не такое уж важное ограничение и наверно пока очень редкий кейс. Да и при желании можно будет прикрутить и его.
Основываясь на этих рассуждениях становится ясно, что нам понадобятся:
- классы, которые описывают последовательности разных символов
- класс, который описывает шаблон генерируемой части и соответствие между символами шаблона и последовательностями
- класс, который будет заниматься генерацией последовательностей из шаблонов, он же и будет нашим генератором купонов
- класс, который будет отвечать за настройку генерации битриксовых купонов
- класс, который будет выполнять генерацию в соответствии с настройками и сохранение купонов в БД
- ну и для всего этого нужна будет коллекция интерфейсов, исключений
Итак:
1 2 3 4 5 6 7 8 9 |
<?php interface SymbolsCollectionInterface { public function getOne($number); public function getRandomOne(); public function getCount(); public function getSymbols(); } |
Этот интерфейс будут реализовывать классы:
- SymbolsCollection — базовый класс
- DigitsCollection — класс с коллекцией цифр
- SpecialCharsCollection — класс со спец. символами
- EnglishLettersUppercaseCollection — класс с английскими буквами в верхнем регистре
- EnglishLettersLowercaseCollection — в нижнем регистре
- RussianLettersUppercaseCollection и RussianLettersLowercaseCollection — то же самое для русских символов
- UnionCollection — класс, который позволит объединить несколько последовательностей в одну
1 2 3 4 5 6 7 8 9 10 |
<?php interface SequenceTemplateInterface { public function setTemplate($template); public function setPlaceholder($placeholder, array $collections); public function addPlaceholder($placeholder, SymbolsCollectionInterface $collection); public function removePlaceholder($placeholder); public function getPlaceholders(); } |
Пока что нам понадобится только одна частная реализация этого интерфейса — SequenceTemplate
1 2 3 4 5 6 7 8 9 10 11 12 |
interface SequenceGeneratorInterface { public function setTemplate(SequenceTemplate $template); public function generateOne(); public function generateUniqueOne(); public function generateSeveral($count); public function generateUniqueSeveral($count); public function getGeneratedCount(); public function calculateCombinationsCount(); public function setPrefix($prefix); public function setPostfix($postfix); } |
Тут тоже пока будет достаточно одной реализации — SequenceGenerator
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<?php interface CouponGeneratorConfigurationInterface { public function setCodeTemplate(SequenceTemplateInterface $template); public function setDiscountId($discountId); public function setActiveFrom(\DateTime $activeFrom = null); public function setActiveTo(\DateTime $activeTo = null); public function setUserId($userId = null); public function setActiveFlag($active = true); public function setType($type = null); public function setMaxUse($maxUse = null); public function setDescription($description = null); } |
У данного интерфейса также будет всего одна реализация — CouponGeneratorConfiguration.
1 2 3 4 5 6 7 |
<?php interface CouponGeneratorInterface { public function setConfiguration(CouponGeneratorConfigurationInterface $config); public function generate(); } |
Ну и одна реализация генератора — CouponGenerator.
Страница генератора
На начальном этапе у нас будет всего один контроллер с тремя разными экранами, который будет отвечать за функциональность всего модуля. Данный контроллер имеет смысл реализовать с помощью обычного компонента битрикс. Данный компонент должен иметь возможность работы в трех режимах (все в соответствии с макетами интерфейса из прошлой статьи):
- Настройка генерации
- Процесс генерации
- Отчет о генерации
Естественно компонент должен иметь возможность работы в режиме ajax, должен быть оформлен в виде класса.
Логирование
Нельзя забывать о логировании, т.к. в случае возникновения проблемных ситуаций нужно будет какое-то основание для поиска ошибки. В идеале, данная система должна следовать PSR-3 рекомендациям. Но в битриксе нет его нативной поддержки, а подключить наикрутейший monolog в битрикс можно только с помощью composer, который недоступен в коробке при инсталляции модулей. Поэтому, думаю, будет достаточно подвязаться к существующей системе логирования в битрикс.
Система прав доступа
Нужно реализовать механизм прав доступа к модулю на основе битриксовых возможностей. Нужно добавить всего 2 вида прав — запрет на работу с этим модулем, и разрешение на осуществление всех операций этого модуля.
Summary
Вот такое техническое заданьице получилось на такой крошечный модуль, несмотря даже на очень низкий уровень детализации. Но я думаю, что этого уровня детализации будет вполне достаточно среднему разработчику для того, чтобы начать непосредственно реализацию этого модуля.
Хочу заметить, что я пока еще не написал ни единой строчки кода этого модуля, но уже около половины дела сделано. Остается дело за малым — открыть любимую IDE и набросать функционал, подумав немного о частных проблемах реализации. Этим и займемся в следующих статьях.
UPD. Обманул я вас немного по части структуры БД. Проверил опытным путем, добавил в таблицу b_sale_discount_coupon свою колонку, после чего прогнал полную проверку системы. Битрикс даже не шелохнулся. Поэтому буду добавлять колонку и строить функционал модуля исходя из этого. И уже если на проверке завернут, буду рефакторить.