Сколько стоит интеграционный слой на WSO2 MI и EF Core: разбор реальной системы по строкам

от автора

Logistics tracker

Logistics tracker

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

Пятнадцать модулей, три ноды за балансировщиком, RabbitMQ и Kafka по три ноды, Redis, PostgreSQL, отдельный сервер идентичности.

Зачем её вообще стали писать. TMS покупали как услугу у внешних поставщиков, и риски выросли.

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

Систему забрали себе.

И вот главное, что стоит сказать сразу, потому что оно ломает привычную рамку разговора про техдолг. Этой системе год и четыре месяца. Первый коммит — апрель 2025-го, сейчас июль 2026-го, 931 коммит.

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

Про команду, потому что это влияет на чтение цифр. Бэкенд писался не в одиночку и не «как получится». В команде были:

  • системный аналитик — требования приходили разобранными, а не пересказом в переписке;

  • QA — сделанное проверял не автор кода;

  • проектный менеджер — полноценная классическая роль: бюджет, сроки, приоритеты, очерёдность работ, оценка рисков.

Веб-интерфейс делала отдельная команда, мобильное приложение — отдельный разработчик. Их работы в этих цифрах нет: считается бэкенд.

Это важно для дальнейшего в двух отношениях.

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

Второе: все часы в таблицах — только разработка. Работа аналитика, QA и менеджера в них не входит ни одним часом, хотя без неё проект бы не выехал. То есть полная стоимость владения выше моих цифр, а не ниже.

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

Я разобрал этот бэкенд по строкам. Не по рассказам, а по коду: посчитал артефакты, замерил объёмы, посмотрел, где что дублируется и что оказалось мёртвым.

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

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

Что обезличено, а что нет. Класс системы называю прямо — TMS с компонентой отслеживания в реальном времени: таких систем сотни, и по классу опознать нельзя. А вот названия системы, компании и конкретной ERP не будет нигде. Стек — WSO2 Micro Integrator, Entity Framework Core, PostgreSQL, .NET — тоже встречается у сотен организаций, так что речь о классе решений, а не о конкретном заказчике.

Цикл про redb и redb.Route. Свежие статьи — сверху:

Полный список — в профиле. Исходники: github.com/redbase-app.


Что за система

Коротко о масштабе, чтобы цифры дальше были к чему привязать. Считал по коду, вендорские библиотеки не включал.

Что

Файлов

Строк

C# — прикладной слой, свой движок, контейнер

851

121 466

SQL — модель, миграции, отчётность, справочники

28

17 341

synapse-XML — артефакты WSO2 MI

206

14 564

OpenAPI — контракты

20

5 235

Razor — операторский дашборд контейнера и шаблонная обвязка хостов

54

5 224

Java — коннектор к шине и классовые медиаторы

18

4 099

Свои JS и CSS

26

3 999

Maven — описания сборки модулей шины

7

3 235

Итого написанного людьми

1 210

≈ 175 200

Сверху — сгенерированные артефакты миграций

4

17 407

Документация в репозиториях

165

28 000

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

Сгенерированные артефакты миграций — 17 407 строк в четырёх файлах. Их не писал человек и не читает человек: это снимки модели данных, которые инструмент ORM создаёт сам. Но они лежат в системе контроля версий, попадают в запросы на слияние и конфликтуют при слиянии ветвей. В счёт написанного кода я их не включаю — честнее держать отдельной строкой. К ним ещё вернёмся, когда дойдём до цены миграций.

Описания сборки для шины — 3 235 строк на семь модулей, в среднем по 460 строк на модуль. Это не артефакты маршрутизации, а Maven-конфигурация: как упаковать модуль в устанавливаемый пакет. Для сравнения: описание сборки .NET-проекта в этой же системе — 30 строк. Пятнадцатикратная разница в файле, который не делает ничего, кроме объяснения, как собрать остальное.

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

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

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

