Модуль для маркетплейс — от идеи до старта продаж. Часть 4 — техническое задание и архитектура

Разработка архитектуры и составление технического задания — это, пожалуй, самый сложный этап работ во всем процессе. Особенно важным этот этап становится при разработке особо крупных и сложных проектов, т.к. на нем закладываются базовые принципы взаимодействия подмодулей системы, гибкость и расширяемость, структуры данных; если что-то на этом этапе не додумать и не доработать, то в будущем это может аукнуться серьезными переработками и массовыми вливаниями средств.

Но, у нас фан-проект 🙂 сурового энтерпрайза тут нет и не предвидится. Да и команды разработки у нас нет, у нас всего один человек в штате 🙂 Так и хочется сказать, что «мы забьем на этот этап», но нет, все же надо заложить какой-то фундамент, чтобы потом от него уже строить код.

Сперва — очевидное.

Раз уж мы разрабатываем модуль для 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, мы сможем это сделать в одном классе.

По большому счету, нужно лишь проверить, совместим ли наш модуль с действующей установкой битрикс, а также иметь возможность узнать причины несовместимостей, если таковые будут.

Внутреннюю кухню этого класса описывать не буду, оставлю на откуп «разработчику» 🙂

Генератор последовательностей

Каждый промокод будет состоять из нескольких частей, и в каждом промокоде может быть генерируемая часть. Пример промокода:

PROMO-ASJ&D173!-CODE

Здесь видно, что центральная часть между символами «-» является генерируемой автоматически. На основании этого можно сформулировать частную подзадачу.

Генератор последовательностей должен уметь:

  • генерировать строковые последовательности заданной длины
  • генерировать заданное количество строковых последовательностей
  • генерировать последовательность на основании заданной коллекции символов
  • просчитать количество возможных комбинаций
  • генерировать строковые последовательности на основании шаблонной строки

На последнем пункте я хотел бы поподробнее остановиться. Мне хочется добавить сразу некий запас гибкости модулю, и не просто генерировать последовательность из набора, а давать возможность влиять на эту последовательность. Поэтому я хочу добавить микроскопический «шаблонный» язык. Хотя это будет не язык, а простая автозамена символов:

  • # — заменяем на цифру от 0 до 9
  • @ — заменяем на букву русского алфавита
  • $ — заменяем на букву английского алфавита
  • * — заменяем на любой символ из заданных последовательностей

Я сознательно пока резервирую эти символы под свои нужды. Чуть позже сделаю какой-нибудь парсер шаблона и может быть более продвинутый язык шаблонов с возможностью управления через интерфейс битрикса, но пока так.

Скорее всего, благодаря этому шаблонному языку, можно будет избавиться от указания префикса и суффикса при генерации промокода, т.к. их можно будет задать непосредственно в шаблоне. Единственный минус тут — это отсутствие возможности задать переменную длину для генерируемой части купона, но это не такое уж важное ограничение и наверно пока очень редкий кейс. Да и при желании можно будет прикрутить и его.

Основываясь на этих рассуждениях становится ясно, что нам понадобятся:

  • классы, которые описывают последовательности разных символов
  • класс, который описывает шаблон генерируемой части и соответствие между символами шаблона и последовательностями
  • класс, который будет заниматься генерацией последовательностей из шаблонов, он же и будет нашим генератором купонов
  • класс, который будет отвечать за настройку генерации битриксовых купонов
  • класс, который будет выполнять генерацию в соответствии с настройками и сохранение купонов в БД
  • ну и для всего этого нужна будет коллекция интерфейсов, исключений

Итак:

Этот интерфейс будут реализовывать классы:

  • SymbolsCollection — базовый класс
  • DigitsCollection — класс с коллекцией цифр
  • SpecialCharsCollection — класс со спец. символами
  • EnglishLettersUppercaseCollection — класс с английскими буквами в верхнем регистре
  • EnglishLettersLowercaseCollection — в нижнем регистре
  • RussianLettersUppercaseCollection и RussianLettersLowercaseCollection — то же самое для русских символов
  • UnionCollection — класс, который позволит объединить несколько последовательностей в одну

Пока что нам понадобится только одна частная реализация этого интерфейса — SequenceTemplate

Тут тоже пока будет достаточно одной реализации — SequenceGenerator

У данного интерфейса также будет всего одна реализация — CouponGeneratorConfiguration.

Ну и одна реализация генератора — CouponGenerator.

Страница генератора

На начальном этапе у нас будет всего один контроллер с тремя разными экранами, который будет отвечать за функциональность всего модуля. Данный контроллер имеет смысл реализовать с помощью обычного компонента битрикс. Данный компонент должен иметь возможность работы в трех режимах (все в соответствии с макетами интерфейса из прошлой статьи):

  • Настройка генерации
  • Процесс генерации
  • Отчет о генерации

Естественно компонент должен иметь возможность работы в режиме ajax, должен быть оформлен в виде класса.

Логирование

Нельзя забывать о логировании, т.к. в случае возникновения проблемных ситуаций нужно будет какое-то основание для поиска ошибки. В идеале, данная система должна следовать PSR-3 рекомендациям. Но в битриксе нет его нативной поддержки, а подключить наикрутейший monolog в битрикс можно только с помощью composer, который недоступен в коробке при инсталляции модулей. Поэтому, думаю, будет достаточно подвязаться к существующей системе логирования в битрикс.

Система прав доступа

Нужно реализовать механизм прав доступа к модулю на основе битриксовых возможностей. Нужно добавить всего 2 вида прав — запрет на работу с этим модулем, и разрешение на осуществление всех операций этого модуля.

Summary

Вот такое техническое заданьице получилось на такой крошечный модуль, несмотря даже на очень низкий уровень детализации. Но я думаю, что этого уровня детализации будет вполне достаточно среднему разработчику для того, чтобы начать непосредственно реализацию этого модуля.

Хочу заметить, что я пока еще не написал ни единой строчки кода этого модуля, но уже около половины дела сделано. Остается дело за малым — открыть любимую IDE и набросать функционал, подумав немного о частных проблемах реализации. Этим и займемся в следующих статьях.

UPD. Обманул я вас немного по части структуры БД. Проверил опытным путем, добавил в таблицу b_sale_discount_coupon свою колонку, после чего прогнал полную проверку системы. Битрикс даже не шелохнулся. Поэтому буду добавлять колонку и строить функционал модуля исходя из этого. И уже если на проверке завернут, буду рефакторить.