Я Javaразработчик в АльфаСтрахование, в команде Авиа Блока. Мы занимаемся страхованием авиапассажиров, и одна из наших прикладных задач — генерация страховых полисов в PDF.
Исторически печать у нас была построена на Eclipse BIRT. Если коротко, BIRT — это движок отчётности и рендеринга документов, а файл .rptdesign — его шаблон в XMLформате: в нём описаны структура документа, таблицы, параметры, стили и скрипты.
Мы внедрили подход, где итоговый документ собирается не из одной «монолитной» .rptdesign, а из базового шаблона, переиспользуемых блоков и версионированных наборов значений. Поверх BIRT появился backend-слой, который отвечает за композицию документа, каскадное версионирование, импорт/экспорт конфигурации между средами и безопасный рендер итогового PDF через BIRT runtime.
Ключевой фокус был не в ускорении рендеринга, а в user experience: изменения в структуре и контенте должны выполнять нетехнические пользователи, а продукт — контролировать качество через управляемый процесс публикации (черновики → product review → публикация версии → откат при необходимости).
В чём была реальная боль
Всё начиналось с обычной просьбы: поправить текст в полисе. Но за такой правкой быстро обнаруживалась проблема: один и тот же документ существовал в нескольких .rptdesign, у партнёров были свои вариации, а любое изменение снова шло через разработчика.
Хотим:
-
Исправить формулировку;
-
Поменять логотип партнёра;
-
Вынести адрес из статичного текста в параметр;
-
Переставить местами два смысловых блока.
Получаем:
-
Разработчик открывает
.rptdesign; -
Правит XML/таблицы/скрипты;
-
Проверяет, что соседние продукты не сломались;
-
Выкатывает новую версию;
-
Через пару недель повторяет всё снова, но уже для другого партнёра.
Пока документов мало — терпимо. Когда продуктов и партнёров становится много, система расползается: одни и те же куски появляются в десятках копий, версия документа перестаёт быть понятной сущностью, а бизнес оказывается зависим от IT даже в простых изменениях.
Почему одного BIRT оказалось недостаточно
BIRT хорошо решает задачу рендеринга: берёт .rptdesign, подставляет параметры и отдаёт PDF. Но BIRT сам по себе не знает о наших бизнес‑инвариантах — правилах, которые важны именно для страховых документов:
-
новые документы должны печататься по новой версии, а старые — по той версии, по которой были выпущены;
-
один и тот же логотип, текст оферты или юридическая вставка должны переиспользоваться между разными документами, но управляться независимо.
То есть движок рендеринга уже был — не хватало предметного слоя поверх него.
Правильная единица изменения
Ключевой вопрос был простой: «что именно является единицей изменения?» Не документ целиком, потому что в реальности меняются локальные части:
-
шапка;
-
таблица условий;
-
логотип;
-
юридическая вставка;
-
порядок блоков;
-
набор значений для конкретного партнёра.
После этого архитектура начала перестраиваться практически сама.
Модель: от трёх слоёв к четырём
Изначально удобно было объяснять модель тремя уровнями: макет → блок → данные блока. Но в живом проекте модель доросла до четырёх уровней.
Макет (layout)
Макет отвечает за сборку документа для связки партнёр + продукт + версия и перестаёт быть «файлом на диске», превращаясь в предметную сущность с конфигурацией:
-
какие наборы данных подключены;
-
в каком порядке выводятся блоки;
-
используется ли режим
onlyBlocks; -
какой у макета статус.
Блок (block)
Блок — переиспользуемый фрагмент документа: шапка, маршрут, таблица рисков, оферта, юридический текст, подписи. Технически блок хранит BIRT-совместимый XML-фрагмент, который можно встроить в итоговый документ.
Важно, что сам блок отвечает только за отдельный кусок документа. А вот за композицию документа целиком отвечает уже layout: именно он хранит, какие блоки участвуют в сборке и в каком порядке они идут. Для бизнеса это не «техническая деталь», а часть продуктовой логики: в зависимости от продукта, партнёра или сценария один и тот же набор блоков может идти в разной последовательности.
Данные блока (blockData)
BlockData — конкретная версия настроек блока: какой логотип подставить, какой текст показать, какие ссылки использовать, какие значения применить для конкретного партнёра/продукта. Этот слой отделяет структуру блока от наполнения.
Справочники и версии значений
Со временем выяснилось, что даже blockData разрастается, если хранить всё как свободный JSON. Поэтому появились:
-
DataReference— описание параметра (что это за значение). -
DataReferenceValue— конкретная версия значения (какое именно значение в этой версии).
В более приземлённом виде это работает так:
-
у блока
HEADERесть параметрlogo; -
DataReferenceописывает сам параметр: «это логотип партнёра, картинка, обязательное поле»; -
DataReferenceValueхранит уже конкретные версии значения: например,logo-v1.pngиlogo-v2.png; -
blockDataне дублирует эти файлы и строки внутри себя, а ссылается на нужные версии значений.
Так модель становится заметно понятнее. blockData отвечает на вопрос «что нужно этому блоку в этой конфигурации», а DataReference и DataReferenceValue — «какие именно значения доступны и какая версия сейчас выбрана».
Это даёт управляемость: система знает, где используется значение, может каскадно обновлять зависимости и позволяет переиспользовать один и тот же логотип, текст или ссылку в нескольких конфигурациях без копипаста.
Пример конфигурации
Ниже — иллюстрация идеи (не точная схема хранения из production): структура документа, состав блоков и конкретные значения живут раздельно.
{ "layout": { "partnerCode": "PARTNER_A", "productCode": "TRAVEL", "version": 2, "blockNameOrder": ["HEADER", "TERMS", "FOOTER"], "onlyBlocks": true }, "blockData": { "blockName": "HEADER", "version": 2, "values": { "logo": "partner-a/logo-v2.png", "offerUrl": "https://example.org/offer", "companyName": "Partner A" } }}
Два кейса, ради которых всё и затевалось
Кейс 1. Исправить опечатку без большой миграции
Если это безопасная правка текста, её не нужно тащить через полную пересборку всего документа. Если изменение должно повлиять только на новые документы — делаем новую версию на нужном уровне модели; если структурное — оно идёт по каскаду.
Кейс 2. Новый логотип только для новых продаж
Бизнес хочет, чтобы новые документы печатались с новым логотипом, а старые — как раньше, без размножения полных копий шаблонов под каждого партнёра. Решение — поднять версию только того уровня, где реально произошло изменение (например, blockData), не создавая новый «монолитный» шаблон документа.
Почему ключевым оказался каскад версий
Самая опасная ситуация — частично обновлённая конфигурация: макет уже новый, блок ещё старый, значения уже обновлены, и поведение системы можно объяснить только ручным разбором связей.
Поэтому разные уровни версионируются независимо, но согласованно, а публикация становится полноценным процессом со статусами и правилами переходов.
Гибридный период и зачем нужен onlyBlocks
Переход к «конструктору» не происходит мгновенно: какое-то время сосуществуют старые и новые поколения. У нас параллельно жили три сценария:
-
Полностью старый макет.
-
Старый макет с точечным внедрением блоков.
-
Новый макет, который собирается только из блоков.
В этом контексте onlyBlocks — не просто оптимизация, а режим, который позволяет реально работать с блочной композицией и ускоряет цикл «поправил → посмотрел».
Как документ собирается на backend
На практике «конструктор PDF» — это серверный pipeline:
-
Получаем запрос на генерацию документа.
-
Берём исходный
.rptdesign(из кэша или Object Storage). -
Определяем, какой
layoutи какие версииblockDataдолжны участвовать в сборке. -
Находим в шаблоне точки вставки для блоков.
-
Подтягиваем XML нужных блоков и значения для них.
-
Собираем итоговый
.rptdesignи при необходимости кэшируем подготовленную версию. -
Передаём результат в BIRT runtime.
-
Возвращаем готовый PDF.
Условный псевдокод:
report = loadBaseTemplate(partnerCode, productCode, version)anchors = findBlockAnchors(report)blocks = loadBlocksWithValues(layoutVersion, blockDataVersions)preparedReport = injectBlocks(report, anchors, blocks)pdf = birtRuntime.render(preparedReport)return pdf
Управляемая публикация: как бизнес меняет документ безопасно
Если бизнес может свободно менять структуру, риски смещаются из «код-ошибок» в «ошибки конфигурации/контента». Чтобы удержать систему управляемой, мы ввели процесс и ограничения.
Процесс
-
Draft— бизнес создаёт черновик конфигурации и контента, собирает структуру, проверяет предпросмотр (в т.ч.onlyBlocks). -
Product review— продукт ревьюит визуальный результат и корректность (бренд / читабельность / логика документа). -
Publish— публикация создаёт версию, которая становится доступна для прод-генерации.
Guardrails
Так как обязательных блоков нет, важны автоматические проверки:
-
Валидация входной конфигурации: корректность
blockNameOrder, допустимость комбинаций версий, валидность ссылок/изображений и типов данных. -
Проверка совместимости версий
layout/block/blockDataна этапе публикации (матрица соответствий версий). -
Предсказуемый предпросмотр: бизнес видит итоговый PDF до публикации и не выпускает «сломанный» документ.
Импорт/экспорт и безопасность
Как только появляются версии, блоки, связанные значения, изображения и зависимости, становится критичным переносить конфигурацию между средами (DEV → TEST → PROD) без ручной сборки.
Поэтому в платформе появляется импорт/экспорт:
-
блоки едут вместе со связанными сущностями;
-
значения и изображения не теряются;
-
связи сохраняются;
-
макеты можно переносить отдельно.
Безопасность тоже перестаёт быть факультативной. Когда в системе есть XML, архивы и пользовательские файлы, нужны:
-
безопасный XMLпарсинг (защита от XXE);
-
контроль путей внутри архивов (zip slip);
-
валидация загружаемых файлов;
-
контроль целостности зависимостей при импорте.
Что измеряем, если цель — user-friendly, а не скорость
Если задача — делегировать изменения бизнесу, ключевые метрики должны быть продуктовыми:
|
Метрика |
Что показывает |
|---|---|
|
Lead time изменения |
От идеи/правки до опубликованной версии |
|
Доля self-service изменений |
Процент правок, сделанных бизнесом без участия разработки (по событиям draft / review / publish) |
|
Количество откатов |
И причины: ошибка данных, визуальная ошибка, неверный порядок блоков, несовместимость версий |
|
Правки после review |
Индикатор качества интерфейса и guardrails |
Заключение
Мы не отказались от BIRT и не переписывали рендеринг с нуля. Мы добавили предметный backend-слой, который превращает набор шаблонов в платформу: документ стал композицией версий макета, блока, данных блока и справочников значений, плюс правила публикации и переноса между средами.
Такой подход делает генерацию страховых полисов управляемой: изменения можно выпускать безопасно и предсказуемо, не превращая каждый новый вариант документа в очередную копию.
Если у вас тоже есть BIRT, Jasper или любая другая шаблонная платформа — и вы уже чувствуете, что модель «один кейс — один шаблон» больше не работает — возможно, вам нужен не новый рендерер, а нормальный слой композиции, версионирования и публикации поверх текущего движка.
ссылка на оригинал статьи https://habr.com/ru/articles/1032464/