Инфраструктура: три ноды за балансировщиком, RabbitMQ и Kafka по три ноды, Redis, PostgreSQL, отдельный сервер идентичности со своим набором баз.

Внутри — два интеграционных слоя одновременно: WSO2 Micro Integrator и собственный движок маршрутизации с контейнером модулей на 38 600 строк. К тому, зачем системе с WSO2 MI понадобился второй, вернёмся отдельно — это самое интересное место в разборе.

Сто семьдесят пять тысяч строк — это не «средняя корпоративная система». Это большой проект, причём только его серверная часть, и содержание такого объёма стоит соответственно.


Слой первый: WSO2 MI — интеграции на synapse-XML

Начнём со слоя, который в оценку обычно не попадает — его по привычке относят к конфигурации.

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

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

Интеграционный слой состоит из 195 XML-артефактов:

Тип артефакта

Штук

Endpoint

102

Sequence

54

Proxy-service

10

Local-entry

10

API

6

Задача по расписанию

4

Шаблон

2

И Java, куда XML не дотянулся

Артефактами дело не заканчивается. Рядом лежит 4 099 строк Java в 18 файлах, и делятся они на две части.

Форк официального коннектора к Kafka — 2 429 строк в 12 файлах. И вот это отдельный сюжет, который стоит рассказать целиком, потому что он типичнее, чем кажется.

Коннектор к Kafka у вендора есть. Он лежит в его же репозитории, ставится штатно и в комплекте присутствует. Проблема в том, что он не заработал как нужно: в нём обнаружились ошибки.

Дальше — обычная развилка, знакомая всем, кто живёт на чужой платформе: ждать исправления от вендора или чинить самому. Ждать было нельзя, поэтому проект форкнул коннектор и починил его у себя.

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

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

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

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

Классовые медиаторы — 1 670 строк. Это Java-классы, которые подключаются прямо в XML-последовательности там, где выразительности XML не хватило. И вот их список говорит о границах языка лучше любого рассуждения:

Медиатор

Строк

Что делает

Масштабирование изображений

433

Ресайз и управление превью — обработка бинарных данных

Кэш OAuth-токенов

402

Состояние, переживающее отдельный запрос

Пул уникальных ключей

401

Потокобезопасная выдача идентификаторов блоками с автопополнением

Диагностика пула потоков

250

Интроспекция рантайма

Валидация токена

183

Собственная логика безопасности

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

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

Мелкая деталь, показательная сама по себе: масштабирование изображений присутствует в двух модулях — рабочая реализация на 433 строки и однострочная заглушка с тем же именем в другом. Тот же сюжет, что с мёртвыми SQL-view ниже: попробовали, перенесли, старое не убрали, потому что доказать безопасность удаления в системе с логикой в конфигах — отдельное расследование.

Итого интеграционный слой описан на трёх языках сразу: XML для маршрутов, Java для того, что XML не выражает, и конфигурационные файлы для настроек. Плюс .NET на прикладной стороне.

Выборка из базы — отдельная история

Ещё одно ограничение, о котором не пишут в обзорах, а узнают в проекте.

Штатный способ обратиться к базе прямо из последовательности — медиатор DBLookup. И у него есть ограничение, которое документация вендора называет прямым текстом: он не возвращает несколько строк. Одна запись — и всё.

Официальная рекомендация на этот случай — завести отдельный сервис данных: свой тип артефакта, со своими SQL-запросами, описанием входных параметров и маппингом выходных полей, — и вызывать его из последовательности отдельным медиатором. То есть ещё один вид артефактов, ещё один слой описания и ещё одна сущность в развёртывании.

Либо — второй путь, который на практике выбирают чаще: заставить базу отдавать не строки, а готовый JSON или XML. Запрос сам сворачивает выборку в документ, а шина получает то, с чем умеет работать.

