|
Professor Seleznov
|
Я 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 или любая другая шаблонная платформа — и вы уже чувствуете, что модель «один кейс — один шаблон» больше не работает — возможно, вам нужен не новый рендерер, а нормальный слой композиции, версионирования и публикации поверх текущего движка.-Источник
|