Продолжаем серию про redb.Route — вводная и разбор четырёх in-memory каналов уже вышли. Сегодня не статья серии, а релизная заметка: в 3.0.1 три конкретных изменения в DSL, каждое с боевым примером из демо.
До 3.0.1 глубоко вложенные scope-ы требовали закрывать себя в строго обратном порядке — утомительно и легко ошибиться. Три вещи изменились.
Плоская навигация через End*()
Typed-closer теперь идёт вверх по цепочке Parent и выходит на нужный уровень за один вызов:
From("direct://demo-cascade-endchoice") .Choice() .When(e => true) .Split(e => new object?[] { 1, 2, 3 }) .Process(e => { /* работа с элементом */ }) .Log("item=${body}") .EndChoice() // проходит мимо Split → When → возвращается в корень маршрута .SetHeader("post-cascade", "ok") .Log("каскад завершён");
Универсальный .End() выходит к ближайшему scope, не называя его:
.Choice() .When(e => true) .Split(e => new object?[] { "a", "b" }) .Log(LogLevel.Information) .Message("inside") .End() // закрывает RichLog → возвращает тело Split .End() // закрывает Split → возвращает тело When .EndChoice() // закрывает Choice → возвращает корень маршрута
Соседние ветки открываются естественно после закрытого sub-scope — .When() и .Otherwise() как extension methods находят ближайший ChoiceDefinition через Parent, поэтому это компилируется как есть:
.Choice() .When(e => e.In.Body is IEnumerable<string> && e.In.Body is not string) .Split(...) .Process(...) .EndSplit() .Log("ветка list завершена [${routeId}]") // всё ещё на теле When, не на Split .When(e => e.In.Body is string s && s.Length > 0) // ← соседняя ветка после EndSplit .Process(e => { /* ... */ }) .Otherwise() .Process(e => { /* fallback */ }).EndChoice()
Три формы логирования — в одном pipeline-шаге
Обновлённое демо показывает все три рядом — полезно понять, где что уместно:
// (A) Лямбда — произвольный C# в рантайме.Log(e => $"[lambda] item={e.In.Body} branch={e.In.Headers["branch"]}")// (B) Строковый шаблон — компилируется expression-движком,// ноль аллокаций когда уровень логирования выключен.Log("[tmpl] item=${body} branch=${header.branch} [${routeId}]")// (C) Rich-log scope — структурированный, несколько сообщений,// заголовки и свойства выводятся как отдельные поля.Log(LogLevel.Information) .Message("[rich-tmpl] item=${body}") .Message(e => $"[rich-lambda] upper={((string)e.In.Body!).ToUpperInvariant()}") .Header("branch") .Property("item-index") .ShowRouteId(true).EndLog()
${body}, ${header.x}, ${property.y}, ${routeId}, ${exception.type}, ${exception.message} — всё разрешается скомпилированным expression-движком (Tokenizer → Parser → AST → IL). Шаблон компилируется один раз, дальше исполняется как закэшированный делегат. .Message() в rich-log scope принимает обе формы одновременно.
То же самое работает внутри catch-блока:
.TryCatch() .Process(e => throw new InvalidOperationException("boom")).DoCatch<InvalidOperationException>() .Log("[tmpl] caught: ${exception.type} — ${exception.message} [${routeId}]") .Log(LogLevel.Warning) .Message(e => $"[lambda] stack: {e.Exception?.StackTrace?.Split('\n').First().Trim()}") .ShowRouteId(true) .EndLog().EndTryCatch()
CRTP-база — удалено 27 дублирующихся тел классов
Каждый leaf-метод (To, Process, SetBody, Filter, Split, Transaction, ~40 штук) раньше копировался в каждый scope-класс. Теперь все они живут один раз в RouteDefinitionBase<TSelf>. Каждый метод возвращает TSelf — цепочка всегда остаётся на конкретном типе текущего scope. Публичный API и AST маршрута идентичны 3.0.0. Чистый внутренний рефакторинг — но именно он разблокировал плоскую навигацию выше.
Фикс: GetContext() тихо возвращал null внутри вложенных scope-ов
IRouteDefinition.GetContext() делал cast к RouteDefinition, который совпадал только с корнем маршрута. Внутри любого вложенного scope — When, Loop, Traced, Catch — метод возвращал null без исключения. Теперь он идёт вверх по Parent до owning-маршрута. Важно, если у вас есть extension methods, которые читают контекст на этапе построения DSL.
Полное демо со всеми четырьмя примерами — DeepDslShowcaseRoutes.cs. Подробный список изменений — в CHANGELOG.md. Всё под Apache 2.0.
ссылка на оригинал статьи https://habr.com/ru/articles/1043332/