В этой системе видно оба следа. В SQL-скриптах 17 мест с агрегацией результата в JSON, и одно из представлений так и называется — «джейсон-вью для водителей».

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

И самое любопытное — давление идёт с обеих сторон. По внутренней оценке той же системы, пять SQL-представлений на стороне приложения были заведены как «костыли для сборки графов в JSON», уже под ORM. То есть и шина, и ORM независимо друг от друга вытолкнули одну и ту же работу в базу.

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

Сразу оговорка: WSO2 MI бесплатный

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

Поэтому дальше речь не про лицензии. Ноль рублей в этой строке.

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

Это и есть предмет разбора: бесплатная не значит дешёвая, и разница видна только когда её посчитаешь.

Почему это дороже, чем выглядит

Разберём подробнее, где именно копится цена.

Нет проверки типов. Опечатка в имени свойства, несовпадение типа, обращение к несуществующему полю — всё это выясняется при выполнении, на стенде, а иногда и в проде.

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

Отдельно оговорюсь, потому что это частое заблуждение — отладчик у WSO2 MI есть. В Integration Studio можно ставить точки останова на медиаторах и смотреть контекст сообщения. Так что «отлаживается только логами» — неправда, и я в первой редакции этой статьи написал именно так, пока меня не поправили. Инструменты есть; дороже здесь другое.

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

Плюс единица поставки здесь — не файл, а весь модуль: проекты собираются Maven’ом в Carbon Application.car-архив, и в шину едет он целиком. В репозитории это видно по <packaging>car</packaging> в шести сборочных файлах.

Оценка

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

Что

Штук

Часов за штуку

Итого

Endpoint (простые, обёртка над адресом)

102

0,5–1

51–102

Sequence (логика, трансформации, условия)

54

4–8

216–432

Proxy-service (полноценный сервис)

10

8–16

80–160

API (маршрутизация + описание)

6

8–16

48–96

Задача по расписанию

4

3–6

12–24

Local-entry, шаблоны

12

1–2

12–24

Форк коннектора Kafka: разбор чужого кода, починка, доработка

1

60–120

60–120

Классовые медиаторы на Java (1 670 строк)

5

25–50

125–250

Развёртывание и настройка шины, кластер, обучение команды

300–500

300–500

Итого разово

904–1 708

Оговорка к таблице: часы за штуку — это написание плюс отладка, а не только набор текста. Для sequence разброс 4–8 часов взят потому, что простая трансформация делается за час, а sequence с ветвлением, обработкой ошибок и вызовом внешнего сервиса легко съедает день.


Слой второй: EF Core — данные через ORM

Здесь картина, знакомая всем, кто вёл проект на Entity Framework. С поправкой, о которой стоит помнить всю эту главу: всё описанное ниже накопилось за шестнадцать месяцев, а не за годы.

Что

Объём

OnModelCreating

~1 500 строк

DbSet в контексте

51

Модели с navigation properties

54 файла

Файлов с Include

40

Файлов с ThenInclude

30

Файлов с SaveChanges

40

SQL-view для сборки графов

5, из них 4 — мёртвый код

Generic Repository + Specifications

отдельный слой

Две вещи здесь стоит разглядеть отдельно.

Пять view, из которых четыре мертвы

Это не небрежность. Это диагноз.

View появились потому, что собирать граф объектов через Include оказалось дорого, и логику сборки вынесли в базу — пусть PostgreSQL сам соберёт JSON. Каждая view — это обходной манёвр вокруг ограничения ORM.

А четыре из пяти оказались мёртвыми, потому что подход перепробовали несколько раз, каждый раз чуть иначе, и старые попытки никто не удалил. Их не удалили не из лени: чтобы уверенно удалить view, надо доказать, что на неё никто не ссылается, а в системе, где часть логики живёт в XML-артефактах, это отдельное расследование.

Процессоры, где большая часть кода — не бизнес-логика

По внутренней оценке той же системы, наиболее тяжёлые обработчики выглядят так:

