redb 4.0: XML-маршруты, ленивые ссылки, уникальные ключи и WS-Trust в одном мажоре
Много работы было сделано ночами при свечах. Обратная связь от людей давала пищу для размышлений и фиксов: комментарии к статьям, обсуждения на GitHub, отчёты с живых стендов. И вот, отработав по-стахановски, выпускаю 4.0.0.
Это мажор всей экосистемы разом. Хранилище redb.Core, интеграционный движок redb.Route, рантайм redb.Tsak и OpenID-сервер redb.Identity выходят одним номером: 76 пакетов на NuGet (у 3.7 было 66), семь образов в GHCR, архивы под Windows и Linux. Pro-издание по-прежнему бесплатно и не требует лицензионного ключа на всей линии 4.x.
Изменений столько, что подробный разбор любого из продуктов тянет на отдельную статью, и такие статьи будут. Здесь коротко, почти списком: что появилось, что закрыто по безопасности и что нужно знать перед обновлением. Полные списки изменений лежат на redb.ru/releases.
redb.Route: маршруты в XML, тест-кит и форматы данных
Десять новых пакетов и заметная перестройка самого движка.
Общий словарь: книга EIP
Wire Tap, Content-Based Router, Splitter, Aggregator, Content Enricher, Claim Check, Idempotent Receiver: ни одно из этих названий не придумано для redb.Route. Все они из книги Грегора Хопе и Бобби Вулфа «Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions» (Addison-Wesley, 2003), каталога из 65 шаблонов обмена сообщениями. На этом каталоге построены Apache Camel, WSO2 Micro Integrator и redb.Route, поэтому он работает как общий словарь интеграции: узнав шаблон по названию, вы узнаете его в любом из этих инструментов. Глаголы DSL в redb.Route следуют написанию Camel: WireTap, Choice, Split, Aggregate, Enrich, ClaimCheck, IdempotentConsumer.
Весь каталог бесплатно доступен на сайте авторов, у каждого шаблона есть схема и короткое объяснение: enterpriseintegrationpatterns.com. Начинать стоит с глав о маршрутизации (Message Routing) и преобразовании сообщений (Message Transformation): форматы разных партнёров, трансляторы между ними и единая точка приёма разобраны там шаблон за шаблоном.
Новые пакеты
redb.Route.Xml. Маршрут описывается декларативно в.route.xmlи загружается в тот же fluent DSL, что пишется на C#. Документ разбирается целиком при загрузке, и все ошибки приходят одним списком: неизвестный элемент с подсказкой, битое выражение, незарегистрированная схема эндпоинта. XSD генерируется из реестра элементов,context.xmlописывает компоненты и бины контекста, генератор печатает XML обратно в C#. Хранилище redb доступно прямо из разметки через<redbGet>и<redbQuery>.redb.Route.TestKit. Тест маршрута без брокеров и без правок в самом маршруте:AdviceRouteсReplaceFrom,MockEndpoints("kafka://*"),MockEndpointс ожиданиями и сценарными ответами,NotifyBuilder. От тестового фреймворка не зависит.redb.Route.Templates. Тело сообщения из шаблона Scriban или Liquid. Тип результата задаёт экранирование подставленных значений, шаблон компилируется приStart(), песочница не даёт вызывать методы.redb.Route.DataFormats.Csv,.Protobuf,.Avro,.Yaml, плюс Base64, GZip и Zip в самом redb.Route. У Protobuf и Avro есть обрамление Confluent.redb.Route.JsonTransform. Преобразование JSON в JSON на JSONata с нативным .NET-движком.redb.Route.Cache. Кэш как EIP: скоупCache(key, ttl)…EndCache()и компонентcache:, в памяти процесса или через любойIDistributedCache.redb.Route.XPath2. XPath 2.0 для того, чего нет в 1.0: регулярные выражения, последовательности,if,for,someиevery.
Движок
- Один язык выражений. Условия, значения и
${...}-шаблоны идут через одну грамматику и один компилятор вместо трёх движков. Битое условие роняетStart(), а не превращается в константу на живом трафике. В языке появилисьstats(target, metric)со статистикой эндпоинтов,format(value, pattern, culture),uuid()и оператор%. - Компонент
bean:. Пользовательский код как обычный эндпоинт: объект берётся из реестра контекста или создаётся с DI через конструктор, параметры URI биндятся на его свойства. - Перехваты и завершение в форме Apache Camel:
Intercept,InterceptFrom,InterceptSendToEndpoint,OnCompletion. - Агрегация без лямбд. Библиотека готовых
AggregationStrategies(сбор тел в список, конкатенация, сумма, минимум и максимум по выражению, слияние в заголовок или свойство),Enrichбез стратегии, throttle с лимитом из сообщения.Aggregate,RecipientList,DynamicRouter,IdempotentConsumer,EnrichиPollEnrichпринимают выражение, а не только делегат. - REST DSL в
redb.Route.Http.Rest("/api/orders").Get("/{id}")с биндингом JSON,Consumes/Producesи OpenAPI 3.0.3 по адресу{basePath}/openapi.json. - Метрики изнутри маршрута.
IExchange.ContextиUseMetricsSnapshot(): слой OpenTelemetry читается прямо в процессе. - Без Newtonsoft.Json.
jpathпереехал на JsonPath.Net (RFC 9535) поверх System.Text.Json, и в зависимостях redb.Route осталась одна JSON-модель.
Нагрузка и хостинг
- Лимиты приёма на эндпоинт. HTTP, SOAP, AS2 и gRPC получили
maxConcurrentRequestsи очередь ожидания: лишний запрос получает429сRetry-Afterдо запуска конвейера, а соседний маршрут на том же порту свой бюджет не теряет. Отказы идут в отдельный счётчикRejected, так что сброс нагрузки не путается с ошибками. concurrentConsumers=autoу RabbitMQ, AMQP 1.0, IBM MQ, SQS и MQTT: число потребителей по числу процессоров, но не меньше двух. По умолчанию по-прежнему один, чтобы сохранялся порядок сообщений.- WebSocket и SignalR на общем Kestrel рядом с HTTP, gRPC, SOAP и AS2: один порт, аутентификация рукопожатия от хоста, шов для backplane при масштабировании.
- Доверенные прокси на общем хосте.
X-Forwarded-Forпроходится справа налево через все перечисленные прокси,X-Forwarded-Protoвосстанавливает схему клиента, и каждый транспорт на хосте видит реальный адрес. Эту доработку подсказал комментарий к статье про 3.7.
Коннекторы
- Kafka: коммит при отзыве партиции больше не теряет прочитанную, но не обработанную запись;
seekToотрабатывает по каждой партиции. - S3: консюмер больше не теряет объекты, настройки подключения доходят до AWS SDK.
- Firebase: FCM multicast и управление топиками, копирование и подписанные URL в Storage, потоковое скачивание,
databaseIdдля проектов Firestore с несколькими базами. - Llm: кэширование системного промпта у Anthropic со счётчиками токенов из кэша, фиксированная преамбула перед историей диалога, HTTP/2 keep-alive для длинных ответов через VPN и NAT, распознавание речи через
stt://и скачивание файлов из Telegram. - SOAP: консюмер обслуживает TLS, маршрут знает, кто его вызвал, и сам выбирает код fault.
- TLS без сертификата больше нигде не открывает незашифрованный порт: WebSocket, SignalR и TCP в такой конфигурации отказываются стартовать.
redb.Core(.Pro): ленивые ссылки и уникальные ключи
Всё ниже работает одинаково на PostgreSQL, MSSQL и SQLite, во Free и в Pro.
- Ленивые ссылки. Ссылка на границе глубины приходит заглушкой с
id, схемой и хешем, и первое обращение кPropsзагружает ровно этот объект. Ссылку, которая должна быть ленивой на любой глубине, помечаютvirtualи включаютEnableLazyReferences. Коллекция заглушек дозагружается одним запросом черезLoadReferencesAsync, без N+1. Сохранение родителя, расчёт хеша и сериализация заглушки не будят. - Ключ объекта
ValueUnique. Читаемая строка (номер заказа, артикул, внешний идентификатор), уникальная в рамках схемы, и upsert по ней черезSaveByUniqueAsync. [RedbUnique]на полях Props. Уникальность силами индекса базы для скаляров любого типа, включаяdecimal, даты иbyte[]; для полей вложенных классов; для вложенного класса, массива или словаря целиком, где ключом становится содержимое поддерева; для элементов коллекции внутри объекта или по всей схеме. ПоискGetByUniqueAsyncзанимает одну пробу индекса, нарушение на трёх СУБД поднимается однимRedbUniqueViolationException. Дубли в уже накопленных данных попадают в отчёт, старт не падает.- Обновление схемы базы по контракту. Модуль схемы применяется при старте приложения. Если у роли нет прав менять схему, старт останавливается типизированным
RedbSchemaOutdatedException, а скрипт для DBA отдаютGetUpgradeScript()иredb schema --upgrade. CancellationTokenво всём асинхронном API. ОтменённыйSaveAsyncоткатывается полностью, а после коммита токен больше не читается: «отменено» никогда не означает «но всё-таки сохранено».- Интерцепторы записи
IRedbSaveInterceptorв духе EF:SavingAsyncможет поправить объект или отменить сохранение,SavedAsyncвидит итог, то же самое для удаления. Под ChangeTracking интерцептор получает применённый дифф с путями свойств, из которого собирается собственный журнал аудита. - Уровень изоляции транзакции по запросу через
BeginTransactionAsync(IsolationLevel)иDbErrorClassifier.IsSerializationFailure, чтобы повторять сериализуемые транзакции. Maintenance:AnalyzeAsyncобновляет статистику планировщика одним вызовом на любой СУБД,GetIndexStatsAsyncчитает состояние индексов.LoadJsonAsync(id, depth)отдаёт объект сырым JSON без CLR-типа, аToString()уRedbObjectвозвращает тот же канонический JSON.byte[]хранится одним BLOB, а не строкой на каждый байт; старая раскладка конвертируется сама. Схема помнит namespace своего типа, и дваOrderиз разных проектов больше не усыновляют схему друг друга.- Производительность: индексы на FK-колонках
_values, ускорение ChangeTracking, хеши в SQLite какBLOB(16), индекс на_value_stringтеперь и на MSSQL. - Исправления, которые касались 3.x: сохранение родителя со ссылкой, заданной только по
id, затирало объект за ссылкой;DateOnlyна существующих базах читался как0001-01-01; MSSQL обрезал вложенные ссылки в результатах LINQ на глубине 1, тогда как PostgreSQL и SQLite брали 10.
redb.Tsak: кластер на ключах базы и модули из XML
- Кластер держится на уникальных ключах базы. Блокировки, узлы, группы и назначения модулей опираются на частичные уникальные индексы redb.Core. Два узла, стартующие на пустой базе, больше не создают два корня кластера: проигравший гонку принимает победителя. Запись назначения защищена номером эпохи лидера, и устаревший лидер не может посадить один модуль на два узла. Существующая база дозаполняет ключи сама при первом старте.
- Модули из XML.
.tpkgможет нести только.route.xmlиcontext.xmlбез единой сборки, и такой пакет перезагружается на лету так же, как модуль на C#. XML-маршрут в воркере видит все элементы разметки из shared-слоя:<cache>,<rest>,<redbGet>. - Дашборд различает сброс нагрузки и тишину. Панель Shedding Routes показывает маршруты, упёршиеся в лимит приёма, у эндпоинтов появились
Rejectedи Bytes Out. Автообновление страниц переживает разовую ошибку, графики мониторинга не замирают под курсором. - Страница About: версия дашборда и узла, аптайм, а для администратора список реально загруженных сборок redb.
- Доверенные прокси:
Tsak:Http:TrustedProxiesпринимает адреса и CIDR-сети, после чего троттлинг API-ключей и модульные маршруты видят реального клиента за цепочкой прокси. - Страницы Audit и Dead-letter работают на PostgreSQL. Вместе с ними на PostgreSQL впервые заработала ежедневная очистка
tsak_audit_log, поэтому на узле, давно живущем на Postgres, перед первым прогоном стоит посмотреть размер этой таблицы. - Протокол кластера: heartbeat больше не откатывает одновременный cordon, узел в cordon не получает новых модулей, кластерный маршрут после остановки через API снова стартует.
- Поставка под кластер: на общей базе Quartz в конфигурации по умолчанию работает в кластерном режиме (на SQLite он выключается сам, с предупреждением в логе), а
ApiEndpointузла определяется автоматически вместо общего для всехlocalhost:9090.
redb.Identity: WS-Trust, audience в токенах и консоль из страниц
- WS-Trust фасад
redb.Identity.Soap. Третий транспорт после HTTP и gRPC:Issue,Validate,CancelиRenewповерх SOAP для систем, которые строят клиента по WSDL. Издатель, реестр клиентов и хранилище токенов общие, так что клиент, зарегистрированный по HTTP, получает токен по SOAP. Без TLS фасад не стартует, есть mTLS с закреплением отпечатка сертификата. - Audience по RFC 9068 в access-токенах. Ресурсы выданных scope и новое поле приложения
AccessTokenAudiencesпопадают вaud, а management API принимает только токены со своим audience. - Контракты отдельным пакетом-носителем. Горячая перезагрузка одного модуля больше не расщепляет общие DTO на две копии, из-за которых типизированное тело терялось по дороге через
direct-vm://. - Уникальность на примитивах redb.Core. На MSSQL ключи
ClientId,ScopeNameиReferenceIdвпервые защищены индексом, а внешняя учётная запись больше не может гонкой привязаться к двум локальным пользователям. - Консоль администратора из страниц вместо диалогов. У провайдеров федерации появилась полноценная страница с маппингом claims, у маппера claims выбирается владелец правила, пользователь создаётся мастером, добавились каталог ресурсов API и дашборд с KPI и аудитом по категориям.
- DPoP за прокси, терминирующим TLS, и правильный
client_idв аудите для клиентов, которые передают учётные данные в теле формы.
Безопасность
Часть пунктов пришла внешними отчётами, часть найдена собственным аудитом фасадов и дашборда. Механика каждого будет в разборах по продуктам. Если у вас в проде 3.x, этот список сам по себе причина обновиться.
- redb.Identity: HTTP-заголовок позволял выдать себя за любого пользователя. Внутренние заголовки теперь вычищаются на входе всех трёх фасадов: HTTP, gRPC и SOAP.
- redb.Identity: страница согласия брала имя приложения и список scope из адреса, а форму согласия можно было отправить за другого пользователя без его сессии. Теперь страница строится из подписанного сервером билета, пользователь берётся только из сессии, формы проверяют
Originи не встраиваются во фрейм. - redb.Identity: introspect отвечал любому аутентифицированному клиенту по любому токену. Теперь только тем, кто входит в audience токена.
- redb.Tsak: страницы дашборда открывались без входа. Теперь закрыта каждая страница, а уровни доступа совпадают с API воркера: аудит и пользователи для Admin, dead-letter и логи для Operator.
- redb.Tsak: подпись модуля проверяется на всех путях загрузки, включая старт процесса, и загружаются ровно проверенные байты. Пароли не попадают в журнал аудита, сессии перепроверяются каждые пять минут, троттлинг входа считает пару из логина и адреса.
- redb.Core: хешер паролей по умолчанию теперь bcrypt. Старые хеши SHA256 проходят проверку как раньше и переходят на bcrypt при следующей смене пароля.
Что ломает мажор
Список короткий, но пройти его стоит до обновления.
- Кластеры redb.Tsak и redb.Identity обновляются остановкой. Узлы одной группы не должны работать на разных версиях: остановить всю группу, развернуть 4.0, запустить. Первый старт дозаполнит ключи.
- Большой базе нужно окно обслуживания. Апгрейд добавляет колонки и строит частичные индексы под эксклюзивными блокировками. Удобнее заранее применить
GetUpgradeScript()с одного узла и стартовать приложение сAutoApplyDatabaseUpgrades = false. - redb.Core: удалена ленивая загрузка Props целиком (
EnableLazyLoadingForProps,WithLazyLoading(), параметрlazyLoadProps)._objects._value_stringограничен 450 символами, и база с более длинными значениями откажет в апгрейде со списком объектов, которые нужно перенести в_noteили Props. Обращение кPropsссылки за пределом глубины теперь идёт в базу; в Blazor WebAssembly для этого естьLazyReferenceAccess = Throw. Сборки, скомпилированные против 3.x, нужно пересобрать. - redb.Route: строковые
LoopExpression,DelayExpressionиThrottleExpressionсталиLoop,DelayиThrottle, аSetBodyExpression("…")и соседи сталиSetBody(Expr("…")). Newtonsoft.Json больше не приезжает транзитивно.${...}форматирует числа и даты инвариантно,${...}в адресе консюмера роняетStart(), а ноль,"0","no"и"off"в условиях теперь ложь. - redb.Identity: backend, который интроспектирует токены другого клиента (классическая пара SPA и BFF), нужно добавить в
AccessTokenAudiencesэтого клиента. Таблица аудита на SQLite сменила тип колонки времени, поэтому файлы аудита SQLite перед обновлением удаляются.
Как поставить
dotnet add package redb.Core
dotnet add package redb.Postgres # или redb.MSSql / redb.SQLite
dotnet add package redb.Postgres.Pro # Pro, бесплатно и без ключа
dotnet add package redb.Route
dotnet add package redb.Route.TestKit
Библиотеки таргетят net8.0, net9.0 и net10.0, хостовые приложения и образы собраны на .NET 10. Tsak и Identity поставляются архивами под Windows и Linux (redb-tsak, redb-identity) и образами в GHCR. Для первого знакомства с Tsak проще всего образ redb-tsak-stack, где воркер и дашборд живут в одном контейнере.
Пакеты: nuget.org/profiles/relikt. Исходники: github.com/redbase-app.
Что дальше
По каждому продукту выйдет отдельный разбор: redb.Core с кодом по ленивым ссылкам и ключам, redb.Route с тест-китом и XML-маршрутами, Tsak и Identity со своими историями. Для XML-маршрутов готовится расширение VS Code с текстовым режимом и графовым редактором.
Спасибо всем, кто писал комментарии, открывал обсуждения и присылал отчёты с продакшена: заметная часть этого релиза выросла именно оттуда.
Если было полезно, ⭐ на GitHub поможет другим это найти.
Другие мои статьи — redb.ru/articles, ещё — на Хабре.