УДК 004.45

Минимизация цикломатической сложности проектов, использующих Srping Data Redis для организации межсервсиного взаимодействия путем отправки сообщений в режиме работы Pub/Sub, с помощью динамической кодогенерации

Хватов Сергей Андреевич – магистрант Университета ИТМО

Аннотация: Одна из основных задач, решаемых при проектировании микросервисных систем – организация взаимодействия компонентов внутри системы (inter-process communication, IPC). В настоящее время существует множество готовых решений, однако с каждым годом на рынке появляется все больше технологий, которые можно рассматривать в качестве возможных кандидатов для организации IPC в MSA. Одна из таких технологий – NoSQL база данных Redis и ее режим работы Pub/Sub. Для веб-фреймворка Spring Boot существует модуль Spring Data Redis, предоставляющий, в том числе, API для работы с Redis в режиме Pub/Sub. Однако исходный код проектов, использующих данный модуль, очень сложен. Цикломатическая сложность подобных программных продуктов даже выше, чем в случае использования модуля Spring for Apache Kafka, предназначенного для работы с одноименной очередью сообщений. В рамках данной статьи рассматривается возможность минимизации цикломатической сложности проектов, использующих Spring Data Redis, за счет динамической генерации кода.

Ключевые слова: микросервисная архитектура, MSA, межсервисное взаимодействие, IPC, Apache Kafka, Redis, кодогенерация, ByteBuddy, Spring, Spring Boot.

Эмпирическая оценка сложности кода

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

Один из основных, наиболее часто применяемых количественных показателей – цикломатическая сложность. Данная метрика была впервые представлена Томасом Дж. Маккейбом в 1976 году в научной статье под названием «A Complexity Measure» [1].

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

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

Наиболее эффективным способом снижения цикломатической сложности является декомпозиция кода, дробление методов на более простые, а также оптимизация логических выражений [2].

Для получения значения данной метрики было принято решение использовать программную платформу SonarQube [3]. Данная платформа предлагает отчеты о повторяющемся коде, стандартах кодирования, модульных тестах, покрытии кода, его сложности, комментариях, ошибках и рекомендациях по безопасности. SonarQube также может записывать историю метрик и предоставляет графики их изменения на всем протяжении жизненного цикла проекта.

Регистрация компонентов в веб-фреймворке Spring Boot

Прежде чем переходить к рассмотрению реализованной библиотеки, необходимо немного подробнее описать, что из себя представляет веб-фреймворк Spring и как в нем устроен механизм регистрации и внедрения зависимостей (DI).

Spring – фреймворк для Java-платформы с открытым исходным кодом на GitHub [4]. Spring был разработан в 2002 году Р. Джонсоном и развивается по сей день.

Данный фреймворк является IoC-контейнером. IoC, или «Inversion of Control» («инверсия зависимостей»), – важный принцип объектно-ориентированного программирования, основная идея которого заключается в делегировании процесса управления исполнением кода библиотеке или фреймворку [5].

На рисунке 1 представлен жизненный цикл компонентов системы (или, как их еще называют, бинов).

image001

Рисунок 1. Жизненный цикл бинов.

В рамках данной работы нас интересует только самый первый этап – загрузка и регистрация бинов.

В Spring за это отвечают так называемые registrar’ы – специальные классы, реализующие интерфейс ImportBeanDefinitionRegistrar [4].

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

В интерфейсе ImportBeanDefinitionRegistrar есть перегруженный метод registerBeanDefinitions, который на вход принимает объект класса BeanDefinitionRegistry. В этом объекте хранится вся информация о компонентах системы, и с его помощью можно на программном уровне регистрировать новые бины [4]. Именно на этом основана реализованная библиотека.

Средства динамической кодогенрации кода на ЯП Java

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

На сегодняшний день существует несколько библиотек, предназначенных для решения данной задач – JDK Dynamic Proxy, CGLIB, ByteBuddy.