Обработчик

Объём

Из них на загрузку и ручную сборку графа

Получение основной сущности по идентификатору

810 строк

~60%

Получение связанного списка

495 строк

6 отдельных запросов + сборка

Сервис фильтрации

163 строки

Include под каждый фильтр

Полторы тысячи строк в трёх файлах, и больше половины из них — не бизнес-правила, а транспортировка данных из базы в память и обратно.

Оценка

Что

Часов

OnModelCreating — 1 500 строк конфигурации связей и ключей

150–250

54 модели с navigation properties

110–220

Generic Repository + Specifications

80–150

Пять SQL-view с моделями (включая четыре, которые окажутся мёртвыми)

40–80

Настройка миграций, скрипты по контурам

60–120

Итого разово

440–820


Слой третий: то, что написали сами

А теперь самое интересное в этой системе, и то, ради чего её вообще стоит разбирать.

Параллельно с WSO2 MI в ней работал собственный интеграционный движок. Не обвязка и не хелперы — полноценный фреймворк маршрутизации в духе Apache Camel, написанный под .NET.

Компонент

Объём

Движок маршрутизации

173 файла, 27 529 строк

Контейнер модулей с дашбордом и REST API

54 файла, 11 117 строк

Итого

≈ 38 600 строк инфраструктурного кода

Что внутри движка:

  • Восемь транспортов — Kafka, RabbitMQ, Redis, Cron, Timer, SEDA, Direct, Validator, каждый со своими потребителем, производителем и точкой подключения;

  • 43 файла абстракций — IComponentIConsumerIProducerIEndpointIProducerTemplate: дословный словарь Camel;

  • 41 файл языка выражений — выражения и предикаты, компилируемые, а не интерпретируемые;

  • 19 файлов определений маршрутов — то есть свой DSL;

  • 16 процессоров и 4 файла транзакционности.

А контейнер — это не «хост, который запускает модули». По инвентарю в нём:

  • загрузка модулей через единую точку входа, реестр модулей и фабрику контекстов;

  • жизненный цикл с горячей перезаменой: остановить контекст, удалить, пересоздать, зарегистрировать маршруты заново, запустить — без остановки процесса;

  • состояния модуля — обнаружен, загружен, инициализирован, ошибка;

  • фильтр состава с шаблонами включения и исключения: можно поднять ноду только с частью модулей;

  • кластеризация и планировщик с общим хранилищем;

  • веб-панель на 14 экранов — обзор, модули, контексты, эндпоинты, API, мониторинг, планировщик, логи, база, хранилища, процессоры, система, пользователи, роли;

  • сбор метрик памяти и рантайма.

Это не заготовка и не эксперимент. Это работающий продукт внутри проекта.

Зачем системе с WSO2 MI понадобился второй движок

Ответ прозаичен и знаком всем, кто работал с корпоративными интеграционными платформами.

Готовая шина хорошо делает то, что задумано её авторами, и плохо — всё остальное. Нужен транспорт, который в комплекте сломан, — форкаете и чините сами. Нужна логика сложнее ветвления — уносите её в Java-классы. Нужна выборка из базы в несколько строк — заводите отдельный сервис данных или заставляете базу отдавать JSON. Нужен обмен запрос-ответ под нагрузкой — разбираетесь, почему зависает общий канал.

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

В какой-то момент это становится очевидным, и вопрос переворачивается: не «зачем писать своё», а «сколько ещё мы будем доплачивать за чужое».

Оценка

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

Что

Часов

Восемь транспортов (потребитель, производитель, точка подключения, повторы, отказы)

640–1 200

Движок выражений и предикатов, компилируемый

250–400

DSL определений маршрутов и процессоры

300–500

Ядро и абстракции

250–400

Транзакционность поверх транспортов

100–200

Контейнер модулей: загрузка, жизненный цикл, веб-панель, REST

