Abstract:
An approach to defining microservice boundaries when splitting a monolithic application based on the analysis of the module dependency graph is considered. Code links are represented by a call matrix, from which coupling and cohesion metrics are calculated, and modules are grouped by the modularity criterion. Using an educational project, a reduction of the average inter-module coupling and the share of cross-module calls after decomposition is shown.

Keywords:
microservices, decomposition, monolith, dependency graph, coupling, cohesion, metrics, clustering, software architecture
Text (PDF): Read Download

Введение

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

Однако наиболее трудным решением при таком переходе остаётся выбор границ сервисов. Неудачная декомпозиция приводит к «распределённому монолиту»: сервисы формально разделены, но связаны настолько плотно, что почти любое изменение затрагивает сразу несколько из них. В результате система получает все издержки распределённости, но не получает главного преимущества — независимости разработки и развёртывания.

На практике границы часто проводят по интуиции или по сложившейся структуре команд. Такой выбор трудно обосновать и ещё труднее проверить. Между тем связи в коде объективно фиксируются: это вызовы между модулями, обращения к общим данным, совместное использование типов. Это позволяет поставить задачу декомпозиции как задачу системного анализа [3] — представить приложение графом зависимостей и искать такое разбиение, при котором сильно связанные элементы остаются вместе, а слабо связанные оказываются в разных сервисах. Близкий по духу приём — архитектурный анализ исходного кода для оценки качества проекта — рассматривается в работе [4].

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

 

Исходные данные и постановка задачи

В качестве объекта рассмотрено учебное веб-приложение интернет-магазина запасных частей, написанное в монолитной архитектуре. Кодовая база включает 34 модуля (пакета), сгруппированных вокруг шести предметных (бизнес-) областей [1]: оформление заказов, каталог товаров, оплата, склад, профиль пользователя и отчётность. Все модули работают с единой базой данных и собираются в один артефакт.

Зависимости между модулями извлекались статически — из импортов и вызовов в исходном коде, а также из обращений к таблицам базы данных. Каждой паре модулей сопоставлялось число направленных связей: сколько раз код одного модуля обращается к другому. Полученные данные представлены ориентированным графом зависимостей G = (V, E), где вершины V — модули, а вес ребра отражает интенсивность обращений. Этот граф и стал исходным материалом для анализа.

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

 

Метрики связности и модель зависимостей

Для числовой оценки использовались две классические характеристики качества кода из объектно-ориентированного проектирования [2]. Связанность модуля (coupling between objects, CBO) — число других модулей, с которыми данный модуль связан напрямую; чем она выше, тем сильнее модуль зависит от окружения. Сцепление внутри модуля (косвенно через метрику LCOM) показывает, насколько элементы модуля решают одну задачу; высокое сцепление желательно, низкое говорит о том, что модуль стоит разделить.

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

                                            Q = (1 / 2m) · Σᵢⱼ [Aᵢⱼ − (kᵢ · kⱼ) / 2m] · δ(cᵢ, cⱼ)                                       (1)

где Aᵢⱼ — вес связи между модулями i и j; kᵢ — суммарный вес связей модуля i; m — суммарный вес всех связей графа; cᵢ — номер группы, в которую отнесён модуль i; δ равна единице, если оба модуля в одной группе, и нулю в противном случае. Значение Q растёт, когда плотно связанные модули собираются в одну группу, поэтому максимизация Q по формуле (1) и даёт искомое разбиение на сервисы.

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

Кластеризация модулей и выделение сервисов

Группировка модулей выполнялась жадным агломеративным методом, направленным на рост модульности. Сначала каждый модуль считается отдельной группой. Затем на каждом шаге объединяются две группы, слияние которых даёт наибольший прирост Q; процесс продолжается, пока объединение увеличивает показатель. Полученное разбиение корректировалось вручную с учётом ограничения по общим таблицам базы данных: если два кандидата в разные сервисы работали с одной таблицей, они объединялись либо для одного из них предусматривался отдельный программный интерфейс доступа к данным.

Слева на рисунке 1 показан исходный монолит: модули плотно переплетены перекрёстными вызовами, граница предметных областей размыта. Справа — результат декомпозиции: пять сервисов с выраженными внутренними связями, взаимодействующих только через интерфейсы (API). Такой вид взаимодействия принципиален: после разбиения сервисы не обращаются к чужим данным напрямую, а запрашивают их у владельца.

Название: Рисунок 1 – Переход от монолита с перекрёстными связями к набору микросервисов, взаимодействующих через интерфейсы - описание: Рисунок 1 – Переход от монолита с перекрёстными связями к набору микросервисов, взаимодействующих через интерфейсы

Рисунок 1 – Переход от монолита с перекрёстными связями к набору микросервисов, взаимодействующих через интерфейсы

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

 

 

