
Про Kafka в этой серии уже было. Теперь — RabbitMQ, и с упором на то, как им пользоваться. Коннектор redb.Route.RabbitMQ — поверх официального RabbitMQ.Client 7.x, но писать вам придётся не «клиент», а маршруты, где весь брокер задаётся одной строкой-URI:
From("rabbitmq://orders?exchange=shop&exchangeType=topic&routingKey=order.*&concurrentConsumers=4") .Log("Order in: ${body}") .To("direct://process");
Прочитали — и уже знаете, что происходит: слушаем очередь orders, привязанную к topic-обменнику shop по маске order.*, в четыре потока, логируем и отдаём дальше. Ни «створи ченнел», ни «забинди», ни «поставь QoS» — всё это коннектор.
Про «уходим от MassTransit» сразу оговорюсь честно: дело не в том, что MassTransit чего-то не умеет — умеет, и RPC (request/response через IRequestClient<T>) в том числе. Разница в модели. MassTransit — это шина с контрактами сообщений, консьюмерами и конфигурацией автобуса: покупаешь всю его картину мира целиком. redb.Route — это явные маршруты в духе Apache Camel: endpoint — строка-URI, паттерны интеграции — шаги маршрута, транспорт — абстракция под ними. Меньше церемоний с контрактами и регистрациями, больше явного «откуда → куда → что по дороге». Что удобнее — вопрос вкуса и задачи; эта статья — про то, как RabbitMQ выглядит во второй модели.
Дальше — как этим пользоваться: анатомия URI, все параметры справочником (что и зачем), заголовки, которые ставит консьюмер, конкурирующие консьюмеры (многопоточность), широковещание по нодам кластера (реальный пример из прода), RPC (у RabbitMQ он получается особенно изящным) и два EIP-паттерна, которые на брокер ложатся идеально — Filter (на компилируемых предикатах) и Dead Letter Channel.
Весь код — на английском, весь текст — по-русски. Примеры не выдуманные — это redb.Route/demos/redb.Route.Demo/Routes, тот же демо-проект, что гоняется в CI.
Цикл про redb и redb.Route. Это продолжение серии, свежие статьи — сверху:
redb.Route — уходим от MassTransit, идём к Apache Camel: Kafka, Scatter‑Gather и транзакции
Apache Camel под .NET: HTTP-коннектор без ASP.NET MVC + Content-Based Router
redb.Route — Apache Camel для .NET, который мы написали потому что выхода другого не было
Полный список — в профиле. Исходники: github.com/redbase-app. Про саму БД: redb.ru.
Анатомия URI
Endpoint — это строка:
rabbitmq://queue-name?host=localhost&exchange=my-exchange&exchangeType=topic&routingKey=order.*
Схема rabbitmq://, хост URI — имя очереди, дальше query-параметры (имена — в таблице ниже). Одна и та же строка работает и консьюмером (в From(...)), и продюсером (в .To(...)) — роль определяется тем, где endpoint стоит в маршруте. Подключение можно не дублировать: заведите фабрику подключения в реестре и сошлитесь через connectionFactory=имя — host/креды/SSL лягут в одно место, соединение переиспользуется.
Важная деталь: значения параметров — это шаблоны. routingKey=order.new — константа, а routingKey=order.${header.type} — динамика: ключ вычисляется на каждое сообщение из его заголовка (тот же механизм, что и ключ партиционирования у Kafka). Так что «строка» здесь не значит «только статика» — ${...} внутри значения превращает его в выражение.
Про флюент. У коннектора есть и билдер
Rabbit.Queue("orders")…, но он expression-first: строковые методы принимают выражение, чтобы значение продюсера можно было вычислять на каждое сообщение (.RoutingKey(Header("order.type"))— динамический ключ, как у Kafka). Для константы там оборачивают (.Host(Constant("localhost"))). В этой статье я пишу через URI — для статичной конфигурации это короче и нагляднее; про динамику через выражения — отдельно.
Все параметры
Главная причина сохранить статью в закладки. Имена — как в URI, дефолты — из коробки.
Подключение
Параметр |
Дефолт |
Зачем |
|---|---|---|
|
Хост брокера |
|
|
|
Порт AMQP |
|
|
Креды |
|
|
Виртуальный хост — изоляция ресурсов внутри брокера |
|
— |
Имя фабрики в реестре: настройки в одном месте, соединение переиспользуется |
|
|
Имя соединения — видно в management UI |
Обменник
Параметр |
Дефолт |
Зачем |
|---|---|---|
|
(пусто) |
Имя обменника; пусто = default exchange (маршрут по имени очереди) |
|
|
|
|
|
Переживает рестарт брокера |
|
|
Удалить, когда не используется |
|
|
Создать обменник+очередь на старте, если их нет |
Очередь
Параметр |
Дефолт |
Зачем |
|---|---|---|
|
(из хоста URI) |
Имя; пусто = авто-генерируемая эксклюзивная |
|
|
Переживает рестарт |
|
|
Удалить, когда ушёл последний консьюмер |
|
|
Допустим только один консьюмер |
Маршрутизация и сообщение
Параметр |
Дефолт |
Зачем |
|---|---|---|
|
(пусто) |
Ключ для publish и bind |
|
|
Content-type сообщений |
|
|
TTL сообщения, мс ( |
|
|
Очередь удаляется после N мс без консьюмеров ( |
Консьюмер
Параметр |
Дефолт |
Зачем |
|---|---|---|
|
|
Единственный рычаг параллелизма — до N сообщений обрабатываются одновременно |
|
|
Префетч на консьюмера; держите ≥ |
|
|
|
|
|
Транзакционный AMQP-канал (несовместим с |
|
(авто) |
Вернуть недоставленное сообщение; по умолчанию |
RPC
Параметр |
Дефолт |
Зачем |
|---|---|---|
|
|
Включить request/reply: публикуем запрос и ждём коррелированный ответ |
|
|
Таймаут ожидания ответа, сек |
|
|
Лимит неподтверждённых publish — чтобы под нагрузкой не спотыкаться о rate limiter клиента |
Ограничения очереди и dead-letter
Параметр |
Дефолт |
Зачем |
|---|---|---|
|
|
Максимум очереди в сообщениях / байтах ( |
|
— |
Переполнение: |
|
— |
DLX — куда уходят «мёртвые» сообщения ( |
|
— |
Ключ при перекладывании в DLX |
|
— |
|
|
|
Максимальный приоритет для priority-очередей |
Устойчивость и TLS
Параметр |
Дефолт |
Зачем |
|---|---|---|
|
|
Авто-восстановление соединения |
|
|
Переобъявить обменники/очереди/binds после восстановления |
|
|
Интервал попыток восстановления, сек |
|
|
Интервал heartbeat («пульс»), сек — брокер и клиент пингуют друг друга, чтобы вовремя заметить мёртвое соединение |
|
|
Не висеть на недоступном узле |
|
|
Включить TLS |
|
— |
SNI и клиентский сертификат для mTLS |
Валидатор на старте не даст выстрелить в ногу: prefetch/concurrentConsumers/timeout должны быть > 0, а autoAck + transacted вместе прямо запрещены.
Свойства AMQP ↔ заголовки: сквозной round-trip
Коннектор мапит свойства сообщения RabbitMQ на заголовки exchange в обе стороны — и на консьюмере, и на продюсере. Консьюмер, приняв доставку, раскладывает BasicProperties по заголовкам; продюсер на отправке читает их обратно и ставит на исходящее сообщение. Значит consume → produce переносит все AMQP-свойства насквозь, а любое из них можно задать/переопределить, просто выставив заголовок.
Именование в два яруса.
Метаданные доставки — стандартного AMQP-имени нет, поэтому префикс redbRmq.:
Заголовок |
Что внутри |
|---|---|
обменник, через который пришло сообщение |
|
|
routing key доставки |
|
флаг повторной доставки (после nack/requeue или падения консьюмера) |
|
тег доставки (используется для ack) |
|
тег консьюмера |
Стандартные свойства сообщения — по общеизвестным именам, без префикса (их и так все знают, и round-trip несёт их как есть):
Заголовок |
Свойство AMQP |
|---|---|
|
correlation-id |
|
reply-to — адрес очереди для ответа |
|
message-id |
|
приоритет 0–9 |
|
TTL сообщения, мс |
|
тип / приложение / пользователь |
|
content-type / кодировка |
|
метка времени |
|
persistent / transient |
Особенно полезен redbRmq.Redelivered: по нему отдельно обработать «а это уже вторая попытка» — увести повторную доставку в другой маршрут вместо бесконечного requeue.
ReplyTo-заголовок ≠ параметрreplyTo. Похожие имена — разные вещи. URI-параметрreplyTo=trueвключает клиентский RPC-режим (см. ниже). А заголовокReplyTo— это само AMQP-свойство reply-to: выставив его на обычной отправке, вы задаёте адрес reply-очереди сообщения напрямую, и продюсер прокинет заголовок в свойство. Всё это работает потому, что маппинг свойств двусторонний — не только консьюмер их читает, но и продюсер уважает.
Рецепт 1: pub/sub
Fanout-обменник рассылает копию всем подписчикам, ключ игнорируется. Продюсер публикует в обменник, каждый консьюмер объявляет свою очередь и биндит:
// издатель — публикует в fanout-обменник (копия всем подписчикам) From("timer://tick?period=5000") .SetBody(_ => BuildEvent()) .To("rabbitmq://events-pub?exchange=events&exchangeType=fanout"); // два независимых подписчика — каждый со своей очередью From("rabbitmq://audit?exchange=events&exchangeType=fanout&declare=true").To("direct://audit"); From("rabbitmq://email?exchange=events&exchangeType=fanout&declare=true").To("direct://email");
Нужна маршрутизация похитрее — берёте exchangeType=topic и routingKey с масками (order.*, #.error). Меняется только тип обменника и ключ.
Рецепт 2: конкурирующие консьюмеры (многопоточность)
Один параметр — concurrentConsumers:
From("rabbitmq://orders?concurrentConsumers=8&prefetchCount=16") // до 8 сообщений параллельно .Process(HandleOrder);
Это единственный рычаг параллелизма консьюмера: он задаёт и AMQP dispatch concurrency у канала, и app-level семафор. Дефолт — 1 (строго серийно, порядок сохранён). Ставите N > 1 — порядок на очереди больше не гарантируется, обработчик должен быть потокобезопасным, а prefetchCount держите ≥ N.
Ремарка для тех, кто щупал коннектор раньше: до версии 3.2.2 concurrentConsumers из-за одной ловушки в RabbitMQ.Client 7.x молча не давал параллелизма (канал был зажат в серийную выдачу). С 3.2.2 это единственный и работающий рычаг — если вы ставили N > 1 и «оно как-то не ускорялось», обновитесь.
Подтверждения — тоже параметром. По умолчанию at-least-once: ack после успешного прохода, nack+requeue при ошибке. Нужна семантика «получил и забыл» — autoAck=true переключает на at-most-once (брокер гасит доставку на выдаче, ошибка не возвращает сообщение).
А если параллелить надо не приём, а обработку внутри маршрута (источник серийный, но работа тяжёлая) — есть ортогональный EIP-шаг .Threads(N). concurrentConsumers масштабирует чтение с очереди, .Threads — обработку; комбинируются свободно.
Рецепт 3: широковещание по нодам кластера (инвалидация локального кеша)
Реальный паттерн из прода. Сервис работает кластером из нескольких нод, у каждой — свой локальный кеш. Пишем в БД на любой ноде — и хотим, чтобы кеш обновился на всех. Классика поверх RabbitMQ: продюсер аудита публикует доменное событие в durable topic-обменник, а каждая нода вешает на него свою временную очередь и слушает.
Продюсер (там, где идёт запись в БД) — по маршруту на тип события, все в один обменник lt.events, каждый со своим routing key. Заголовки проставляем сразу, чтобы подписчики роутили/фильтровали, не разбирая тело. Подключение — через общую фабрику:
// общий stamp: доменные заголовки для подписчиков — маршрутизация без парсинга тела Action<IExchange> stamp = e => { var a = e.In.GetBody<AuditEvent>(); e.In.Headers["CorrelationId"] = a.CorrelationId; e.In.Headers["EntityType"] = a.EntityType; e.In.Headers["EventType"] = a.EventType.ToString(); }; From("direct://audit.entity.created") .RouteId("audit-entity-created") .Process(stamp) .SetBody(e => JsonSerializer.Serialize(e.In.GetBody<AuditEvent>())) .To("rabbitmq://?connectionFactory=RabbitMQ&exchange=lt.events&exchangeType=topic" + "&routingKey=dal.events.created&exchangeDurable=true"); // updated / deleted / batch / error — тот же обменник, routingKey=dal.events.{updated|deleted|…}
Консьюмер на каждой ноде — вот тут вся соль. queue= пустой + autoDelete=true: каждая нода получает свою временную очередь (имя выдаёт сервер), привязанную к общему обменнику. Нода отключилась — очередь исчезла. Значит копию каждого события получает каждая нода:
// created и updated — два консюмера в одну общую логику From("rabbitmq://?connectionFactory=RabbitMQ&exchange=lt.events&exchangeType=topic" + "&routingKey=dal.events.created&queue=&autoDelete=true&expires=300000" + "&concurrentConsumers=2&prefetchCount=10") .RouteId("cache-consumer-created") .To("direct://cache-update"); From("rabbitmq://?connectionFactory=RabbitMQ&exchange=lt.events&exchangeType=topic" + "&routingKey=dal.events.updated&queue=&autoDelete=true&expires=300000" + "&concurrentConsumers=2&prefetchCount=10") .RouteId("cache-consumer-updated") .To("direct://cache-update"); // один процессор на оба потока — обновляет локальный кеш этой ноды From("direct://cache-update") .RouteId("cache-update") .Process(new CacheUpdateProcessor(/* … */));
Ключевое — контраст с Рецептом 2. Там competing consumers: одна общая durable-очередь, работа делится между консюмерами (каждое сообщение достаётся кому-то одному). Здесь наоборот: у каждой ноды своя ephemeral-очередь на общий обменник → широковещание, событие получают все. Один коннектор, противоположная топология — разница в одном параметре (queue=имя&durable=true против queue=&autoDelete=true). Запись в БД на любой ноде → одно событие в обменник → локальный кеш обновился на всех нодах, без опроса и без единой точки отказа.
Рецепт 4: RPC (request/reply)
Вот где особенно приятно. Классический RPC на RabbitMQ — это временная reply-очередь, correlationId, ожидание ответа. Здесь — один параметр на клиенте и ничего на сервере. Вот как это задано в демо (DemoEndpoints.cs):
// сервер — обычный консьюмер, declare создаёт очередь public const string RabbitConsumer = "rabbitmq://demo-rpc-queue?host=localhost&port=5672&username=admin&password=admin&declare=true"; // клиент — replyTo=true включает RPC, timeout=15 — ждать ответ не дольше 15 с public const string RabbitProducer = "rabbitmq://demo-rpc-queue?host=localhost&port=5672&username=admin&password=admin&replyTo=true&timeout=15";
Клиент — обычный .To(...), только он не «выстрелил и забыл», а дождался ответа и поехал дальше уже с ним (MainPipelineRoutes.cs):
.Log("[3-RABBIT] → Sending to RabbitMQ RPC...") .To(RabbitProducer) // публикуем запрос и ЖДЁМ ответ .Log("[3-RABBIT] ← stamp.rabbit=${header.stamp.rabbit}") // ответ уже в exchange
Сервер — просто консьюмер, ему для RPC настраивать не надо ничего. Коннектор сам видит reply-to на входящем сообщении, делает маршрут InOut и отправляет то, что лежит в Out, обратно в reply-очередь с тем же correlationId. Весь «сервер» из демо:
From(RabbitConsumer) .RouteId("demo-rabbit-worker") .Log("[RABBIT-W] ▶ Received: ${body}") .SetHeader("stamp.rabbit", e => $"ok:{DateTime.UtcNow:HH:mm:ss.fff}") .Log("[RABBIT-W] ◀ Stamped, replying"); // reply уходит сам
Ни слова про reply-очередь и корреляцию — клиент отправил запрос и получил обратно заголовок stamp.rabbit, проставленный сервером. Чистый request/reply поверх брокера, без ручной возни с временными очередями.
Тут и виден контраст с MassTransit. Там request/response тоже есть — но это IRequestClient<TRequest>, типизированные контракты TRequest/TResponse и консьюмер, зарегистрированный в автобусе. Мощно, но это отдельный слой абстракций, в который надо войти. Здесь — replyTo=true в URI, а на сервере вообще ничего: обычный консьюмер, коннектор сам увидел reply-to и ответил. Разные подходы к одной задаче: контракты и шина против одного флага на endpoint.
Рецепт 5 (EIP): Filter — пропускаем только нужное
Message Filter — простой и постоянно нужный EIP: пропустить дальше только те сообщения, что удовлетворяют условию, остальные — молча отбросить. В redb.Route это .Filter(...) … .EndFilter() (или короче .End() — это алиас): блок между ними выполняется только для прошедших фильтр, для остальных маршрут на этом заканчивается. И вот тут разворачивается компилируемый предикатный движок — тот самый, что мы обсуждали в теме про выражения.
Условие задаётся тремя способами. Предикатом по заголовку (или телу):
From("rabbitmq://orders?concurrentConsumers=4") .Filter(Header("score").isGreaterThan(50)) // score > 50 .To("rabbitmq://vip-orders") .EndFilter();
Набор предикатов — весь, что ожидаешь: .isEqualTo / .isNotEqualTo / .isGreaterThan / .isLessThan / .isGreaterThanOrEqualTo / .isLessThanOrEqualTo / .isBetween(a, b) / .contains(...). Слева — Header("...") или Body():
.Filter(Header("email").contains("@example")) .To("direct://internal") .EndFilter(); .Filter(Header("grade").isEqualTo("B+")) .To("direct://grade-b") .EndFilter();
Строковым выражением — ${...} резолвится, результат приводится к boolean. Для truthy-проверки — просто значение; для сравнения оберните его в logical(...) (голое ${header.score > 50} внутри шаблона вернёт пустую строку — так писать нельзя):
.Filter("${header.status}") // 'active' → truthy → пропустит .Log("active only") .EndFilter(); .Filter("${logical(header.score > 50)}") // сравнение → "True"/"False" .To("rabbitmq://vip") .EndFilter();
Или лямбдой, когда нужна произвольная логика на C#:
.Filter(ex => ex.In.Body is string s && s.Contains("urgent")) .To("rabbitmq://urgent") .EndFilter();
Фильтровать удобно как раз по тем заголовкам, что консьюмер уже проставил, — например, .Filter(Header("redbRmq.RoutingKey").contains("order")), чтобы в блок попадали только сообщения с нужным ключом маршрутизации.
Как выражение превращается в bool
Под капотом Filter на каждое сообщение вычисляет выражение в object? и приводит к bool — по правилам ConvertToBoolean:
bool → как есть string → "true"/"false" парсятся; иначе непустая строка = true, пустая = false null → false прочее (число/объект, не-null) → true
Отсюда — поведение трёх форм:
Предикат
Header("amount").isGreaterThan(1000)— этоIPredicate, у которого уже готовыйFunc<IExchange,bool>. Коэрция не нужна: настоящий boolean прямо от сравнения. Компилируется в делегат и кэшируется — не рефлексия на каждом сообщении, а быстрый вызов.Строка-шаблон
"${header.status}"—${...}интерполируется в значение, дальше truthy-правила.status="active"→ непустая строка →true(потому в демо и «active is truthy»); а"${header.enabled}"при"false"→bool.TryParse→false. Сравнение прямо в шаблоне ("${header.score > 50}") не считается — вернёт пустую строку (то естьfalse); для сравнения нуженlogical(...), как показано выше.Лямбда
ex => …— что вернули, то и предикат.
То есть «булево выражение» — это либо честный bool от предиката/сравнения, либо truthy-приведение интерполированного значения. Пустая строка, null и "false" — отсекают; непустая строка, число, true — пропускают.
DSL открыт: свой шаг — это extension-метод
Обрати внимание на один момент. Строковой и предикатной форм Filter(string) / Filter(IPredicate) нет в интерфейсе IRouteDefinition — там только канонические Filter(Func<IExchange,bool>) и Filter(IExpression). Строковая форма — это extension-метод, тонкий фасад над каноническим:
// весь строковый overload целиком (redb.Route/…/RouteDefinitionCamelDslExtensions.cs): public static FilterDefinition Filter(this IRouteDefinition self, string simpleTemplate) => self.Filter(new StringExpression(simpleTemplate)); // оборачиваем строку в шаблон-выражение
Отсюда важное следствие: DSL расширяемый, и свой шаг добавляется ровно так же — extension-методом с делегированием в канонический. Захотелось собственный «глагол» в маршруте — пишете:
// свой шаг: пропустить только "urgent" в теле public static FilterDefinition OnlyUrgent(this IRouteDefinition self) => self.Filter(ex => ex.In.Body is string s && s.Contains("urgent")); // и в маршруте он выглядит как родной: From("rabbitmq://orders") .OnlyUrgent() .To("rabbitmq://urgent") .EndFilter();
Никакой магии и никаких правок в движке — просто метод расширения над IRouteDefinition. Ровно так же в самом фреймворке сделаны и Camel-совместимые формы, и строковые шаблоны, и предикатные overload'ы. То есть если вам не хватает какого-то шага — вы его дописываете сами, не форкая коннектор.
Здесь мы задели только край выражений — Header(...), Body(), ${...}, logical(...). А вообще выражения в redb.Route — это отдельный компилируемый язык: доступ к телу и заголовкам, свойства exchange, индексаторы (items[0]), строковые функции, JSONPath и XPath. Всё это токенизируется, парсится в AST и компилируется в делегат (System.Linq.Expressions, не рефлексия), с кэшем. Тема большая — про сам движок будет своя статья, он того заслуживает.
Рецепт 6 (EIP): Dead Letter Channel
Из паттернов Хопе и Вульфа на RabbitMQ идеально ложится Dead Letter Channel — потому что у брокера это встроенная фича (x-dead-letter-exchange), а не эмуляция в коде. Идея: сообщение, которое не обработалось (или протухло по TTL, или не влезло в лимит очереди), не теряется и не крутится вечно в requeue, а уезжает в «морг» — отдельный dead-letter обменник, откуда его можно спокойно разобрать.
Настраивается прямо в URI рабочей очереди:
// рабочая очередь: при nack без requeue / протухании / переполнении — сообщение уходит в DLX From("rabbitmq://orders?declare=true" + "&deadLetterExchange=orders.dlx" // куда + "&deadLetterRoutingKey=orders.failed" // с каким ключом + "&messageTtl=30000" // не обработали за 30 с — в DLX + "&overflow=reject-publish-dlx&maxLength=100000" // и переполнение туда же + "&concurrentConsumers=4") .Process(HandleOrder); // «морг»: отдельный маршрут слушает DLX — логирует и складывает в redb для разбора From("rabbitmq://orders.dead?declare=true&exchange=orders.dlx&exchangeType=direct&routingKey=orders.failed") .Log("[DLQ] dead-lettered: ${body}") .ProcessWithRedb((redb, ex) => redb.SaveAsync(BuildDeadLetter(ex)));
Никакого самописного retry-цикла и «поспал-повторил» — надёжность отдаётся брокеру, а у вас остаётся чистый маршрут-разгребатель, который пишет проблемные сообщения в базу. И очередь не разбухает под наплывом: лишнее сразу уезжает в DLX, а не копится в памяти брокера.
Итог
RabbitMQ в redb.Route — это маршруты, где весь брокер задан одним URI: From("rabbitmq://…") / .To("rabbitmq://…"), а вся возня с каналами, QoS, биндами, reply-очередями и dead-letter спрятана за параметрами строки. Обменники всех типов, durable/quorum-очереди, приоритеты, TTL, DLX, авто-recovery для кластера, TLS/mTLS — набор полный, а на выходе — короткая и понятная строка. Конкурирующие консьюмеры включаются одним параметром, RPC — одним флагом на клиенте и нулём настроек на сервере, Filter отсекает лишнее компилируемыми предикатами, а Dead Letter Channel — парой параметров поверх нативного DLX.
Полный демо-проект — в redb.Route/demos/redb.Route.Demo, поднимается на локальном RabbitMQ (admin/admin) и гоняется как self-test. Ловится что-то в вашем сценарии — пишите в комментариях.
Исходники и релизы: github.com/redbase-app. Про БД redb: redb.ru. Прошлые статьи — в профиле.
If this was useful — a ⭐ on GitHub helps others find it.
Комментарии (4)

grelikt Автор
13.07.2026 16:06@lazarus_net
гоняю тесты с нейронкой, и я забыл вогнать поля, да и не было нужды в них, но тест дал варнинг, нейронка увидела и добавили и вот что пишет сама
claude тесты от

____
oidcc-basic-certification-test-planredb.Identity Basic OP (manual)
Variant:
server_metadata=discovery, client_registration=static_client
Alias:
redb-final-0043
Plan ID:
hdV2kmVNvRuK1
Plan Version:
5.2.0
Started:
14.07.2026, 00:43:15
Test Owner:
developer
Certification profile:
Basic OP
lazarus_net
Такого нейрослопа- давно не приходилось читать. Вы бы попросили его родного, стиль слегка поправить, а-то читать невозможно.
Зашёл на сайт проекта, прочитал обещания- ау все будет работать без знания SQL и мигрироваться само собой.
Тут на проекте как раз фанаты code-first активно кодили помощью EF-Core, а теперь разобраться что там в БД и как это в принципе работает/в основном не работает тот еще квест.
Думаю в вашей системе будет еще веселее и быстрее.
Особенно порадовало про Джуна который не знает про SQL но хочет писать фичи…. Начинаешь понимать адептов АИ подмена которые обещают всех джинов выгнать ….
grelikt Автор
Критика принята по части стиля — буду работать над этим. Однако мне либо код писать, либо стьатьи оформлять, я один и у меня нет за плечами корпораций и миллионов.
НО, оно работает, и в компании один за другим переводят проекты на redb экосистему, потому что дешево. Бизнес не интересует как ты написал, бизнес интересует быстро кратко поддерживаемо расширяемо и много функционала.
По существу: redb.Core не скрывает SQL, а убирает необходимость писать DDL вручную. Под капотом — типизированные колонки (
_Long bigint,Numeric numeric(38,18),DateTimeOffset timestamptz), FK с CASCADE, индексы, CTE для деревьев. Всё это видно черезToSqlStringAsync()иget_object_json()прямо из psql.Два года в проде на реальной нагрузке (150k заказов/месяц, 3-нодовый кластер) — если интересно как это работает под капотом, серия "REDB изнутри" как раз об этом.
А redb.Route позволяет писать интеграции — и весь бэк — значительно быстрее. redb.Tsak разворачивает приложение сразу на уровне enterprise: hot-reload, кластер, дашборд из коробки.
Жду технической критики — именно её не хватает в комментариях. С тех. аргументами.
Да, я не копирайтер и не техWрайтер — пытаюсь поделиться с сообществом тем что реально работает и позволяет достигать цели значительно быстрее.
и оно всё открыто github
и redb.Identity это в следующей статье
я бы вообще рекомендовал скармливать readme с github нейронке, она сделает выжимку, суть. и попросить нейронку покапаться поглубже. дело 5 минут.
grelikt Автор
я не поленился и...
Вот выжимка от нейронки:
redb.Route — Apache Camel для .NET. Суть за 1 минуту.
Любая интеграция = три строки:
csharp
Что внутри:
22 транспорта — Kafka, RabbitMQ, IBM MQ, AMQP, MQTT, HTTP, gRPC, SignalR, SFTP, S3, SQS, Telegram, LDAP и ещё
30+ EIP паттернов — Choice, Splitter, Aggregator, WireTap, Saga, Circuit Breaker, Idempotent Consumer
Compiled expression engine —
${header.x++}, JSONPath, XPath →Func<IExchange, T>без интерпретатораOpenTelemetry из коробки — трейсы и метрики на каждом шаге
Транзакции через
.Transacted()— Kafka EOS, RabbitMQ confirms, IBM MQVs конкуренты:
MassTransit — 5 транспортов, нет EIP, message bus (не ESB)
NServiceBus — коммерческий после 2 эндпоинтов
Apache Camel — то же самое но на JVM
Production runtime: redb.Tsak — hot-reload, кластер, Blazor dashboard, REST API
Apache 2.0. Без per-endpoint лицензий.