400–700

Итого разово

1 940–3 400

Это 1,2–2 человеко-года, и это самая дорогая строка во всей смете.

Что контейнер модулей отменил

Есть в этой системе четыре проекта с суффиксом .App, и они наглядно показывают, зачем нужен контейнер.

Изначально каждый модуль был отдельным приложением: свой процесс, свой хост, своя выкатка. Мобильный API — приложение. Слой доступа к данным — приложение. Интеграция — приложение. GPS-контур — приложение.

Что внутри такого хоста, на примере одного из них:

Файл

Строк

Что делает

DalApp.cs

158

Точка входа, сборка контейнера зависимостей

DatabaseContextFactory.cs

88

Фабрика контекста БД

StaticAuditHostedService.cs

54

Фоновая служба

RouteContextHostedService.cs

31

Поднять контекст маршрутов

Error / Index / Privacy

54

Страницы из шаблона проекта

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

Четыре хоста, 741 строка на всех. И заметьте деталь: в каждом лежит страница «Политика конфиденциальности» из стандартного шаблона ASP.NET, которую никто никогда не открывал. Она приехала при создании проекта и осталась — потому что удалять её никто не считал своей задачей.

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

Четыре единицы развёртывания схлопнулись в одну, и цена этого схлопывания — удалить 741 строку обвязки. Сами модули остались как были: мобильный API — 2 855 строк, слой данных — 2 074, GPS — 556. Изменилось не то, что они делают, а то, где они живут.

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

Но именно она себя оправдала

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

Покупная шина и ORM обошлись дешевле на входе — и оба слоя пришлось убрать. Собственный движок обошёлся дороже всех — и он единственный, что пережил переход. Больше того, он лёг в основу продукта, который сейчас развивается отдельно от системы, для которой писался.

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


Слой четвёртый: WSO2 IS — 227 таблиц до первой строки бизнес-логики

Отдельная строка, которую в сметах не видно никогда. И здесь самая наглядная цифра всего разбора.

За аутентификацию в системе отвечает WSO2 Identity Server. Он приносит с собой четыре собственные базы данных, которые надо создать, наполнить, версионировать, бэкапить и обновлять вместе с продуктом.

Я посчитал таблицы в развёрточных скриптах:

База

Таблиц

Размер скрипта

identity_db

139

92 КБ

shared_db

60

44 КБ

user_db

23

16 КБ

cluster_db

5

4 КБ

Итого служебных

227

156 КБ

Теперь сравните с бизнес-моделью самой системы — той, ради которой всё затевалось:

Таблиц

Служебные таблицы сервера идентичности

227

Бизнес-модель: рейсы, точки, заказы, транспорт, справочники

53

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

Оговорюсь сразу: эти 227 таблиц — штатная схема продукта, её никто не писал. Она приезжает при установке. Но развернуть, версионировать, бэкапить и обновлять её приходится вам, на всех контурах.

Из чего система реально пользуется

Прошёлся по артефактам и посмотрел, какие эндпоинты сервера дёргаются:

Эндпоинт

Вызовов

scim2/Users

7

oauth2/token

2

oauth2/revoke

2

scim2/Groups

1

Весь используемый контракт — четыре эндпоинта: выдать токен, отозвать токен, управлять пользователями, управлять группами.

Это не претензия к продукту. WSO2 IS умеет несопоставимо больше — федерацию, SAML, адаптивную аутентификацию, десятки протоколов. Он рассчитан на организацию, которой всё это нужно. Если вам нужны четыре эндпоинта, вы всё равно разворачиваете и обслуживаете продукт целиком: вы платите за его полноту, а не за свою потребность.

Но главное не в этом

Сервер идентичности выдаёт и проверяет токены. Проблема начинается там, где это упирается в нагрузку.

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

Штатная схема — спросить сервер идентичности — на таком потоке не работает: он превращается в узкое место всего конвейера.