Изначально планировалось использовать библиотеку CGLIB. Однако, в ходе работы было выяснено, что данная библиотека не позволяет генерировать прокси-классы с переопределенными методами базового класса без явной инстанцииации прокси-объектов, что необходимо для регистрации бинов в BeanDefinitionRegistry.

Помимо функциональных возможностей, немаловажную роль сыграла и производительность ByteBuddy. Согласно документации, данная библиотека не уступает, а в некоторых случаях превосходит, конкурентов по скорости своей работы [6].

Реализованная библиотека

В ходе работы была создана библиотека, позволяющая на основе метаинформации, хранящейся в аннотациях и сигнатурах методов, реализовывать продьюсеров и консьюмеров для работы с NoSQL базой данных Redis в режиме Pub/Sub. Помимо этого, библиотека предоставляет все компоненты, необходимые для работы с Redis.

Алгоритм работы библиотеки следующий.

На этапе старта приложения в работу включаются классы, реализующие интерфейс ImportBeanDefinitionRegistrar, описанный ранее. Данные registrar’ы сканируют указанные в конфигурации пакеты на предмет наличия классов, помеченных аннотациями Publisher и Listener. Далее, для каждого такого класса генерируется прокси-класс, содержащий в себе всю необходимую логику для получения и отправки сообщений в Redis. В итоге, сгенерированный класс регистрируется как бин в BeanDefinitionRegistry и обрабатывается фреймворком, как если бы он был в исходном коде проекте изначально.

На рисунке 2 представлен класс, который находится в исходном коде проекта, в то время как на рисунке 3 – класс, сгенерированный его основе на старте приложения.

image002

Рисунок 2. Интерфейс в исходном коде проекта.

image003

Рисунок 3. Сгенерированный класс.

Результаты применения библиотеки в тестовом проекте

Для тестирования библиотеки был разработан небольшой проект, состоящий из одного консьюмера и одного продьюсера. У проекта есть две версии – в первой используется только Spring Data Redis, во второй – разработанная в ходе данной работы библиотека.

С помощью SonarQube было получено значение цикломатической сложности обеих версий проекта. В первом случае она составила 19, а во втором – 13. Таким образом, сложность упала на 31%.

Также хотелось бы отметить, что помимо цикломатической сложности, были сняты еще несколько метрик. Их значения представлены в таблице 1.

Таблица 1. Сравнение сложности кода тестовых проектов.

 

С библиотекой

Без библиотеки

Cyclomatic Complexity

13

19

Cognitive Complexity

2

2

Lines of Code

257

293

Number of files

9

10

Как можно заметить, не снизилось значение только у показателя Cognitive Complexity, который и так был на очень низком уровне.

Заключение

Подводя итоги, можно сказать, что поставленная цель была достигнута в полном объеме. Разработанная библиотека не только позволила уменьшить цикломатическую сложность проекта, но и снизила значения ряда других метрик, также оценивающих сложность исходного кода.

Список литературы

  1. J. McCabe. “A Complexity Measure”. In: IEEE Transactions on Software Engineering SE-2.4 (Dec. 1976), pp. 308–320. ISSN: 0098-5589. DOI: 10.1109/TSE.1976.233837.
  2. Цикломатическая сложность [Электронный ресурс]. – Режим доступа: https://1c-syntax.github.io/bsl-language-server/diagnostics/CyclomaticComplexity/ (дата доступа: 05.05.2023).
  3. SonarQube Documentation [Электронный ресурс]. – Режим доступа: https://docs.sonarqube.org/latest/ (дата доступа: 05.05.2023).
  4. Spring Documentation [Электронный ресурс]. – Режим доступа: https://docs.spring.io/spring-framework/docs/current/reference/html/ (дата доступа: 20.04.2023).
  5. Martin Fowler. Inversion of Control Containers and the Dependency Injection pattern. [Электронный ресурс]. – Режим доступа: https://martinfowler.com/articles/injection.html (дата доступа: 05.05.2023).
  6. ByteBuddy Documentation [Электронный ресурс]. – Режим доступа: https://bytebuddy.net/#/tutorial (дата доступа: 20.04.2023).

Интересная статья? Поделись ей с другими: