redb ecosystem
redb ecosystem

Мажор всей экосистемы: что появилось в redb.Core, redb.Route, redb.Tsak и redb.Identity, что закрыто по безопасности и что ломает обновление.

Много работы было сделано ночами при свечах. Обратная связь от людей давала пищу для размышлений и фиксов: комментарии к статьям, обсуждения на 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: WireTapChoiceSplitAggregateEnrichClaimCheckIdempotentConsumer.

Весь каталог бесплатно доступен на сайте авторов, у каждого шаблона есть схема и короткое объяснение: 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 с ReplaceFromMockEndpoints("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: регулярные выражения, последовательности, ifforsome и every.

Движок

  • Один язык выражений. Условия, значения и ${...}-шаблоны идут через одну грамматику и один компилятор вместо трёх движков. Битое условие роняет Start(), а не превращается в константу на живом трафике. В языке появились stats(target, metric) со статистикой эндпоинтов, format(value, pattern, culture)uuid() и оператор %.

  • Компонент bean:. Пользовательский код как обычный эндпоинт: объект берётся из реестра контекста или создаётся с DI через конструктор, параметры URI биндятся на его свойства.

  • Перехваты и завершение в форме Apache Camel: InterceptInterceptFromInterceptSendToEndpointOnCompletion.

  • Агрегация без лямбд. Библиотека готовых AggregationStrategies (сбор тел в список, конкатенация, сумма, минимум и максимум по выражению, слияние в заголовок или свойство), Enrich без стратегии, throttle с лимитом из сообщения. AggregateRecipientListDynamicRouterIdempotentConsumerEnrich и 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: IssueValidateCancel и Renew поверх SOAP для систем, которые строят клиента по WSDL. Издатель, реестр клиентов и хранилище токенов общие, так что клиент, зарегистрированный по HTTP, получает токен по SOAP. Без TLS фасад не стартует, есть mTLS с закреплением отпечатка сертификата.

  • Audience по RFC 9068 в access-токенах. Ресурсы выданных scope и новое поле приложения AccessTokenAudiences попадают в aud, а management API принимает только токены со своим audience.

  • Контракты отдельным пакетом-носителем. Горячая перезагрузка одного модуля больше не расщепляет общие DTO на две копии, из-за которых типизированное тело терялось по дороге через direct-vm://.

  • Уникальность на примитивах redb.Core. На MSSQL ключи ClientIdScopeName и 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 целиком (EnableLazyLoadingForPropsWithLazyLoading(), параметр lazyLoadProps). objects.value_string ограничен 450 символами, и база с более длинными значениями откажет в апгрейде со списком объектов, которые нужно перенести в _note или Props. Обращение к Props ссылки за пределом глубины теперь идёт в базу; в Blazor WebAssembly для этого есть LazyReferenceAccess = Throw. Сборки, скомпилированные против 3.x, нужно пересобрать.

  • redb.Route: строковые LoopExpressionDelayExpression и ThrottleExpression стали LoopDelay и 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.0net9.0 и net10.0, хостовые приложения и образы собраны на .NET 10. Tsak и Identity поставляются архивами под Windows и Linux (redb-tsakredb-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, ещё — на Хабре.