Решение, которое пришлось построить: кэш токенов Java-медиатором прямо внутри шины. Первую проверку делает сервер идентичности — он же токен и выдал. Если токен признан годным, он кладётся в кэш, и дальше проверка идёт на стороне WSO2 MI, без обращения к серверу.

Четыреста строк Java, которых не было бы, если бы поток был меньше.

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

Что

Часов

Развёртывание сервера идентичности, четыре базы, интеграция

200–350

Настройка кластера: три ноды, балансировщик, тройки брокеров, Redis

150–300

Конвейеры сборки для двух стеков — .NET и Java/Maven

100–200

Итого разово

450–850


Ежегодное содержание

Разовые затраты — половина истории. Вторая половина приходит каждый год.

Два стека вместо одного

Прикладной слой на .NET, интеграционный на Java с Maven. Это два конвейера сборки, два набора зависимостей, два цикла обновлений и две области компетенции внутри одной команды.

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

Отправитель и потребитель на одном соединении — взаимная блокировка

Вот настоящая боль, а не гипотетическая.

Механика такая. Обмен по схеме запрос-ответ означает, что отправитель, послав сообщение, ждёт ответа. Если в этот момент на том же соединении RabbitMQ висит ещё и потребитель, ожидание занимает общий канал — и всё встаёт. Обмен не идёт, соединение зависает.

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

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

Стоит осознать класс проблемы. Упавший сервис виден сразу: ошибка, алерт, перезапуск. Зависший на общем канале обмен не виден никак, пока кто-то не заметит растущую очередь или не пожалуется пользователь. И диагностика — это не чтение стектрейса, а раскопки в поведении чужого рантайма: почему потребитель жив, соединение открыто, а сообщения не разбираются.

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

И второе: RPC-очереди не переживают перестроение кластера

Тот же класс проблем, но злее.

Часть обмена между шиной и прикладными нодами идёт по схеме запрос-ответ через RabbitMQ. Такая схема использует временные очереди для ответов — они создаются под конкретного клиента и живут, пока живёт соединение.

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

Причём нельзя сказать, что платформа этого не предусматривает. Механизмы восстановления там есть, и они включены: автоматическое восстановление соединения, восстановление топологии, интервал переподключения, счётчик повторов на фабрике. Всё это настроено.

Но работает через раз. И лечение в итоге — перезапуск ноды шины.

Конфиг как дневник борьбы

Лучшее свидетельство того, чего это стоило, — сам конфигурационный файл. Он не выглядит как настройки из документации, он выглядит как протокол расследования. Там есть:

  • пометка, что проверку соединения при возврате в пул пришлось отключить, чтобы не ловить NullPointerException — с комментарием «критично»;

  • три параметра времени жизни соединений и каналов, закомментированных с пометкой «слишком дорого»;

  • интервал восстановления, уменьшенный вдвое, с записью прежнего значения рядом;

  • таймауты запрос-ответа, поднятые с 90 до 120 секунд «для времени восстановления» — тоже с сохранённым прежним значением;

  • отдельные фабрики на отправку и на приём с идентичными параметрами — то самое лечение тихо умирающих потребителей.

Каждая строка с комментарием — это чей-то потраченный день. Обычно несколько.

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

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

У схемы данных два источника правды

Схема живёт одновременно в двух местах: миграции ORM и отдельные SQL-скрипты, которые применяются руками. Оба источника подтверждены, а канонический порядок их применения во внутренней документации помечен как «требует уточнения у владельцев базы».

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

Следствие видно невооружённым глазом. Миграционные скрипты лежат отдельно для трёх окружений; я сверил их хеши: test и prod идентичны, dev разошёлся.

Это не чья-то ошибка, это свойство подхода. Как только применение схемы становится ручной операцией, расхождение контуров — вопрос времени, а не аккуратности.

Оценка

Что

Часов в год