Таблица 1 – Характеристики выделенных сервисов

Сервис

Модулей

Внутренних связей (ср.)

Внешних связей

Заказы

8

5,9

4

Каталог

6

4,7

2

Оплата

5

4,2

3

Склад

7

5,1

2

Профиль

8

4,5

1

 

Результаты декомпозиции

Сравнение метрик до и после разбиения приведено на рисунке 2. Средняя связанность модуля (CBO) снизилась с 12,4 до 4,1, наибольшая связанность — с 24 до 7, а среднее число зависимостей на модуль — с 6,8 до 2,3. Доля кросс-модульных вызовов, выходящих за границу будущего сервиса, сократилась с 41 % до 12 %. Иными словами, после декомпозиции подавляющее большинство связей замкнулось внутри сервисов, а наружу осталась лишь небольшая, контролируемая часть взаимодействий.

Название: Рисунок 2 – Метрики связанности модулей до и после декомпозиции - описание: Рисунок 2 – Метрики связанности модулей до и после декомпозиции

Рисунок 2 – Метрики связанности модулей до и после декомпозиции

Модульность разбиения Q, рассчитанная по формуле (1), составила 0,62, что для графа такого размера соответствует хорошо выраженной структуре групп. Важно, что полученные границы совпали с предметными областями приложения лишь частично: например, модуль формирования отчётов, который интуитивно относили к отдельной области, по связям оказался ближе к сервису заказов и был включён в него.

Дополнительно оценивалась независимость развёртывания. До разбиения изменение в любом из шести направлений требовало пересборки и повторного тестирования всего приложения. После выделения сервисов изменение, не затрагивающее межсервисные интерфейсы, ограничивалось одним сервисом: по выборке из 40 типовых задач сопровождения доля изменений, локализованных в одном сервисе, составила 78 %.

Обсуждение

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

У подхода есть и ограничения. Статический анализ не учитывает интенсивность связей во время работы: редкий, но критичный по данным вызов он оценивает так же, как частый, но безобидный. Не охватываются и неявные зависимости — через очереди сообщений, общий кэш или конфигурацию. Поэтому числовое разбиение разумно рассматривать как первое приближение, которое уточняется по эксплуатационным данным и знаниям предметной области. Сопоставление архитектурных вариантов по нескольким критериям, в том числе с привлечением экспертных моделей, обсуждается в работах [5, 6].

Заключение

Показано, что границы микросервисов можно выделять по анализу графа зависимостей, а не «по чутью». Связи модулей представлены матрицей вызовов, по ней рассчитаны метрики связанности и сцепления, а группировка выполнена по критерию модульности. На учебном проекте средняя связанность модуля снизилась втрое, доля кросс-модульных вызовов упала с 41 % до 12 %, а большинство изменений после разбиения стали локализоваться в одном сервисе. В итоге процедура даёт воспроизводимое и числово обоснованное разбиение монолита, пригодное как отправная точка для перехода к микросервисной архитектуре.

1. Nikitin, I. V. Sravnenie podhodov monolitnoy arhitektury i mikroservisnoy arhitektury pri realizacii servernoy chasti veb-prilozheniya / I. V. Nikitin, T. Yu. Gricenko // Dnevnik nauki. – 2020. – № 3(39). – S. 22. – EDN TTOGCE.

2. Karahanova, A. A. Analiz mikroservisnoy arhitektury, monolitnyh prilozheniy, arhitektury SOA / A. A. Karahanova // Sinergiya Nauk. – 2020. – № 46. – S. 255-262. – EDN BVOVEU.

3. Taraskin, A. A. Eksperimental'noe sravnenie proizvoditel'nosti monolitnoy i aktornoy arhitektur dlya dashbordov real'nogo vremeni / A. A. Taraskin, A. R. Harisov // Vestnik nauki. – 2025. – T. 3, № 12(93). – S. 1413-1422. – EDN ZCMWEQ.

4. Minakova, O. V. Povyshenie effektivnosti raboty v proektah open sourse na osnove arhitekturnogo analiza (na primere proekta Sahana) / O. V. Minakova, I. V. Pocebneva, P. Yu. Gusev // Modelirovanie sistem i processov. – 2024. – T. 17, № 1. – S. 84-92. – DOIhttps://doi.org/10.12737/2219-0767-2024-17-1-84-92. – EDN NZNSKM.

5. Kuripta, O. V. Arhitekturnoe reshenie proektirovaniya servisov prostranstvenno-vremennoy navigacii v obrazovatel'nyh uchrezhdeniyah / O. V. Kuripta, O. V. Minakova, I. V. Pocebneva // Modelirovanie sistem i processov. – 2024. – T. 17, № 1. – S. 65-72. – DOIhttps://doi.org/10.12737/2219-0767-2024-17-1-65-72. – EDN CNSEZA.