Поддержка и правка 195 XML-артефактов (без типизации и отладчика)

250–450

Обновления интеграционной платформы и проверка совместимости

100–200

Миграции базы, три контура, устранение расхождений

150–300

Поддержка форка коннектора: перенос правок при обновлениях вендора

60–120

Обслуживание четырёх служебных баз

60–120

Двойные компетенции: онбординг и передача задач между стеками

150–250

Итого в год

750–1 400


Сводка

Разовые затраты на построение такого слоя:

Слой

Часов

Интеграции на WSO2 MI (synapse-XML + Java)

904–1 708

Данные через ORM

440–820

Собственный интеграционный движок и контейнер

1 940–3 400

Инфраструктура и вход

450–850

Итого

3 734–6 778

Это 2,2–4 человеко-года — до единой строки бизнес-логики.

Ежегодно: 750–1 400 часов, то есть 0,45–0,85 постоянной ставки, только на то, чтобы слой продолжал работать.

По ставке 4 000 ₽/час это 14,9–27,1 млн ₽ разово и 3–5,6 млн ₽ ежегодно.

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

Половина кодовой базы — не бизнес-логика

Ещё один срез, который стоит посчитать отдельно. Из ста семидесяти пяти тысяч строк, написанных людьми:

Что это

Строк

Собственный движок маршрутизации и контейнер модулей

38 646

SQL: модель, миграции, отчётность, справочники

17 341

Артефакты WSO2 MI на XML плюс описания сборки

17 799

Контракты, дашборд, свои JS и CSS

14 458

Конфигурация ORM, модели, репозитории, обходные представления

12 672

Java: форк коннектора и классовые медиаторы

4 099

Инфраструктура, итого

≈ 105 000

Прикладной код: правила, расчёты, обработка, маршруты модулей

≈ 70 000

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

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

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

Ставка здесь — полная стоимость часа для компании: оклад, налоги, взносы, рабочее место, управление, отпуска и простой. Не сумма в оффере — её обычно и подставляют по ошибке, занижая результат в два-три раза. Четыре тысячи — осознанно нижняя граница; подставьте свою, часы от неё не зависят.


Чего эти цифры не учитывают

Раздел, без которого оценка была бы нечестной.

Это модель, а не замер. Никто не вёл параллельный учёт по каждому артефакту. Часы за штуку — экспертная оценка, и каждую строку можно оспорить отдельно. Именно поэтому таблицы разложены построчно, а не даны итогом.

Часть работы всё равно пришлось бы сделать. Бизнес-логика — правила, расчёты, валидация — не зависит от того, на каком стеке она написана. В цифрах выше её нет и не должно быть: считается только инфраструктурный слой.

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

У ESB есть свои сильные стороны. Готовая шина даёт коробочные адаптеры к тому, что вы иначе писали бы руками, единообразную точку администрирования и понятную модель для команды, которая на ней уже работала. Если у вас в штате есть эта компетенция, часть моих оценок для вас завышена.

Организация могла бы часть закупить. Тогда экономия конвертируется в лицензии и в отсутствие собственного развития, а не в человеко-часы.

И главное: система работает. Она держит прод, обслуживает интеграцию с SAP и мобильные клиенты — и, напомню, ей год и четыре месяца. Команда за шестнадцать месяцев подняла с нуля то, что раньше покупали как услугу у внешних поставщиков. Это не разбор провала, а разбор цены, которую платят за конкретный набор архитектурных решений. Цена может быть оправданной; вопрос в том, знаете ли вы её.


Что дальше

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

Три слоя из четырёх демонтируются. Остаётся тот, что обошёлся дороже всех.

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

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

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

Исходники и релизы: github.com/redbase-app. Про хранилище redb: redb.ru. Прошлые статьи цикла — в профиле.

If this was useful — a ⭐ on GitHub helps others find it.

ссылка на оригинал статьи https://habr.com/ru/articles/1064688/