{"id":481982,"date":"2026-06-01T18:24:18","date_gmt":"2026-06-01T18:24:18","guid":{"rendered":"https:\/\/savepearlharbor.com\/?p=481982"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=481982","title":{"rendered":"redb.Route \u2014 Apache Camel \u0434\u043b\u044f .NET, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u044b \u043d\u0430\u043f\u0438\u0441\u0430\u043b\u0438 \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0432\u044b\u0445\u043e\u0434\u0430 \u0434\u0440\u0443\u0433\u043e\u0433\u043e \u043d\u0435 \u0431\u044b\u043b\u043e"},"content":{"rendered":"<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/647\/f3e\/09a\/647f3e09a01015de505ae1f1efd82a00.png\" alt=\"redb\" title=\"redb\" width=\"784\" height=\"1168\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/647\/f3e\/09a\/647f3e09a01015de505ae1f1efd82a00.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/647\/f3e\/09a\/647f3e09a01015de505ae1f1efd82a00.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>redb<\/figcaption><\/div>\n<\/figure>\n<h4>\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430<\/h4>\n<p>\u0423 \u0432\u0430\u0441 \u043d\u0435 5 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u2014 \u0443 \u0432\u0430\u0441\u00a0<strong>\u0434\u0435\u0441\u044f\u0442\u043a\u0438<\/strong>. \u0411\u044d\u043a\u0435\u043d\u0434, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0440\u043e\u0441 \u0442\u0440\u0438 \u0433\u043e\u0434\u0430: \u043c\u043e\u043d\u043e\u043b\u0438\u0442, \u0440\u0430\u0441\u043a\u043e\u043b\u043e\u0442\u044b\u0439 \u043d\u0430 \u043a\u0443\u0441\u043a\u0438, GPS-\u0444\u0438\u0434 \u043e\u0442 \u0430\u0432\u0442\u043e\u043f\u0430\u0440\u043a\u0430, \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044f, \u0432\u0435\u0431-\u043a\u0430\u0431\u0438\u043d\u0435\u0442 \u0434\u0438\u0441\u043f\u0435\u0442\u0447\u0435\u0440\u0430, \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 SAP \/ 1\u0421 \/ \u0440\u0435\u0433\u0443\u043b\u044f\u0442\u043e\u0440\u0430\u043c\u0438 \/ \u043c\u0430\u0440\u043a\u0435\u0442\u043f\u043b\u0435\u0439\u0441\u0430\u043c\u0438, \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 SMTP-\u0432\u043e\u0440\u043a\u0435\u0440, \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 PDF-\u0433\u0435\u043d\u0435\u0440\u0430\u0442\u043e\u0440, \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0448\u0435\u0434\u0443\u043b\u0435\u0440 \u043d\u043e\u0447\u043d\u044b\u0445 \u043f\u0435\u0440\u0435\u0441\u0447\u0451\u0442\u043e\u0432. \u041c\u0435\u0436\u0434\u0443 \u043d\u0438\u043c\u0438 \u2014 Kafka (\u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043a\u043b\u0430\u0441\u0442\u0435\u0440\u043e\u0432, \u043f\u043e \u0442\u043e\u043f\u0438\u043a\u0443 \u043d\u0430 \u0434\u043e\u043c\u0435\u043d), RabbitMQ (RPC + pub\/sub + DLQ), Redis (\u043a\u044d\u0448, last-known-state, pub\/sub-\u043a\u0430\u043d\u0430\u043b\u044b), \u043f\u0430\u0440\u0430 HTTP-\u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u043e\u0432 \u043d\u0430\u0440\u0443\u0436\u0443, SFTP \u0441 \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u043e\u043c, SQL-polling outbox-\u0442\u0430\u0431\u043b\u0438\u0446\u044b \u0441\u0442\u0430\u0440\u043e\u0433\u043e \u043c\u043e\u043d\u043e\u043b\u0438\u0442\u0430, MQTT \u0441 \u0442\u0440\u0435\u043a\u0435\u0440\u043e\u0432, IBM MQ \u0434\u043b\u044f \u043e\u0434\u043d\u043e\u0433\u043e \u0434\u0440\u0435\u0432\u043d\u0435\u0433\u043e \u0431\u0430\u043d\u043a\u043e\u0432\u0441\u043a\u043e\u0433\u043e \u043a\u043e\u043d\u0442\u0443\u0440\u0430, SignalR-\u0445\u0430\u0431\u044b \u0434\u043b\u044f real-time-\u0434\u0430\u0448\u0431\u043e\u0440\u0434\u043e\u0432. \u041d\u0430 \u043a\u0430\u0436\u0434\u043e\u043c \u0441\u0442\u044b\u043a\u0435 \u2014 \u0441\u0432\u043e\u0439 \u0440\u0435\u0442\u0440\u0430\u0439, \u0441\u0432\u043e\u0439 DLQ (\u0438\u043b\u0438 \u043d\u0435\u0442 DLQ), \u0441\u0432\u043e\u044f \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f, \u0441\u0432\u043e\u0438 \u043c\u0435\u0442\u0440\u0438\u043a\u0438 (\u0438\u043b\u0438 \u043d\u0435\u0442 \u043c\u0435\u0442\u0440\u0438\u043a), \u0441\u0432\u043e\u044f \u0431\u043e\u0439\u043b\u0435\u0440\u043f\u043b\u0435\u0439\u0442-\u043e\u0431\u0432\u044f\u0437\u043a\u0430 \u0438\u0437 \u043a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440\u043e\u0432 \u0438\u00a0<code>try\/catch<\/code>.<\/p>\n<p>\u041a\u0430\u0436\u0434\u044b\u0439 \u0438\u0437 \u044d\u0442\u0438\u0445 \u0441\u0442\u044b\u043a\u043e\u0432 \u0436\u0438\u0432\u0451\u0442 \u0441\u0432\u043e\u0435\u0439 \u0436\u0438\u0437\u043d\u044c\u044e \u0432\u00a0<code>Program.cs<\/code>\u00a0\u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0435\u0433\u043e \u0441\u0435\u0440\u0432\u0438\u0441\u0430. \u041a\u0430\u0436\u0434\u044b\u0439 \u2014 \u044d\u0442\u043e hand-rolled \u0446\u0438\u043a\u043b:<\/p>\n<pre><code>\/\/ \u0413\u0434\u0435-\u0442\u043e \u0432 OrdersService:var consumer = new ConsumerBuilder&lt;Null, string&gt;(consumerConfig).Build();consumer.Subscribe(\"orders\");while (!stoppingToken.IsCancellationRequested){    try    {        var msg = consumer.Consume(stoppingToken);        var dto = JsonSerializer.Deserialize&lt;OrderDto&gt;(msg.Value);        if (dto.Type != \"new\") continue;          \/\/ filter        var retries = 0;        while (true)                              \/\/ retry        {            try { await PublishToRabbit(dto); break; }            catch (Exception ex) when (retries++ &lt; 3)            { await Task.Delay(TimeSpan.FromSeconds(retries * retries)); }        }        consumer.Commit(msg);                     \/\/ ack    }    catch (ConsumeException ex) { _logger.LogError(ex, \"kafka\"); }    catch (Exception ex)        { _logger.LogError(ex, \"send\"); }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:87px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0418 \u0442\u0430\u043a\u0438\u0445 80 \u0441\u0442\u0440\u043e\u043a \u2014 \u043d\u0430 \u043a\u0430\u0436\u0434\u043e\u0435 \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435. \u0423 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u2014 \u0441\u0432\u043e\u0439 \u0440\u0435\u0442\u0440\u0430\u0439, \u0441\u0432\u043e\u0439 DLQ (\u0438\u043b\u0438 \u043d\u0435\u0442 DLQ), \u0441\u0432\u043e\u044f \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f, \u0441\u0432\u043e\u0438 \u043c\u0435\u0442\u0440\u0438\u043a\u0438 (\u0438\u043b\u0438 \u043d\u0435\u0442 \u043c\u0435\u0442\u0440\u0438\u043a). \u041d\u0430 code review \u044d\u0442\u043e \u00ab\u043d\u0443 \u0434\u0430, \u0447\u0451\u00bb. \u0427\u0435\u0440\u0435\u0437 \u0433\u043e\u0434 \u043d\u0430 \u043e\u043d\u0431\u043e\u0440\u0434\u0438\u043d\u0433\u0435 \u043d\u043e\u0432\u043e\u0433\u043e \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430 \u2014 \u00ab\u0430 \u0433\u0434\u0435 \u0442\u0443\u0442 \u0432\u043e\u043e\u0431\u0449\u0435 \u0447\u0442\u043e \u043b\u0435\u0436\u0438\u0442\u00bb.<\/p>\n<p>\u041d\u0430 JVM \u044d\u0442\u0443 \u0437\u0430\u0434\u0430\u0447\u0443 20 \u043b\u0435\u0442 \u043d\u0430\u0437\u0430\u0434 \u0440\u0435\u0448\u0438\u043b\u0438\u00a0<a href=\"https:\/\/camel.apache.org\/\" rel=\"noopener noreferrer nofollow\">Apache Camel<\/a>\u00a0\u0438 \u0431\u043e\u043b\u0435\u0435 \u043e\u0431\u0449\u0438\u0439\u00a0<a href=\"https:\/\/www.enterpriseintegrationpatterns.com\/\" rel=\"noopener noreferrer nofollow\">Enterprise Integration Patterns<\/a>\u00a0(\u043a\u043d\u0438\u0436\u043a\u0430 Gregor Hohpe \u0438 Bobby Woolf, \u0442\u0430 \u0441\u0430\u043c\u0430\u044f \u00ab\u0436\u0451\u043b\u0442\u0430\u044f\u00bb). \u0418\u0434\u0435\u044f \u043f\u0440\u043e\u0441\u0442\u0430\u044f: \u043c\u0430\u0440\u0448\u0440\u0443\u0442 \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043a\u0430\u043a pipeline\u00a0<code>From \u2192 Process \u2192 To<\/code>, EIP-\u043f\u0430\u0442\u0442\u0435\u0440\u043d\u044b (Splitter, Aggregator, Content-Based Router, WireTap, Dead Letter Channel, Idempotent Consumer, Saga) \u2014 first-class \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b DSL. \u0422\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u043e\u0432 \u2014 300+: \u043e\u0442 Kafka \u0434\u043e LDAP, \u043e\u0442 S3 \u0434\u043e IBM MQ.<\/p>\n<p>\u0412 .NET \u044d\u0442\u043e\u0433\u043e \u043d\u0435 \u0431\u044b\u043b\u043e.<\/p>\n<p>MassTransit, NServiceBus, Wolverine \u2014 \u043e\u0442\u043b\u0438\u0447\u043d\u044b\u0435\u00a0<strong>message bus<\/strong>&#8216;\u044b, \u043d\u043e \u044d\u0442\u043e \u043d\u0435 \u043e\u0434\u043d\u043e \u0438 \u0442\u043e \u0436\u0435. \u0423 \u043d\u0438\u0445 4\u20137 \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u043e\u0432 (Kafka, RabbitMQ, Azure SB, SQS, \u0438\u043d\u043e\u0433\u0434\u0430 SQL), \u0444\u043e\u043a\u0443\u0441 \u2014 durable saga + handler classes. EIP-\u043a\u0430\u0442\u0430\u043b\u043e\u0433 \u043e\u043d\u0438 \u0437\u0430\u043a\u0440\u044b\u0432\u0430\u044e\u0442 \u043d\u0430 3-4 \u043f\u0430\u0442\u0442\u0435\u0440\u043d\u0430 (Saga, Request\/Response, Outbox). \u0410 \u043a\u043e\u0433\u0434\u0430 \u043d\u0443\u0436\u043d\u043e \u00ab\u0437\u0430\u0431\u0435\u0440\u0438 XML \u0438\u0437 SQL stored proc \u2192 \u0440\u0430\u0441\u043f\u0430\u0440\u0441\u0438 \u2192 \u043f\u043e\u043b\u043e\u0436\u0438 \u0432 RabbitMQ \u2192 \u043f\u0440\u043e\u0434\u0443\u0431\u043b\u0438\u0440\u0443\u0439 \u0432 SFTP-\u0430\u0440\u0445\u0438\u0432 \u0441 retention 30 \u0434\u043d\u0435\u0439\u00bb \u2014 \u043f\u0438\u0448\u0435\u0448\u044c \u0440\u0443\u043a\u0430\u043c\u0438 \u0432\u0441\u044e \u044d\u0442\u0443 \u043a\u043b\u0430\u0441\u0441\u0438\u0447\u0435\u0441\u043a\u0443\u044e \u043a\u0430\u0448\u0443 \u0438\u0437 \u043a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440\u043e\u0432, \u043f\u0440\u043e\u0434\u044e\u0441\u0435\u0440\u043e\u0432 \u0438\u00a0<code>try\/catch<\/code>.<\/p>\n<p>\u0418 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u043f\u043e\u0432\u043e\u0440\u043e\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0441\u043b\u0443\u0447\u0438\u043b\u0441\u044f \u0431\u0443\u043a\u0432\u0430\u043b\u044c\u043d\u043e \u0441\u0435\u0439\u0447\u0430\u0441:\u00a0<strong>MassTransit v9 \u0441\u0442\u0430\u043b \u043a\u043e\u043c\u043c\u0435\u0440\u0447\u0435\u0441\u043a\u0438\u043c<\/strong>. \u041f\u0440\u043e\u0435\u043a\u0442 \u043f\u0435\u0440\u0435\u0435\u0445\u0430\u043b \u043a \u043d\u043e\u0432\u043e\u0439 \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u0438\u00a0<a href=\"https:\/\/massient.com\/\" rel=\"noopener noreferrer nofollow\">Massient, Inc.<\/a>, \u043d\u0430 \u0433\u043b\u0430\u0432\u043d\u043e\u0439 \u043d\u043e\u0432\u043e\u0439 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438\u00a0<a href=\"http:\/\/masstransit.massient.com\" rel=\"noopener noreferrer nofollow\"><code>masstransit.massient.com<\/code><\/a>\u00a0\u043a\u043d\u043e\u043f\u043a\u0430 \u00abGet a License\u00bb, \u0432 \u0434\u043e\u043a\u0430\u0445 \u2014\u00a0<a href=\"https:\/\/masstransit.massient.com\/configuration\/license\/\" rel=\"noopener noreferrer nofollow\">\u00abConfigure the license key\u00bb<\/a>, Customer Support \u0438 Usage Telemetry. \u0414\u043e\u0441\u043b\u043e\u0432\u043d\u043e \u0441 \u0438\u0445 \u0441\u0430\u0439\u0442\u0430:\u00a0<em>\u00abMassient is the new company behind the commercial release of MassTransit v9.\u00bb<\/em>\u00a0\u0422\u043e \u0435\u0441\u0442\u044c \u0438\u0437 \u00ab\u0442\u0440\u0451\u0445 .NET-\u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u00bb \u0434\u0432\u0435 \u2014 \u043f\u043b\u0430\u0442\u043d\u044b\u0435 (NServiceBus \u0434\u0430\u0432\u043d\u043e, MassTransit v9 \u0442\u0435\u043f\u0435\u0440\u044c), \u043e\u0441\u0442\u0430\u0451\u0442\u0441\u044f Wolverine \u043d\u0430 MIT \u2014 \u0438 \u043e\u043d, \u043e\u043f\u044f\u0442\u044c-\u0442\u0430\u043a\u0438, \u043d\u0435 ESB, \u0430 mediator + saga, \u0431\u0435\u0437 \u0434\u0432\u0443\u0445 \u0434\u0435\u0441\u044f\u0442\u043a\u043e\u0432 \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u043e\u0432 \u0438 \u0431\u0435\u0437 EIP-\u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0430. \u0421\u0432\u043e\u0431\u043e\u0434\u043d\u043e\u0433\u043e Camel-\u0443\u0440\u043e\u0432\u043d\u044f \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u0430 \u043f\u043e\u0434 .NET \u0434\u043e \u0441\u0438\u0445 \u043f\u043e\u0440 \u043d\u0435 \u0431\u044b\u043b\u043e.<\/p>\n<p>\u0425\u043e\u0442\u0435\u043b\u043e\u0441\u044c: \u043e\u043f\u0438\u0441\u0430\u0442\u044c \u043c\u0430\u0440\u0448\u0440\u0443\u0442 \u043a\u0430\u043a pipeline \u043d\u0430 C#, \u0447\u0442\u043e\u0431 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0441\u0430\u043c\u0430 \u0437\u043d\u0430\u043b\u0430 \u043f\u0440\u043e Kafka, RabbitMQ, SQL, SFTP, HTTP, MQTT, retry, DLQ, transactional ack, \u0438 \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u0431\u044b\u043b \u043f\u043e\u043b\u043d\u044b\u0439 \u043d\u0430\u0431\u043e\u0440 EIP-\u043f\u0430\u0442\u0442\u0435\u0440\u043d\u043e\u0432 \u0438\u0437 \u043a\u0430\u043d\u043e\u043d\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u043a\u043d\u0438\u0436\u043a\u0438.<\/p>\n<p>\u041d\u0430\u043f\u0438\u0441\u0430\u043b\u0438. \u0412\u044b\u043b\u043e\u0436\u0438\u043b\u0438 \u043f\u043e\u0434 Apache 2.0.<\/p>\n<hr\/>\n<h4>Production case<\/h4>\n<p>\u042d\u0442\u043e\u0442 \u0442\u0435\u043a\u0441\u0442 \u2014 \u043d\u0435 \u0442\u0435\u043e\u0440\u0438\u044f.\u00a0<strong>redb.Route \u2014 \u044d\u0442\u043e ESB-\u043a\u0430\u0440\u043a\u0430\u0441 \u0443\u0440\u043e\u0432\u043d\u044f Apache Camel<\/strong>, \u0438 \u043c\u044b \u044d\u0442\u043e \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u0435\u043c \u043d\u0435 \u043d\u0430 \u0431\u0443\u043c\u0430\u0433\u0435: 22 \u0432\u043d\u0435\u0448\u043d\u0438\u0445 \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u0430 + 5 \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0445 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432, 30+ EIP-\u043f\u0430\u0442\u0442\u0435\u0440\u043d\u043e\u0432 \u043a\u0430\u043a first-class \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b DSL, compiled expression engine \u043d\u0430\u00a0<code>System.Linq.Expressions<\/code>, transactional pipelines \u0441\u00a0<code>ITransactedAction<\/code>\u00a0(Kafka EOS, RabbitMQ publisher confirms + tx channels, IBM MQ syncpoint, AMQP 1.0, SQL via\u00a0<code>TransactionScope<\/code>), persistent\u00a0<code>IdempotentConsumer<\/code>,\u00a0<code>Saga<\/code>\u00a0\u0441 \u043a\u043e\u043c\u043f\u0435\u043d\u0441\u0438\u0440\u0443\u044e\u0449\u0438\u043c\u0438 \u0448\u0430\u0433\u0430\u043c\u0438,\u00a0<code>CircuitBreaker<\/code>,\u00a0<code>Throttle<\/code>,\u00a0<code>Aggregator<\/code>,\u00a0<code>Resequencer<\/code>,\u00a0<code>Scatter-Gather<\/code>,\u00a0<code>RecipientList<\/code>,\u00a0<code>DynamicRouter<\/code>,\u00a0<code>Enrich<\/code>\/<code>PollEnrich<\/code>,\u00a0<code>Claim Check<\/code>, OpenTelemetry \u043d\u0430 \u043a\u0430\u0436\u0434\u044b\u0439 \u0448\u0430\u0433, runtime-\u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u0441 hot-reload \u0438 \u043a\u043b\u0430\u0441\u0442\u0435\u0440\u043e\u043c \u0431\u0435\u0437 \u0432\u043d\u0435\u0448\u043d\u0435\u0433\u043e \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u043e\u0440\u0430. 351 \u043f\u0440\u043e\u0445\u043e\u0434\u044f\u0449\u0438\u0439 \u0442\u0435\u0441\u0442, \u0432\u043a\u043b\u044e\u0447\u0430\u044f \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0435\u00a0<strong>DSL reference-suites<\/strong>\u00a0\u043f\u043e\u0434 Camel-\u0441\u0435\u043c\u0430\u043d\u0442\u0438\u043a\u0443 Choice\/When\/Otherwise, TryCatchFinally \u0438 Filter (~1500 \u0441\u0442\u0440\u043e\u043a \u0442\u043e\u043b\u044c\u043a\u043e \u0432 \u0441\u043f\u0435\u0446\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f\u0445). \u042d\u0442\u043e \u043d\u0435 \u00ab\u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a-\u043e\u0431\u0451\u0440\u0442\u043a\u0430 \u043d\u0430\u0434\u00a0<code>IHostedService<\/code>\u00bb, \u044d\u0442\u043e \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u044b\u0439 ESB.<\/p>\n<p>\u0418 \u043e\u043d \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0432 \u043f\u0440\u043e\u0434\u0435. \u041f\u0435\u0440\u0432\u044b\u0439 \u2014 \u043a\u0440\u0443\u043f\u043d\u0430\u044f \u043b\u043e\u0433\u0438\u0441\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u044f (~150k \u0437\u0430\u043a\u0430\u0437\u043e\u0432\/\u043c\u0435\u0441, ~20k B2B-\u043a\u043b\u0438\u0435\u043d\u0442\u043e\u0432, \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u0430\u0432\u0442\u043e\u043f\u0430\u0440\u043a, 600+ \u0433\u043e\u0440\u043e\u0434\u043e\u0432). \u0412\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u044f\u044f TMS \u2014 ~500 \u0432\u043e\u0434\u0438\u0442\u0435\u043b\u0435\u0439 + ~50 \u0434\u0438\u0441\u043f\u0435\u0442\u0447\u0435\u0440\u043e\u0432, 3-\u043d\u043e\u0434\u043e\u0432\u044b\u0439 \u043a\u043b\u0430\u0441\u0442\u0435\u0440 (Xeon, 4 \u044f\u0434\u0440\u0430 \/ 8 \u0413\u0411 \/ 50 \u0413\u0411 SSD \u043d\u0430 \u043d\u043e\u0434\u0443), ~3 \u043c\u0435\u0441\u044f\u0446\u0430 \u0441\u0442\u0430\u0431\u0438\u043b\u044c\u043d\u043e\u0439 \u0440\u0430\u0431\u043e\u0442\u044b, 10\u201315% CPU \u043f\u043e\u0434 \u043f\u043e\u043b\u043d\u043e\u0439 \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u043e\u0439.<\/p>\n<p>\u0427\u0435\u0440\u0435\u0437 redb.Route \u0442\u0443\u0434\u0430 \u0437\u0430\u0432\u0435\u0434\u0435\u043d\u044b:\u00a0<strong>SAP S\/4<\/strong>\u00a0(\u0447\u0435\u0440\u0435\u0437 SQL polling stored procedure \u2192 XML \u2192 redb),\u00a0<strong>Kafka<\/strong>\u00a0(\u0444\u0438\u0434 \u0441 GPS-\u0442\u0440\u0435\u043a\u0435\u0440\u043e\u0432),\u00a0<strong>RabbitMQ<\/strong>\u00a0(\u0432\u043d\u0443\u0442\u0440\u0438\u0441\u0435\u0440\u0432\u0438\u0441\u043d\u0430\u044f \u0448\u0438\u043d\u0430 \u0441\u043e\u0431\u044b\u0442\u0438\u0439),\u00a0<strong>HTTP API<\/strong>\u00a0\u0434\u043b\u044f UI \u0434\u0438\u0441\u043f\u0435\u0442\u0447\u0435\u0440\u0430,\u00a0<strong>\u041c\u0435\u0440\u043a\u0443\u0440\u0438\u0439 \/ \u0415\u0413\u0410\u0418\u0421 \/ \u0427\u0435\u0441\u0442\u043d\u044b\u0439 \u0417\u041d\u0410\u041a \/ \u0424\u0413\u0418\u0421 \u0417\u0435\u0440\u043d\u043e<\/strong>\u00a0(\u0440\u043e\u0441\u0441\u0438\u0439\u0441\u043a\u0438\u0435 \u0440\u0435\u0433\u0443\u043b\u044f\u0442\u043e\u0440\u044b),\u00a0<strong>LDAP\/AD<\/strong>\u00a0\u0434\u043b\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438,\u00a0<strong>cron-backup<\/strong>\u00a0\u0447\u0435\u0440\u0435\u0437\u00a0<code>redb.Export<\/code>\u00a0\u0441 \u0440\u043e\u0442\u0430\u0446\u0438\u0435\u0439. \u041e\u0434\u0438\u043d\u00a0<code>InitRoute<\/code>-\u0444\u0430\u0439\u043b \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u0442 22\u00a0<code>RouteBuilder<\/code>-\u043a\u043b\u0430\u0441\u0441\u0430. \u041a\u0430\u0436\u0434\u044b\u0439 \u2014 \u0438\u0437\u043e\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 pipeline, \u0441\u043e \u0441\u0432\u043e\u0438\u043c\u0438\u00a0<code>OnException<\/code>-\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430\u043c\u0438, \u0441\u0432\u043e\u0438\u043c\u00a0<code>RouteId<\/code>, \u0441\u0432\u043e\u0438\u043c\u0438 \u043c\u0435\u0442\u0440\u0438\u043a\u0430\u043c\u0438 OpenTelemetry.<\/p>\n<p>\u0412\u0442\u043e\u0440\u043e\u0439 \u043f\u0440\u043e\u0434 \u2014 \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0430 \u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0438 \u0442\u043e\u0433\u043e \u0436\u0435 \u0437\u0430\u043a\u0430\u0437\u0447\u0438\u043a\u0430,\u00a0<strong>\u0434\u0435\u0441\u044f\u0442\u043a\u0438 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432<\/strong>\u00a0\u0432 \u043e\u0434\u043d\u043e\u043c \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438 (~37 .NET-\u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432, ~50+\u00a0<code>RouteBuilder<\/code>-\u043a\u043b\u0430\u0441\u0441\u043e\u0432). \u041c\u043e\u0434\u0443\u043b\u0438 \u043e\u0431\u0449\u0430\u044e\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437 RabbitMQ (RPC + pub\/sub), \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0439 event-bus, Kafka \u0441 Consumer Group \u0434\u043b\u044f GPS-\u0441\u0442\u0440\u0438\u043c\u0430 \u0441 \u043c\u043e\u0431\u0438\u043b\u043e\u043a, Redis \u0434\u043b\u044f last-known-location-\u043a\u044d\u0448\u0430, RabbitMQ-DLQ \u043f\u043e\u0434 poison messages, \u0438 \u0432\u0441\u0451 \u044d\u0442\u043e \u0437\u0430\u0432\u044f\u0437\u0430\u043d\u043e \u043d\u0430 \u0435\u0434\u0438\u043d\u044b\u0439 ESB-\u043a\u0430\u0440\u043a\u0430\u0441. \u0422\u043e \u0435\u0441\u0442\u044c \u044d\u0442\u043e \u043d\u0435 \u00ab5 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u0441 REST API\u00bb \u2014 \u044d\u0442\u043e \u0434\u0435\u0441\u044f\u0442\u043a\u0438 \u043d\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e \u0434\u0435\u043f\u043b\u043e\u044f\u0449\u0438\u0445\u0441\u044f \u043c\u043e\u0434\u0443\u043b\u0435\u0439, \u0443 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0441\u0432\u043e\u0439\u00a0<code>IRouteContext<\/code>, \u0441\u0432\u043e\u0439 \u043d\u0430\u0431\u043e\u0440 endpoint&#8217;\u043e\u0432, \u0441\u0432\u043e\u0439 lifecycle. \u0421\u0440\u0430\u0432\u043d\u0438\u043c\u0430\u044f \u043f\u043e \u0441\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0430 \u043d\u0430 JVM \u0431\u044b\u043b\u0430 \u0431\u044b \u2014 Apache Camel + Karaf. \u0423 \u043d\u0430\u0441 \u2014 redb.Route + Tsak.<\/p>\n<p>\u0418 \u0432\u0441\u0451 \u044d\u0442\u043e \u2014\u00a0<strong>\u0432 \u0442\u0440\u0451\u0445 \u0441\u0442\u0435\u043d\u0434\u0430\u0445: dev \/ test \/ prod, \u043a\u0430\u0436\u0434\u044b\u0439 \u2014 3-\u043d\u043e\u0434\u043e\u0432\u044b\u0439 \u043a\u043b\u0430\u0441\u0442\u0435\u0440<\/strong>. \u0422\u043e \u0435\u0441\u0442\u044c\u00a0<code>RouteBuilder<\/code>-\u043a\u043b\u0430\u0441\u0441\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0432\u044b \u0432\u0438\u0434\u0438\u0442\u0435 \u0434\u0430\u043b\u044c\u0448\u0435 \u043f\u043e \u0441\u0442\u0430\u0442\u044c\u0435, \u043f\u0440\u044f\u043c\u043e \u0441\u0435\u0439\u0447\u0430\u0441 \u043a\u0440\u0443\u0442\u044f\u0442\u0441\u044f \u0432 \u0434\u0435\u0432\u044f\u0442\u0438 \u0438\u043d\u0441\u0442\u0430\u043d\u0441\u0430\u0445 Tsak \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e: \u0442\u0440\u0438 \u043d\u043e\u0434\u044b \u043f\u043e\u0434\u043d\u0438\u043c\u0430\u044e\u0442 \u043e\u0434\u0438\u043d\u0430\u043a\u043e\u0432\u044b\u0435 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u044b, redb-\u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u043e\u0440 \u0440\u0430\u0437\u0434\u0430\u0451\u0442 \u043c\u0435\u0436\u0434\u0443 \u043d\u0438\u043c\u0438 \u043a\u043b\u0430\u0441\u0442\u0435\u0440\u043d\u044b\u0435 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u044b (Kafka Consumer Group \u2014 \u0431\u0430\u043b\u0430\u043d\u0441\u0438\u0440\u043e\u0432\u043a\u043e\u0439 \u043f\u0430\u0440\u0442\u0438\u0446\u0438\u0439, RabbitMQ \u2014 \u043a\u043e\u043d\u043a\u0443\u0440\u0435\u043d\u0442\u043d\u044b\u0435 consumer&#8217;\u044b \u043d\u0430 \u0442\u043e\u0439 \u0436\u0435 \u043e\u0447\u0435\u0440\u0435\u0434\u0438, HTTP-\u0444\u0430\u0441\u0430\u0434\u044b \u2014 \u0437\u0430 L4-\u0431\u0430\u043b\u0430\u043d\u0441\u0435\u0440\u043e\u043c, cron \u2014 leader-election \u0447\u0435\u0440\u0435\u0437 redb-\u043e\u0431\u044a\u0435\u043a\u0442\u044b, \u0447\u0442\u043e\u0431\u044b \u043a\u0440\u043e\u043d-\u0437\u0430\u0434\u0430\u0447\u0430 \u043e\u0442\u0440\u0430\u0431\u043e\u0442\u0430\u043b\u0430 \u0440\u043e\u0432\u043d\u043e \u043e\u0434\u0438\u043d \u0440\u0430\u0437 \u043d\u0430 \u043a\u043b\u0430\u0441\u0442\u0435\u0440). \u0414\u0435\u0432-\u0441\u0442\u0435\u043d\u0434 \u2014 \u0434\u043b\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0438 feature-\u0432\u0435\u0442\u043e\u043a, test \u2014 \u043f\u0440\u0438\u0451\u043c\u043e\u0447\u043d\u044b\u0435 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0438 \u0438 \u0440\u0435\u0433\u0440\u0435\u0441\u0441 \u043f\u0435\u0440\u0435\u0434 \u0440\u0435\u043b\u0438\u0437\u043e\u043c, prod \u2014 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0430. \u041c\u0435\u0436\u0434\u0443 \u0441\u0442\u0435\u043d\u0434\u0430\u043c\u0438 \u0440\u0430\u0437\u044a\u0435\u0437\u0436\u0430\u0435\u0442\u0441\u044f \u0442\u043e\u0442 \u0436\u0435\u00a0<code>.tpkg<\/code>-\u043f\u0430\u043a\u0435\u0442: \u043c\u0435\u043d\u044f\u0435\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u043a\u043e\u043d\u0444\u0438\u0433 \u0438 \u0441\u0442\u0440\u043e\u043a\u0430 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u0411\u0414. Cluster + hot-reload + \u0442\u0440\u0438 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u044b\u0445 \u0441\u0442\u0435\u043d\u0434\u0430 \u2014 \u044d\u0442\u043e \u0438 \u0435\u0441\u0442\u044c \u00abproduction-ready\u00bb, \u0430 \u043d\u0435 \u00ab\u0443 \u043c\u0435\u043d\u044f \u043d\u0430 \u043d\u043e\u0443\u0442\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442\u00bb.<\/p>\n<p>\u0422\u0440\u0435\u0442\u0438\u0439 \u043f\u0440\u043e\u0434 \u2014 \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0430 (~672k \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432, ~8M \u0441\u0432\u043e\u0439\u0441\u0442\u0432), \u0442\u0430\u043c Route \u0438\u0433\u0440\u0430\u0435\u0442 \u0440\u043e\u043b\u044c \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0435\u0439 \u0448\u0438\u043d\u044b \u043c\u0435\u0436\u0434\u0443 \u043c\u043e\u0434\u0443\u043b\u044f\u043c\u0438. \u0420\u0435\u0430\u043b\u044c\u043d\u044b\u0439 \u043a\u043e\u0434 \u0432\u0441\u0435\u0445 \u0442\u0440\u0451\u0445 \u2014 \u043d\u0438\u0436\u0435.<\/p>\n<p>\u0418 \u044d\u0442\u043e \u043d\u0435 \u00ab\u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0435 \u0441\u0431\u043e\u0440\u043a\u0438 \u0438\u0437 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u044f\u00bb. \u0412\u0441\u044f \u0441\u0442\u043e\u043f\u043a\u0430 3.0.0 \u043e\u043f\u0443\u0431\u043b\u0438\u043a\u043e\u0432\u0430\u043d\u0430 \u043d\u0430 <a href=\"http:\/\/nuget.org\" rel=\"noopener noreferrer nofollow\">nuget.org<\/a> \u2014\u00a0<a href=\"https:\/\/www.nuget.org\/profiles\/relikt\" rel=\"noopener noreferrer nofollow\"><strong>43 \u043f\u0430\u043a\u0435\u0442\u0430 \u043f\u043e\u0434 \u043f\u0440\u0435\u0444\u0438\u043a\u0441\u043e\u043c\u00a0<\/strong><code><strong>redb.*<\/strong><\/code><\/a><strong>, \u0441\u0443\u043c\u043c\u0430\u0440\u043d\u043e ~20 800 \u0437\u0430\u0433\u0440\u0443\u0437\u043e\u043a<\/strong>\u00a0\u043d\u0430 \u043c\u043e\u043c\u0435\u043d\u0442 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u044f \u0441\u0442\u0430\u0442\u044c\u0438. \u041f\u043e \u0443\u0431\u044b\u0432\u0430\u043d\u0438\u044e:\u00a0<code>redb.Route<\/code>\u00a0(~1600),\u00a0<code>redb.Core<\/code>\u00a0(~1590),\u00a0<a href=\"http:\/\/redb.Core.Pro\" rel=\"noopener noreferrer nofollow\"><code>redb.Core.Pro<\/code><\/a>\u00a0(~1160),\u00a0<code>redb.Templates<\/code>,\u00a0<code>redb.Postgres<\/code>\u00a0\/\u00a0<code>redb.MSSql<\/code>\u00a0(~1000 \u043a\u0430\u0436\u0434\u044b\u0439), \u0434\u0430\u043b\u044c\u0448\u0435 Pro-\u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u044b,\u00a0<code>redb.CLI<\/code>,\u00a0<code>redb.Export<\/code>, \u0438 \u0432\u0435\u0441\u044c \u0437\u043e\u043e\u043f\u0430\u0440\u043a\u00a0<code>redb.Route.*<\/code>\u00a0\u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u043e\u0432 (Kafka, RabbitMQ, IBM MQ, MQTT, AMQP, Redis, gRPC, SignalR, WebSocket, TCP, SFTP, FTP, File, S3, Mail, LDAP, Quartz, Elasticsearch, AzureServiceBus, Firebase, Http, Sql) +\u00a0<code>redb.Tsak.*<\/code>\u00a0(Core, <a href=\"http:\/\/Core.Pro\" rel=\"noopener noreferrer nofollow\">Core.Pro<\/a>, Client, CLI, Contracts, Templates). \u0422\u043e \u0435\u0441\u0442\u044c \u0432\u0441\u0451, \u043f\u0440\u043e \u0447\u0442\u043e \u044d\u0442\u0430 \u0441\u0442\u0430\u0442\u044c\u044f \u2014 \u0440\u0435\u0430\u043b\u044c\u043d\u043e \u043b\u0435\u0436\u0438\u0442, \u0432\u0435\u0440\u0441\u0438\u043e\u043d\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0438 \u043f\u043e\u0434\u0442\u044f\u0433\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437\u00a0<code>dotnet add package<\/code>.<\/p>\n<hr\/>\n<h4>\u041a\u0430\u043a \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u043c\u0430\u0440\u0448\u0440\u0443\u0442<\/h4>\n<p>\u0427\u0442\u043e\u0431\u044b \u043d\u0435 \u0432\u0440\u0430\u0442\u044c \u0438\u0433\u0440\u0443\u0448\u0435\u0447\u043d\u044b\u043c\u00a0<code>From \u2192 Filter \u2192 To<\/code>, \u0441\u0440\u0430\u0437\u0443 \u0431\u043e\u0435\u0432\u043e\u0439 shape. \u042d\u0442\u043e \u0443\u043f\u0440\u043e\u0449\u0451\u043d\u043d\u044b\u0439 \u043d\u0430\u0431\u0440\u043e\u0441\u043e\u043a \u0438\u0437 \u043b\u043e\u0433\u0430-\u0442\u0440\u0435\u043a\u0438\u043d\u0433\u043e\u0432\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b: HTTP-\u0444\u0430\u0441\u0430\u0434 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u043c\u0430\u0441\u0441\u0438\u0432 GPS-\u0442\u043e\u0447\u0435\u043a, throttle \u043d\u0430 200 rps, \u0438\u0434\u0435\u043c\u043f\u043e\u0442\u0435\u043d\u0442\u043d\u043e\u0441\u0442\u044c \u043f\u043e\u00a0<code>MessageId<\/code>, \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f JSON, \u0432\u0435\u0442\u0432\u043b\u0435\u043d\u0438\u0435 \u043f\u043e \u0442\u0438\u043f\u0443 \u0441\u043e\u0431\u044b\u0442\u0438\u044f,\u00a0<code>Multicast<\/code>\u00a0\u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e \u0432 \u0442\u0440\u0438 \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u0430 (Kafka \u043d\u0430 \u0430\u0433\u0440\u0435\u0433\u0430\u0446\u0438\u044e, RabbitMQ \u043d\u0430 DAL-\u0437\u0430\u043f\u0438\u0441\u044c, Redis-\u043a\u044d\u0448 last-known-location \u0444\u043e\u043d\u043e\u043c \u0447\u0435\u0440\u0435\u0437\u00a0<code>WireTap<\/code>), \u0430 \u043d\u0430\u0440\u0443\u0436\u0443 \u2014 JSON-\u043e\u0442\u0432\u0435\u0442:<\/p>\n<pre><code>public class GpsHttpFacadeRoutes : RouteBuilder{    protected override void Configure()    {        OnException&lt;JsonException&gt;()            .Handled()            .SetHeader(\"HTTP_SC\", Constant(400))            .To(\"direct:\/\/error-handler\")        .EndOnException();        From(\"http:\/\/0.0.0.0:5090\/integration\/gps?methods=POST\")            .RouteId(\"gps-http-facade\")            .Throttle(200).Per(TimeSpan.FromSeconds(1))                      \/\/ \u043d\u0435 \u0431\u043e\u043b\u044c\u0448\u0435 200 rps            .IdempotentConsumer(Header(\"MessageId\"), repository: \"redb\")     \/\/ \u0434\u0443\u0431\u043b\u0438 \u043e\u0442\u0441\u0435\u043a\u0430\u0435\u043c            .Process(ValidateGpsBatch)                                       \/\/ JSON \u2192 List&lt;GpsPoint&gt;            .Choice()                .When(Header(\"eventType\").IsEqualTo(\"location\"))                    .Multicast().ParallelProcessing()                        .To(\"kafka:mobile.trips.location.sync?key=${header.tripId}\")                        .To(\"rabbitmq:?exchange=lt.dal&amp;routingKey=dal.trips.location.sync\")                        .WireTap(\"redis:set:gps:last:location:${header.tripId}?ttl=86400\")                    .EndMulticast()                .When(Header(\"eventType\").IsEqualTo(\"checkin\"))                    .To(\"rabbitmq:?exchange=lt.dal&amp;routingKey=dal.gps.checkin_checkout\")                .Otherwise()                    .Log(LogLevel.Warning, \"unknown eventType: ${header.eventType}\")            .EndChoice()            .SetBody(Constant(\"\"\"{ \"status\": \"accepted\" }\"\"\"))            .SetHeader(\"Content-Type\", Constant(\"application\/json\"));    }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u042d\u0442\u043e\u0442 \u043e\u0434\u0438\u043d \u043a\u043b\u0430\u0441\u0441 \u0437\u0430\u043c\u0435\u043d\u044f\u0435\u0442: HTTP-\u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440, middleware-throttle, \u0440\u0443\u0447\u043d\u0443\u044e \u0438\u0434\u0435\u043c\u043f\u043e\u0442\u0435\u043d\u0442\u043d\u043e\u0441\u0442\u044c \u0447\u0435\u0440\u0435\u0437 Redis, \u0440\u0443\u0447\u043d\u043e\u0439 Kafka-\u043f\u0440\u043e\u0434\u044e\u0441\u0435\u0440, \u0440\u0443\u0447\u043d\u0443\u044e RabbitMQ-\u043e\u0431\u0432\u044f\u0437\u043a\u0443,\u00a0<code>try\/catch<\/code>\u00a0\u043f\u043e\u0432\u0435\u0440\u0445 \u0434\u0435\u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0438 \u043b\u043e\u0433\u0438\u043a\u0443 \u0434\u0451\u0440\u0433\u0430\u043d\u0438\u044f Redis \u0438\u0437 background-\u0441\u0435\u0440\u0432\u0438\u0441\u0430. \u0412\u0441\u0451, \u0447\u0442\u043e \u0432\u043d\u0443\u0442\u0440\u0438 \u2014 first-class \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b DSL:\u00a0<code>Throttle<\/code>,\u00a0<code>IdempotentConsumer<\/code>,\u00a0<code>Choice<\/code>,\u00a0<code>Multicast<\/code>,\u00a0<code>WireTap<\/code>,\u00a0<code>OnException&lt;T&gt;<\/code>. \u041a\u0430\u0436\u0434\u044b\u0439 \u0438\u0437 \u043d\u0438\u0445 \u2014 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 EIP-\u043f\u0430\u0442\u0442\u0435\u0440\u043d \u0441 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u043c\u0438 \u043c\u0435\u0442\u0440\u0438\u043a\u0430\u043c\u0438 OpenTelemetry.<\/p>\n<blockquote>\n<p>\u041a\u0441\u0442\u0430\u0442\u0438,\u00a0<strong>\u0438\u043c\u0435\u043d\u043d\u043e \u0442\u0430\u043a\u043e\u0439 Route-\u0444\u0430\u0441\u0430\u0434 \u0432 \u043e\u0434\u043d\u043e\u043c \u0438\u0437 \u043f\u0440\u043e\u0434\u043e\u0432 \u0441\u0435\u0439\u0447\u0430\u0441 \u0437\u0430\u043c\u0435\u0449\u0430\u0435\u0442 WSO2 Micro Integrator<\/strong>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0434\u043e \u044d\u0442\u043e\u0433\u043e \u0438\u0433\u0440\u0430\u043b \u0440\u043e\u043b\u044c HTTP\u2192Kafka\/RabbitMQ-\u0448\u043b\u044e\u0437\u0430. WSO2 \u0434\u0430\u0451\u0442 \u0442\u0443 \u0436\u0435 \u043c\u0430\u0448\u0438\u043d\u0435\u0440\u0438\u044e \u0447\u0435\u0440\u0435\u0437 XML-\u043a\u043e\u043d\u0444\u0438\u0433\u0438 \u0438 Synapse-DSL \u2014 \u043d\u043e \u043a\u043e\u0433\u0434\u0430 \u0432\u043e\u043a\u0440\u0443\u0433 \u0443\u0436\u0435 C#-\u0441\u0442\u0435\u043a, \u0434\u0435\u0440\u0436\u0430\u0442\u044c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e JVM \u0440\u0430\u0434\u0438\u00a0<code>&lt;switch&gt;\/&lt;foreach parallel-execution=\"true\"&gt;\/&lt;endpoint uri=\"rabbitmq:\/?...\"&gt;<\/code>\u00a0\u2014 \u043b\u0438\u0448\u043d\u0435\u0435 \u0437\u0432\u0435\u043d\u043e. Route \u0434\u0430\u0451\u0442 \u0442\u0435 \u0436\u0435 EIP, \u043d\u043e \u0432 \u043e\u0434\u043d\u043e\u043c \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435, \u043d\u0430 \u043e\u0434\u043d\u043e\u043c \u044f\u0437\u044b\u043a\u0435, \u0441 C#-\u0442\u0438\u043f\u0438\u0437\u0430\u0446\u0438\u0435\u0439 \u0438 \u043e\u0431\u0449\u0438\u043c\u0438\u00a0<code>IServiceProvider<\/code>-\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u044f\u043c\u0438.<\/p>\n<\/blockquote>\n<p>\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f:<\/p>\n<pre><code>\/\/ Program.csbuilder.Services.AddRedbRoute(route =&gt;    route.AddRouteBuilder&lt;GpsHttpFacadeRoutes&gt;());builder.Services.AddRedbRouteHttp();builder.Services.AddRedbRouteKafka();builder.Services.AddRedbRouteRabbitMQ();builder.Services.AddRedbRouteRedis();<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041c\u0430\u0440\u0448\u0440\u0443\u0442 \u0441\u0442\u0430\u0440\u0442\u0443\u0435\u0442 \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u043c, \u043e\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u043d\u0430 shutdown \u0441 graceful drain, \u043c\u0435\u0442\u0440\u0438\u043a\u0438 OpenTelemetry \u2014 \u043f\u043e \u043a\u0430\u0436\u0434\u043e\u043c\u0443 \u0448\u0430\u0433\u0443 \u0438\u0437 \u043a\u043e\u0440\u043e\u0431\u043a\u0438 (<code>route.gps-http-facade.duration<\/code>,\u00a0<a href=\"http:\/\/multicast.to\" rel=\"noopener noreferrer nofollow\"><code>multicast.to<\/code><\/a><code>[1].failures<\/code>,\u00a0<code>idempotent.duplicates<\/code>,\u00a0<code>throttle.rejected<\/code>).<\/p>\n<blockquote>\n<p>\u041f\u043e\u043b\u043d\u044b\u0439 \u043a\u043e\u0434 \u0442\u0430\u043a\u043e\u0433\u043e \u0430\u0433\u0433\u0440\u0435\u0433\u0430\u0442\u043e\u0440\u0430 (\u0442\u043e\u043b\u044c\u043a\u043e \u0441 Kafka \u043d\u0430 \u0432\u0445\u043e\u0434\u0435, \u0431\u0435\u0437 HTTP) \u2014 \u043d\u0438\u0436\u0435, \u0432 \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u00a0<a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/c%3A\/Work\/redb_code\/csharp\/redb\/docs\/launch-2026-05\/HABR_ARTICLE_ROUTE_DRAFT.md#%D0%B2%D1%82%D0%BE%D1%80%D0%BE%D0%B9-%D0%BF%D1%80%D0%BE%D0%B4--rabbitmq-%D0%BA%D0%B0%D0%BA-rpc-%D0%B8-pubsub-%D1%88%D0%B8%D0%BD%D0%B0\" rel=\"noopener noreferrer nofollow\">\u00ab\u0412\u0442\u043e\u0440\u043e\u0439 \u043f\u0440\u043e\u0434 \u2014 RabbitMQ \u043a\u0430\u043a RPC \u0438 pub\/sub \u0448\u0438\u043d\u0430\u00bb<\/a>. \u0410 \u043a\u043e\u043c\u0443 \u043d\u0443\u0436\u0435\u043d\u00a0<code>hello world<\/code>\u00a0\u043d\u0430 15 \u0441\u0442\u0440\u043e\u043a \u2014 \u043e\u043d \u0432\u00a0<a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/c%3A\/Work\/redb_code\/csharp\/redb\/docs\/launch-2026-05\/redb.Route.Demo\" rel=\"noopener noreferrer nofollow\"><code>redb.Route.Demo<\/code><\/a>.<\/p>\n<\/blockquote>\n<hr\/>\n<h4>\u0427\u0435\u0433\u043e \u0445\u043e\u0442\u0435\u043b Camel \u2014 \u0438 \u0447\u0442\u043e \u0438\u0437 \u044d\u0442\u043e\u0433\u043e \u0435\u0441\u0442\u044c<\/h4>\n<p>EIP-\u043f\u0430\u0442\u0442\u0435\u0440\u043d\u043e\u0432 \u0438\u0437 \u043a\u0430\u043d\u043e\u043d\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u043a\u043d\u0438\u0436\u043a\u0438 \u0432 Camel \u2014 80+. \u041c\u044b \u0437\u0430\u043a\u0440\u044b\u043b\u0438 30+ \u2014 \u0441\u0430\u043c\u044b\u0435 \u0445\u043e\u0434\u043e\u0432\u044b\u0435. \u0412\u0441\u0435 \u043e\u043d\u0438 \u2014 first-class \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b DSL:<\/p>\n<pre><code>\/\/ Content-Based Router.Choice()    .When(Header(\"priority\").isEqualTo(\"high\")).To(\"direct:\/\/fast-lane\")    .When(Header(\"priority\").isEqualTo(\"low\")).To(\"seda:\/\/batch\")    .Otherwise().To(\"direct:\/\/standard\").EndChoice()\/\/ Splitter \u2014 \u043a\u0430\u0436\u0434\u043e\u0435 \u0432\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u043c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e.Split(e =&gt; e.In.GetBody&lt;Order&gt;().Items)    .Process(async (e, ct) =&gt; await ProcessItemAsync(e, ct)).EndSplit()\/\/ Aggregator \u2014 \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u043c \u043f\u043e correlationId.Aggregate(Header(\"orderId\"),    strategy: new CompletionAggregator(),    completionSize: 10,    completionTimeout: TimeSpan.FromSeconds(30))\/\/ WireTap \u2014 \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u043a\u043e\u043f\u0438\u044e \u0432 audit \u0431\u0435\u0437 \u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u043a\u0438 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0433\u043e \u043f\u043e\u0442\u043e\u043a\u0430.WireTap(\"seda:\/\/audit\")\/\/ Recipient List \u2014 \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u0438\u0439 fan-out.RecipientList(e =&gt; ResolveDestinations(e))\/\/ Idempotent Consumer \u2014 \u0434\u0435\u0434\u0443\u043f\u043b\u0438\u043a\u0430\u0446\u0438\u044f \u043f\u043e \u043a\u043b\u044e\u0447\u0443 (persistent backend).IdempotentConsumer(Header(\"messageId\"), repository: redbRepository)<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0418 \u0435\u0449\u0451:\u00a0<code>Multicast<\/code>,\u00a0<code>Dynamic Router<\/code>,\u00a0<code>Resequencer<\/code>,\u00a0<code>Scatter-Gather<\/code>,\u00a0<code>Claim Check<\/code>,\u00a0<code>Throttle<\/code>,\u00a0<code>Delay<\/code>,\u00a0<code>Loop<\/code>,\u00a0<code>Enrich<\/code>,\u00a0<code>PollEnrich<\/code>,\u00a0<code>Saga<\/code>\u00a0(\u0431\u0435\u0437 durable state \u2014 \u043a\u043e\u043c\u043f\u0435\u043d\u0441\u0438\u0440\u0443\u044e\u0449\u0438\u0435 \u0448\u0430\u0433\u0438),\u00a0<code>Circuit Breaker<\/code>,\u00a0<code>Validate<\/code>,\u00a0<code>Marshal\/Unmarshal<\/code>,\u00a0<code>Transacted<\/code>,\u00a0<code>Process<\/code>. \u0418 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0435 \u043e\u0431\u0432\u044f\u0437\u043a\u0438 \u2014\u00a0<code>Retry<\/code>,\u00a0<code>DeadLetterChannel<\/code>,\u00a0<code>TryCatch\/DoCatch\/DoFinally<\/code>,\u00a0<code>OnException<\/code>.<\/p>\n<hr\/>\n<h4>Compiled expression engine<\/h4>\n<p>\u0412 Camel \u0435\u0441\u0442\u044c\u00a0<a href=\"https:\/\/camel.apache.org\/components\/4.4.x\/languages\/simple-language.html\" rel=\"noopener noreferrer nofollow\">Simple Language<\/a>\u00a0\u2014\u00a0<code>${header.priority}<\/code>,\u00a0<code>${body.items.size()}<\/code>\u00a0\u0438 \u0442\u0430\u043a \u0434\u0430\u043b\u0435\u0435. \u0423 \u043d\u0435\u0433\u043e \u043e\u0434\u0438\u043d \u043c\u0438\u043d\u0443\u0441: \u043e\u043d \u0438\u043d\u0442\u0435\u0440\u043f\u0440\u0435\u0442\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0432 \u0440\u0430\u043d\u0442\u0430\u0439\u043c\u0435. \u041d\u0430 \u0433\u043e\u0440\u044f\u0447\u0435\u043c \u043f\u0443\u0442\u0438 \u044d\u0442\u043e \u0437\u0430\u043c\u0435\u0442\u043d\u043e.<\/p>\n<p>\u0423 redb.Route \u0435\u0441\u0442\u044c\u00a0<strong>\u0442\u043e \u0436\u0435 \u0441\u0430\u043c\u043e\u0435, \u043d\u043e \u043a\u043e\u043c\u043f\u0438\u043b\u0438\u0440\u0443\u0435\u043c\u043e\u0435<\/strong>.\u00a0<code>${header.priority}<\/code>,\u00a0<code>${body.OrderId}<\/code>, \u0430\u0440\u0438\u0444\u043c\u0435\u0442\u0438\u043a\u0430, JSONPath, XPath \u2014 \u0432\u0441\u0451 \u044d\u0442\u043e \u043f\u0430\u0440\u0441\u0438\u0442\u0441\u044f \u043e\u0434\u0438\u043d \u0440\u0430\u0437 \u043f\u0440\u0438\u00a0<code>Build()<\/code>\u00a0\u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430, \u043f\u0440\u0435\u0432\u0440\u0430\u0449\u0430\u0435\u0442\u0441\u044f \u0432\u00a0<code>Expression&lt;Func&lt;IExchange, T&gt;&gt;<\/code>\u00a0\u0447\u0435\u0440\u0435\u0437\u00a0<code>System.Linq.Expressions<\/code>, \u043a\u043e\u043c\u043f\u0438\u043b\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0432 IL \u0438 \u043a\u044d\u0448\u0438\u0440\u0443\u0435\u0442\u0441\u044f. \u041d\u0430 \u0433\u043e\u0440\u044f\u0447\u0435\u043c \u043f\u0443\u0442\u0438 \u2014 \u043e\u0431\u044b\u0447\u043d\u044b\u0439 \u0434\u0435\u043b\u0435\u0433\u0430\u0442.<\/p>\n<pre><code>\/\/ \u041f\u0440\u0435\u0434\u0438\u043a\u0430\u0442\u044b \u0432 Choice.When \/ Filter \u2014 \u0442\u0440\u0438 \u0444\u043e\u0440\u043c\u044b:.When(e =&gt; e.In.GetHeader&lt;string&gt;(\"priority\") == \"high\")   \/\/ 1. lambda.When(Header(\"priority\").isEqualTo(\"high\"))                \/\/ 2. typed builder.When(\"header.priority == 'high'\")                         \/\/ 3. string expression\/\/ String templates \u0432 SetBody \/ SetHeader \/ Log:.SetHeader(\"reply\", \"${header.orderId}-confirmed\").SetBody(\"Order ${body.Id}: total=${body.Total} for ${header.customerName}\").Log(\"Processed ${header.orderId} in ${header.elapsed}ms\")<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041c\u043e\u0436\u043d\u043e \u0441\u043c\u0435\u0448\u0438\u0432\u0430\u0442\u044c. Internally \u2014 \u043e\u0434\u0438\u043d \u0438 \u0442\u043e\u0442 \u0436\u0435 AST \u0441 \u043e\u0434\u043d\u0438\u043c \u0438 \u0442\u0435\u043c \u0436\u0435 \u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0442\u043e\u0440\u043e\u043c.<\/p>\n<hr\/>\n<h4>\u0422\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u044b<\/h4>\n<p>22 \u0432\u043d\u0435\u0448\u043d\u0438\u0445 + 5 \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0445:<\/p>\n<div>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th>\n<p align=\"left\">\u041a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u044f<\/p>\n<\/th>\n<th>\n<p align=\"left\">\u0422\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u044b<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">\u041e\u0447\u0435\u0440\u0435\u0434\u0438 \/ \u0448\u0438\u043d\u044b<\/p>\n<\/td>\n<td>\n<p align=\"left\">Kafka, RabbitMQ, IBM MQ, MQTT (5.0), AMQP 1.0, Azure Service Bus, Redis<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">HTTP \/ RPC<\/p>\n<\/td>\n<td>\n<p align=\"left\">HTTP (in\/out), WebSocket, SignalR, gRPC, TCP<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">\u0424\u0430\u0439\u043b\u044b \/ \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430<\/p>\n<\/td>\n<td>\n<p align=\"left\">SFTP, FTP, File, S3, Firebase (Firestore + Cloud Storage + FCM), GenericFile (base)<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">\u0411\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445<\/p>\n<\/td>\n<td>\n<p align=\"left\">SQL (polling outbox-style)<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">\u041a\u043e\u0440\u043f\u043e\u0440\u0430\u0442\u0438\u0432\u043d\u044b\u0435<\/p>\n<\/td>\n<td>\n<p align=\"left\">LDAP \/ Active Directory, Mail (SMTP\/IMAP\/POP3), Elasticsearch 8.x<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">\u041f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0449\u0438\u043a<\/p>\n<\/td>\n<td>\n<p align=\"left\">Quartz, Cron<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">\u0412\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0435<\/p>\n<\/td>\n<td>\n<p align=\"left\">Direct, SEDA, Timer, Mock, Log<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<p>\u041a\u0430\u0436\u0434\u044b\u0439 \u2014 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 NuGet-\u043f\u0430\u043a\u0435\u0442 (<code>redb.Route.Kafka<\/code>,\u00a0<code>redb.Route.RabbitMQ<\/code>\u00a0\u0438 \u0442.\u0434.). \u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u044e\u0442\u0441\u044f \u044f\u0432\u043d\u043e \u0447\u0435\u0440\u0435\u0437\u00a0<code>AddRedbRoute*()<\/code>. \u0423 \u0431\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u0430 \u2014 fluent builder \u0441\u0432\u0435\u0440\u0445\u0443 URI-\u0441\u0442\u0440\u043e\u043a\u0438:<\/p>\n<pre><code>\/\/ URI formFrom(\"kafka:\/\/orders?groupId=svc&amp;brokers=broker1:9092,broker2:9092&amp;autoOffsetReset=earliest\");\/\/ Type-safe builder \u2014 \u0442\u043e \u0436\u0435 \u0441\u0430\u043c\u043e\u0435From(Kafka.Topic(\"orders\")    .Brokers(\"broker1:9092\", \"broker2:9092\")    .GroupId(\"svc\")    .AutoOffsetReset(AutoOffsetReset.Earliest)    .Acks(\"All\"));<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>Builder \u043b\u0443\u0447\u0448\u0435 \u0442\u0435\u043c, \u0447\u0442\u043e (\u0430) intellisense, (\u0431) \u043e\u043f\u0435\u0447\u0430\u0442\u043a\u0438 \u043b\u043e\u0432\u044f\u0442\u0441\u044f \u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0442\u043e\u0440\u043e\u043c, (\u0432) refactor \u2192 rename \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442.<\/p>\n<hr\/>\n<h4>\u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043e\u0448\u0438\u0431\u043e\u043a \u2014 \u0447\u0435\u0442\u044b\u0440\u0435 \u0443\u0440\u043e\u0432\u043d\u044f<\/h4>\n<p>\u0421\u0430\u043c\u043e\u0435 \u0431\u043e\u043b\u044c\u043d\u043e\u0435 \u0432 hand-rolled-\u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f\u0445. \u0423 Route \u2014 \u0447\u0435\u0442\u044b\u0440\u0435 \u0443\u0440\u043e\u0432\u043d\u044f, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043a\u043e\u043c\u0431\u0438\u043d\u0438\u0440\u0443\u044e\u0442\u0441\u044f:<\/p>\n<p><strong>1. Per-step\u00a0<\/strong><code><strong>Retry<\/strong><\/code>\u00a0\u2014 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0439 retry \u0432\u043e\u043a\u0440\u0443\u0433 \u043e\u0434\u043d\u043e\u0433\u043e \u0448\u0430\u0433\u0430. \u041a\u043e\u0433\u0434\u0430 \u0432\u043d\u0435\u0448\u043d\u0438\u0439 \u0441\u0435\u0440\u0432\u0438\u0441 \u0444\u043b\u0430\u043a\u0430\u044e\u0449\u0438\u0439, \u043d\u043e \u0432 \u0446\u0435\u043b\u043e\u043c \u0436\u0438\u0432\u043e\u0439:<\/p>\n<pre><code>.Retry(maxRetries: 5, initialDelay: TimeSpan.FromSeconds(1)).To(\"http:\/\/flaky-service\/submit\")<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><strong>2.\u00a0<\/strong><code><strong>DeadLetterChannel<\/strong><\/code>\u00a0\u2014 route-level. \u041b\u044e\u0431\u0430\u044f \u043d\u0435\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u043e\u0441\u043b\u0435 retry \u0443\u0445\u043e\u0434\u0438\u0442 \u0432 \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 sink. Exception \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442\u0441\u044f \u043d\u0430 exchange, \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044c \u0432 DLC-\u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0435:<\/p>\n<pre><code>From(\"kafka:\/\/orders\")    .DeadLetterChannel(\"seda:\/\/dlq\")    .Process(...)    .To(\"sql:\/\/orders\");From(\"seda:\/\/dlq\")    .Log(\"DLQ: ${exception.message}\")    .Choice()        .When(e =&gt; e.GetException() is TimeoutException).To(\"seda:\/\/retry-later\")        .Otherwise().To(\"sftp:\/\/archive\/failed\/\")    .EndChoice();<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><strong>3.\u00a0<\/strong><code><strong>DoTry \/ DoCatch \/ DoFinally<\/strong><\/code>\u00a0\u2014 scoped, \u043a\u0430\u043a \u043e\u0431\u044b\u0447\u043d\u044b\u0439 try\/catch \u0432\u043d\u0443\u0442\u0440\u0438 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430:<\/p>\n<pre><code>.DoTry()    .To(\"http:\/\/external-api\/submit\")    .Process(async (e, ct) =&gt; await PostProcess(e, ct)).DoCatch&lt;HttpRequestException&gt;()    .Log(\"HTTP failure: ${exception.message}\")    .To(\"seda:\/\/retry-queue\").DoCatch&lt;TimeoutException&gt;()    .Log(\"Timeout, archiving\")    .To(\"sftp:\/\/archive\/timeouts\/\").DoFinally()    .Log(\"Attempt complete (success or failure)\").End()<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><strong>4.\u00a0<\/strong><code><strong>OnException&lt;T&gt;<\/strong><\/code><strong>\u00a0\u2014 \u043d\u0430 \u0443\u0440\u043e\u0432\u043d\u0435 \u0432\u0441\u0435\u0433\u043e \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430.<\/strong>\u00a0\u0413\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u044b\u0435 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438: \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u0432\u00a0<code>IRouteContext<\/code>\u00a0\u0438 \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u044e\u0442\u0441\u044f \u043a\u043e\u00a0<strong>\u0432\u0441\u0435\u043c<\/strong>\u00a0\u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430\u043c \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430, \u0430 \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u043a\u00a0<code>From(...)<\/code>\u00a0\u0438\u0437 \u044d\u0442\u043e\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430. \u041e\u0431\u044a\u044f\u0432\u0438\u043b \u043e\u0434\u0438\u043d \u0440\u0430\u0437 \u0432 \u043b\u044e\u0431\u043e\u043c\u00a0<code>RouteBuilder<\/code>\u00a0\u2014 \u043b\u043e\u0432\u0438\u0442 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043f\u043e \u0432\u0441\u0435\u043c\u00a0<code>From(...)<\/code>\u00a0\u0432\u0441\u0435\u0445\u00a0<code>RouteBuilder<\/code>-\u043e\u0432 \u044d\u0442\u043e\u0433\u043e \u043c\u043e\u0434\u0443\u043b\u044f:<\/p>\n<pre><code>public class OrderRoutes : RouteBuilder{    protected override void Configure()    {        OnException&lt;HttpRequestException&gt;()            .MaximumRedeliveries(5)            .UseExponentialBackOff()            .BackOffMultiplier(2.0)            .Handled()                  \/\/ exchange \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0430\u0435\u0442 \u0438\u0434\u0442\u0438 \u043a\u0430\u043a \u043d\u0438 \u0432 \u0447\u0451\u043c            .To(\"seda:\/\/http-failures\")        .EndOnException();        OnException&lt;DbException&gt;()            .MaximumRedeliveries(2)            .UseOriginalMessage()       \/\/ \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0442\u0435\u043b\u043e \u0434\u043e \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438            .To(\"seda:\/\/db-failures\")        .EndOnException();        \/\/ \u042d\u0442\u0438 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438 \u043b\u043e\u0432\u044f\u0442 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0442\u0443\u0442, \u0430 \u0432\u043e \u0432\u0441\u0435\u0445        \/\/ RouteBuilder-\u0430\u0445 \u043c\u043e\u0434\u0443\u043b\u044f \u2014 \u043e\u043d\u0438 \u0436\u0438\u0432\u0443\u0442 \u043d\u0430 \u0443\u0440\u043e\u0432\u043d\u0435 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430:        From(\"kafka:\/\/orders\").To(\"http:\/\/payments-svc\/charge\");        From(\"kafka:\/\/shipments\").To(\"http:\/\/logistics-svc\/dispatch\");    }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><code>.Handled()<\/code>,\u00a0<code>.Continued()<\/code>,\u00a0<code>.UseExponentialBackOff()<\/code>,\u00a0<code>.UseOriginalMessage()<\/code>\u00a0\u2014 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0435 \u043a\u0440\u0443\u0442\u0438\u043b\u043a\u0438 Camel&#8217;\u0430, \u043f\u0435\u0440\u0435\u043d\u0435\u0441\u0451\u043d\u043d\u044b\u0435 \u043e\u0434\u0438\u043d-\u0432-\u043e\u0434\u0438\u043d.<\/p>\n<hr\/>\n<h4>Transactional routes \u2014 \u0447\u0442\u043e \u043f\u043e\u0434 \u043a\u0430\u043f\u043e\u0442\u043e\u043c<\/h4>\n<p><code>.Transacted()<\/code>\u00a0\u2014 \u044d\u0442\u043e \u043d\u0435 \u043f\u0440\u043e\u0441\u0442\u043e \u043c\u0435\u0442\u043a\u0430 \u00ab\u043e\u0431\u0435\u0440\u043d\u0438 \u0432 TransactionScope\u00bb. \u042d\u0442\u043e \u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442 \u0441 \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u043e\u043c: \u043f\u0440\u0438 \u0443\u0441\u043f\u0435\u0445\u0435 \u0432\u0441\u0435\u0433\u043e pipeline \u0441\u0434\u0435\u043b\u0430\u0439 commit\/ack, \u043f\u0440\u0438 failure \u2014 rollback\/nack.<\/p>\n<pre><code>From(\"kafka:\/\/orders?groupId=svc&amp;brokers=...\")    .Transacted()                          \/\/ \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f    .ProcessWithRedb(async (redb, e, ct) =&gt; await SaveOrderAsync(redb, e, ct))    .To(\"rabbitmq:\/\/orders-confirmed\");    \/\/ \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u0435\u0442\u0441\u044f \u0432 RabbitMQ                                           \/\/ commit Kafka offset + commit RabbitMQ + commit redb \u2014 \u0430\u0442\u043e\u043c\u0430\u0440\u043d\u043e<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0427\u0442\u043e \u0440\u0435\u0430\u043b\u044c\u043d\u043e \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442:<\/p>\n<ul>\n<li>\n<p><strong>Kafka-\u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442<\/strong>\u00a0\u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442 transactional producer (<code>enable.idempotence=true<\/code>,\u00a0<a href=\"http:\/\/transactional.id\" rel=\"noopener noreferrer nofollow\"><code>transactional.id<\/code><\/a>), \u043a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440 \u043a\u043e\u043c\u043c\u0438\u0442\u0438\u0442 offset \u0447\u0435\u0440\u0435\u0437\u00a0<code>producer.SendOffsetsToTransaction(...)<\/code>\u00a0\u0432 \u0442\u043e\u0439 \u0436\u0435 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438. \u042d\u0442\u043e\u00a0<a href=\"https:\/\/www.confluent.io\/blog\/exactly-once-semantics-are-possible-heres-how-apache-kafka-does-it\/\" rel=\"noopener noreferrer nofollow\">Kafka EOS<\/a>\u00a0\u2014 exactly-once \u043c\u0435\u0436\u0434\u0443 partition&#8217;\u0430\u043c\u0438.<\/p>\n<\/li>\n<li>\n<p><strong>RabbitMQ-\u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442<\/strong>\u00a0\u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442 publisher confirms (<code>MaxOutstandingConfirms<\/code>), \u043f\u043b\u044e\u0441 \u043f\u0440\u0438\u00a0<code>.Transacted()<\/code>\u00a0\u2014 transacted channels (<code>tx-select \/ tx-commit \/ tx-rollback<\/code>).<\/p>\n<\/li>\n<li>\n<p><strong>IBM MQ, AMQP 1.0<\/strong>\u00a0\u2014 \u043d\u0430\u0442\u0438\u0432\u043d\u044b\u0435 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438 \u0447\u0435\u0440\u0435\u0437\u00a0<code>MQGMO_SYNCPOINT<\/code>\/local transactions.<\/p>\n<\/li>\n<li>\n<p><strong>SQL-\u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442<\/strong>\u00a0\u0431\u0438\u043d\u0434\u0438\u0442\u00a0<code>IDbTransaction<\/code>\u00a0\u043a \u043a\u0430\u0436\u0434\u043e\u043c\u0443 \u0448\u0430\u0433\u0443 \u0447\u0435\u0440\u0435\u0437\u00a0<code>TransactionScope<\/code>\u00a0(\u0440\u0430\u0441\u043f\u0440\u0435\u0434\u0435\u043b\u0451\u043d\u043d\u0443\u044e \u2014 \u0434\u043b\u044f SQL Server, \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u0443\u044e \u2014 \u0434\u043b\u044f PostgreSQL \u0438 MySQL).<\/p>\n<\/li>\n<\/ul>\n<p>\u041f\u043e\u0434 \u043a\u0430\u043f\u043e\u0442\u043e\u043c \u2014 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u00a0<code>ITransactedAction<\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043a\u0430\u0436\u0434\u044b\u0439 \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442 \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u0442 \u043f\u043e-\u0441\u0432\u043e\u0435\u043c\u0443. \u0421 \u0442\u043e\u0447\u043a\u0438 \u0437\u0440\u0435\u043d\u0438\u044f \u0432\u0430\u0448\u0435\u0433\u043e \u043a\u043e\u0434\u0430 \u2014 \u043e\u0434\u043d\u0430 \u0441\u0442\u0440\u043e\u0447\u043a\u0430\u00a0<code>.Transacted()<\/code>.<\/p>\n<hr\/>\n<h4>\u0420\u0435\u0430\u043b\u044c\u043d\u044b\u0439 \u043a\u043e\u0434 \u0438\u0437 \u043f\u0440\u043e\u0434\u0430<\/h4>\n<p>\u042d\u0442\u0438 \u043a\u0443\u0441\u043a\u0438 \u0432\u044b\u0434\u0440\u0430\u043b \u0438\u0437 \u0431\u043e\u0435\u0432\u043e\u0433\u043e \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u044f \u0442\u043e\u0439 \u0441\u0430\u043c\u043e\u0439 \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0435\u0439 TMS. \u0418\u043c\u0435\u043d\u0430 \u043c\u0435\u0442\u043e\u0434\u043e\u0432 \u0438 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u043a\u0430\u043a \u0435\u0441\u0442\u044c.<\/p>\n<p><strong>InitRoute \u2014 \u0442\u043e\u0447\u043a\u0430 \u0432\u0445\u043e\u0434\u0430 \u043c\u043e\u0434\u0443\u043b\u044f Tsak.<\/strong>\u00a0\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b (\u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u044b), redb-\u0441\u0445\u0435\u043c\u044b, \u0441\u043b\u043e\u0432\u0430\u0440\u0438, \u0438 11 RouteBuilder-\u043a\u043b\u0430\u0441\u0441\u043e\u0432:<\/p>\n<pre><code>public static class InitRoute{    public static IRouteContext main(IRouteContext context)    {        \/\/ 1. \u0422\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u044b \u043c\u043e\u0434\u0443\u043b\u044f        context.AddComponent(new SqlComponent());        context.AddComponent(new HttpComponent { ServerManager = new SharedHttpServerManager() });        context.AddComponent(new CronComponent());        \/\/ 2. Named SQL data source \u2014 connection string \u0432 DI, \u043d\u0435 \u0432 URI \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430        DbProviderFactories.RegisterFactory(\"Microsoft.Data.SqlClient\", SqlClientFactory.Instance);        context.AddToRegistry(\"sap-s4\", (ISqlConnectionFactory)new SqlConnectionFactory(            new SqlConnectionOptions            {                ConnectionString = sapConn,                ProviderName = \"Microsoft.Data.SqlClient\"            }));        \/\/ 3. \u0421\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u044f redb-\u0441\u0445\u0435\u043c \u2014 code-first, \u0431\u0435\u0437 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0439        var redb = context.GetRedbService();        redb.InitializeAsync(ensureCreated: true).GetAwaiter().GetResult();        redb.SyncSchemeAsync&lt;Driver&gt;().GetAwaiter().GetResult();        redb.SyncSchemeAsync&lt;Vehicle&gt;().GetAwaiter().GetResult();        redb.SyncSchemeAsync&lt;TransportationRoute&gt;().GetAwaiter().GetResult();        redb.SyncSchemeAsync&lt;TransportationPoint&gt;().GetAwaiter().GetResult();        \/\/ ... \u0435\u0449\u0451 8 \u0441\u0445\u0435\u043c        EnsureTsUmLists(redb);        await RefDataCache.RefreshAsync(redb);        \/\/ 4. \u041c\u0430\u0440\u0448\u0440\u0443\u0442\u044b        var rc = (RouteContext)context;        rc.AddRoutes(new TsumExceptionRouteBuilder());     \/\/ global OnException        rc.AddRoutes(new TsumRouteBuilder());              \/\/ SQL polling \u043e\u0442 SAP S\/4        rc.AddRoutes(new TsumPlacementRouteBuilder());     \/\/ \u0440\u0430\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u043c\u0430\u0448\u0438\u043d        rc.AddRoutes(new TsumTransportStatusRouteBuilder());        rc.AddRoutes(new TsumShippingPointRouteBuilder());        rc.AddRoutes(new TsumBackupRouteBuilder());        \/\/ daily cron backup        rc.AddRoutes(new TsumCleanupRouteBuilder());        rc.AddRoutes(new TsumSliceJobRouteBuilder());        \/\/ ... \u0435\u0449\u0451 3        return context;    }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><strong>SQL polling \u0438\u0437 SAP S\/4 stored procedure \u2192 XML \u2192 redb.<\/strong>\u00a0\u041e\u0434\u0438\u043d \u0438\u0437 \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u0445 ETL-\u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043e\u0432:<\/p>\n<pre><code>private void ConfigureSqlConsumer(){    var procs = Context!.GetProperty&lt;IDictionary&lt;string, object?&gt;&gt;(\"Procedures\")!;    var proc = (string)procs[\"MonitoringReport\"]!;   \/\/ \u0438\u043c\u044f \u043f\u0440\u043e\u0446\u0435\u0434\u0443\u0440\u044b \u2014 \u0438\u0437 \u043a\u043e\u043d\u0444\u0438\u0433\u0430, \u043f\u043e Mode (prod\/test)    var builder = Sql.Poll($\"EXEC {proc} @DateFrom, @DateTo, @OutputFormat\")        .DataSource(Constant(\"sap-s4\"))        .CommandTimeout(300)        .Delay(60000)                                \/\/ \u043e\u043f\u0440\u043e\u0441 \u0440\u0430\u0437 \u0432 \u043c\u0438\u043d\u0443\u0442\u0443        .OutputType(SqlOutputType.Scalar)            \/\/ FOR XML \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u043e\u0434\u043d\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435        .Param(\"DateFrom\", (object?)null)        .Param(\"DateTo\", (object?)null)        .Param(\"OutputFormat\", \"XML\");    From(builder)        .RouteId(\"tsum-sql-consumer\")        .Process(e =&gt; e.In.ContentType = \"application\/xml\")        .Process(DeserializeXml)                     \/\/ XmlSerializer \u2192 List&lt;TransportationOrder&gt;        .To(\"direct:\/\/tsum\");}private void ConfigureHttpConsumer(){    \/\/ \u0422\u043e\u0442 \u0436\u0435 \u043a\u043e\u043d\u0435\u0447\u043d\u044b\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a, \u043d\u043e \u0444\u0440\u043e\u043d\u0442 \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u043e\u0432 \u0440\u0443\u043a\u0430\u043c\u0438    From(\"http:0.0.0.0:5089\/api\/tsum\/monitoring?inOut=true\")        .RouteId(\"tsum-http-consumer\")        .ConvertBody&lt;string&gt;()        .Process(DeserializeXml)        .To(\"direct:\/\/tsum\");}private void ConfigureProcessing(){    \/\/ \u0415\u0434\u0438\u043d\u0430\u044f \u0442\u043e\u0447\u043a\u0430 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u2014 \u0447\u0442\u043e \u0438\u0437 SQL, \u0447\u0442\u043e \u0438\u0437 HTTP    From(\"direct:\/\/tsum\")        .RouteId(\"tsum-processing\")        .ProcessWithRedb(async (redb, exchange, ct) =&gt;        {            var orders = exchange.In.GetBody&lt;List&lt;TransportationOrder&gt;&gt;();            if (orders is null || orders.Count == 0) return;            \/\/ \u0421\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u044f \u0441\u043f\u0440\u0430\u0432\u043e\u0447\u043d\u0438\u043a\u043e\u0432 \u0432\u043e\u0434\u0438\u0442\u0435\u043b\u0435\u0439\/\u043c\u0430\u0448\u0438\u043d\/\u0442\u043e\u0447\u0435\u043a            var dicts = await DictionarySyncService.SyncFromOrdersAsync(redb, orders, ct);            if (dicts.AnyChanged) await RefDataCache.RefreshAsync(redb);            \/\/ \u0414\u0435\u0434\u0443\u043f\u043b\u0438\u043a\u0430\u0446\u0438\u044f \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0445 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043e\u0432            var codes = orders.Select(o =&gt; o.Code).ToList();            var existing = await redb.Query&lt;TransportationRoute&gt;()                .WhereRedb(o =&gt; codes.Contains(o.ValueString!))                .ToListAsync();            \/\/ ... \u0435\u0449\u0451 ~200 \u0441\u0442\u0440\u043e\u043a \u0431\u0438\u0437\u043d\u0435\u0441-\u043b\u043e\u0433\u0438\u043a\u0438        });}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0417\u0430\u043c\u0435\u0442\u043d\u0430\u044f \u0448\u0442\u0443\u043a\u0430:\u00a0<strong>\u043e\u0434\u0438\u043d \u0438 \u0442\u043e\u0442 \u0436\u0435 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a<\/strong>\u00a0(<code>direct:\/\/tsum<\/code>) \u043e\u0431\u0441\u043b\u0443\u0436\u0438\u0432\u0430\u0435\u0442 \u0438 SQL-\u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a, \u0438 HTTP-\u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442 \u0434\u043b\u044f \u0440\u0443\u0447\u043d\u044b\u0445 \u0442\u0435\u0441\u0442\u043e\u0432. \u042d\u0442\u043e \u0438 \u0435\u0441\u0442\u044c \u0442\u043e\u0442 \u0441\u0430\u043c\u044b\u0439 Camel&#8217;\u043e\u0432\u0441\u043a\u0438\u0439 \u0442\u0440\u044e\u043a \u2014\u00a0<code>direct:<\/code>\u00a0\u043a\u0430\u043a in-process invariant&#8217;\u043d\u044b\u0439 \u043a\u0430\u043d\u0430\u043b, \u043a \u043a\u043e\u0442\u043e\u0440\u043e\u043c\u0443 \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u0438\u0446\u0435\u043f\u0438\u0442\u044c \u043b\u044e\u0431\u043e\u0439\u00a0<code>To(...)<\/code>.<\/p>\n<p><strong>HTTP API \u043d\u0430 \u043f\u043e\u0440\u0442\u0443 5090 \u0441 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0435\u0439 \u0447\u0435\u0440\u0435\u0437 JWT + LDAP.<\/strong>\u00a0\u041e\u0434\u0438\u043d \u0438\u0437 endpoint&#8217;\u043e\u0432:<\/p>\n<pre><code>private void ConfigureRoutesEndpoint(){    From(\"http:0.0.0.0:5090\/api\/tsum\/routes?inOut=true&amp;cors=true&amp;corsOrigins=*\")        .RouteId(\"tsum-api-routes\")        .Process(Auth.ProcessAsync)                         \/\/ \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f JWT, \u043d\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 headers        .Process(TsumAuthProcessor.RequirePermission(TsumPermission.ViewAll))        .ConvertBody&lt;string&gt;()        .ProcessWithRedb(async (redb, exchange, ct) =&gt;        {            var bodyStr = exchange.In.Body?.ToString() ?? \"{}\";            var filter = JsonSerializer.Deserialize&lt;RouteFilterRequest&gt;(bodyStr, JsonOptions);            \/\/ GET-\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0442\u043e\u0436\u0435 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u043c \u2014 \u0434\u043b\u044f \u043e\u0442\u043b\u0430\u0434\u043a\u0438 \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435            string? QP(string name) =&gt;                exchange.In.Headers.TryGetValue($\"redbHttp.QueryParam.{name}\", out var v)                    ? v?.ToString() : null;            if (QP(\"startPlanFrom\") is { } qpFrom &amp;&amp;                DateTimeOffset.TryParse(qpFrom, out var from))                filter.StartPlanFrom ??= from;            \/\/ ... \u0435\u0449\u0451 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e query-\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432            var query = redb.Query&lt;TransportationRoute&gt;();            \/\/ \u0414\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u044f \u0447\u0435\u0440\u0435\u0437 redb LINQ            if (!string.IsNullOrEmpty(filter.CodeSearch))                query = query.WhereRedb(o =&gt; o.ValueString!.Contains(filter.CodeSearch));            var spItems = await ResolveIds(filter.ShippingPointIds);            if (spItems.Count &gt; 0)                query = query.Where(r =&gt; spItems.Contains(r.ShippingPoint));            \/\/ ... \u0435\u0449\u0451 ~10 \u0444\u0438\u043b\u044c\u0442\u0440\u043e\u0432 \u0438 \u043f\u0430\u0433\u0438\u043d\u0430\u0446\u0438\u044f            var items = await query.Skip(offset).Take(limit).ToListAsync();            JsonRouteHelper.SetJsonBody(exchange, new { total, offset, limit, items });        });}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>HTTP-\u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442 \u0437\u0434\u0435\u0441\u044c \u2014 \u044d\u0442\u043e\u00a0<a href=\"https:\/\/github.com\/redbase-app\/redb-route\/tree\/main\/src\/redb.Route.Http\" rel=\"noopener noreferrer nofollow\"><code>redb.Route.Http<\/code><\/a>\u00a0\u0441 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u043c Kestrel-\u043a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440\u043e\u043c.\u00a0<code>inOut=true<\/code>\u00a0\u2014 request\/response \u0440\u0435\u0436\u0438\u043c, \u043e\u0442\u0432\u0435\u0442 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442\u0441\u044f \u0432\u00a0<code>Body<\/code>\u00a0exchange&#8217;\u0430. \u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438\u00a0<code>redbHttp.QueryParam.*<\/code>\u00a0\u043f\u0430\u0440\u0441\u044f\u0442\u0441\u044f \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u043e\u043c \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438.<\/p>\n<p><strong>Cron backup \u0432\u0441\u0435\u0439 \u0411\u0414 \u0447\u0435\u0440\u0435\u0437 redb.Export, \u0441 \u0440\u043e\u0442\u0430\u0446\u0438\u0435\u0439.<\/strong>\u00a0\u0411\u0435\u0437 \u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0438\u0445 \u0448\u0435\u0434\u0443\u043b\u0435\u0440\u043e\u0432:<\/p>\n<pre><code>protected override void Configure(){    var backupConfig = Context!.GetProperty&lt;IDictionary&lt;string, object?&gt;&gt;(\"Backup\");    var directory = (string)backupConfig[\"Directory\"]! ?? \"backups\";    var retentionDays = int.Parse(backupConfig[\"RetentionDays\"]?.ToString() ?? \"7\");    Context.SetProperty(\"_backup.directory\", directory);    Context.SetProperty(\"_backup.retentionDays\", retentionDays);    From(\"cron:\/\/tsum-backup?schedule=0 0 3 * * ?\")    \/\/ \u043a\u0430\u0436\u0434\u044b\u0439 \u0434\u0435\u043d\u044c \u0432 03:00        .RouteId(\"tsum-backup-cron\")        .ProcessWithRedb(RunBackupAsync);}private async Task RunBackupAsync(IRedbService redb, IExchange exchange, CancellationToken ct){    var pgConn = redb.Configuration.ConnectionString!;    var directory = Context!.GetProperty&lt;string&gt;(\"_backup.directory\")!;    Directory.CreateDirectory(directory);    var fileName = $\"tsum_backup_{DateTime.UtcNow:yyyy-MM-dd_HHmmss}.redb\";    var filePath = Path.Combine(directory, fileName);    var provider = ProviderFactory.Create(\"postgres\");    await provider.OpenAsync(pgConn, ct);    var exportService = new ExportService(provider, verbose: false, batchSize: 10000);    await exportService.ExportAsync(filePath, schemeIds: null, compress: true, dryRun: false, ct);    RotateBackups(directory, retentionDays);}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0422\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u00a0<code>cron:\/\/<\/code>\u00a0\u2014 \u044d\u0442\u043e\u00a0<code>redb.Route.Quartz<\/code>\u00a0\u0441 <a href=\"http:\/\/Quartz.NET\" rel=\"noopener noreferrer nofollow\">Quartz.NET<\/a> \u043f\u043e\u0434 \u043a\u0430\u043f\u043e\u0442\u043e\u043c. Cron-expression \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439,\u00a0<a href=\"https:\/\/www.quartz-scheduler.net\/documentation\/quartz-3.x\/tutorial\/crontriggers.html\" rel=\"noopener noreferrer nofollow\">Quartz-\u043e\u0432\u0441\u043a\u0438\u0439<\/a>.<\/p>\n<p>\u0415\u0441\u043b\u0438 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043d\u0430 \u044d\u0442\u0438 \u0442\u0440\u0438 \u0444\u0440\u0430\u0433\u043c\u0435\u043d\u0442\u0430 \u2014 \u0442\u0430\u043c \u043d\u0435\u0442 \u043d\u0438 \u043e\u0434\u043d\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u0438 \u043f\u0440\u043e \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u043a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440\u043e\u0432, \u043f\u0440\u043e \u0440\u0430\u0431\u043e\u0442\u0443 \u0441\u00a0<code>IServiceProvider<\/code>, \u043f\u0440\u043e lifecycle, \u043f\u0440\u043e graceful shutdown. \u0412\u0441\u0451 \u044d\u0442\u043e \u0441\u0438\u0434\u0438\u0442 \u0432\u043d\u0443\u0442\u0440\u0438 Route. \u0411\u0438\u0437\u043d\u0435\u0441-\u043a\u043e\u0434 \u2014 \u044d\u0442\u043e\u00a0<code>Process(...)<\/code>\u00a0\u0438\u00a0<code>ProcessWithRedb(...)<\/code>.<\/p>\n<hr\/>\n<h4>\u0412\u0442\u043e\u0440\u043e\u0439 \u043f\u0440\u043e\u0434 \u2014 RabbitMQ \u043a\u0430\u043a RPC \u0438 pub\/sub \u0448\u0438\u043d\u0430<\/h4>\n<p>\u0422\u0435 \u043a\u0443\u0441\u043a\u0438 \u0432\u044b\u0448\u0435 \u043f\u0440\u043e TMS \u2014 \u044d\u0442\u043e SQL + HTTP + cron. \u0427\u0442\u043e\u0431\u044b \u043d\u0435 \u0441\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u043b\u043e\u0441\u044c \u0432\u043f\u0435\u0447\u0430\u0442\u043b\u0435\u043d\u0438\u0435 \u00abredb.Route \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u043e\u0434 polling-\u0437\u0430\u0434\u0430\u0447\u0438\u00bb, \u0432\u043e\u0442 \u0444\u0440\u0430\u0433\u043c\u0435\u043d\u0442\u044b \u0438\u0437 \u0432\u0442\u043e\u0440\u043e\u0439 \u043f\u0440\u043e\u0434\u043e\u0432\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u0442\u043e\u0433\u043e \u0436\u0435 \u0437\u0430\u043a\u0430\u0437\u0447\u0438\u043a\u0430 \u2014 \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u044b \u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0438 \u043d\u0430 ~150k \u0437\u0430\u043a\u0430\u0437\u043e\u0432\/\u043c\u0435\u0441. \u041c\u043e\u0434\u0443\u043b\u0438 \u043e\u0431\u0449\u0430\u044e\u0442\u0441\u044f \u0434\u0440\u0443\u0433 \u0441 \u0434\u0440\u0443\u0433\u043e\u043c \u0447\u0435\u0440\u0435\u0437 RabbitMQ: \u043e\u0434\u043d\u0438 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u044b \u2014 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439 RPC \u043f\u043e\u0432\u0435\u0440\u0445 \u043e\u0447\u0435\u0440\u0435\u0434\u0435\u0439 \u0441\u00a0<code>CorrelationId<\/code>, \u0434\u0440\u0443\u0433\u0438\u0435 \u2014 fire-and-forget pub\/sub. \u0421\u0442\u0430\u0440\u0430\u044f \u0432\u0435\u0440\u0441\u0438\u044f \u043a\u043e\u0434\u0430 \u0431\u044b\u043b\u0430 \u043d\u0430 snake-case-\u0444\u043e\u0440\u043a\u0435 \u043d\u0430\u0448\u0435\u0433\u043e DSL; \u0441\u0435\u0439\u0447\u0430\u0441 \u0432\u0441\u0451 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043d\u0430 \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u043e\u043c 3.0 \u2014 \u043f\u0440\u0438\u0432\u043e\u0436\u0443 \u043a \u043a\u0430\u043d\u043e\u043d\u0438\u0447\u0435\u0441\u043a\u043e\u043c\u0443 API.<\/p>\n<p><strong>RPC \u0447\u0435\u0440\u0435\u0437 RabbitMQ \u2014 \u0437\u0430\u043f\u0440\u043e\u0441\/\u043e\u0442\u0432\u0435\u0442 \u0441\u00a0<\/strong><code><strong>CorrelationId<\/strong><\/code><strong>.<\/strong>\u00a0\u041c\u043e\u0434\u0443\u043b\u044c ControlPanel \u043f\u0438\u0448\u0435\u0442 \u0432 \u043e\u0447\u0435\u0440\u0435\u0434\u044c\u00a0<a href=\"http:\/\/dal.controlpanel.trips.get.tracking.link\" rel=\"noopener noreferrer nofollow\"><code>dal.controlpanel.trips.get.tracking.link<\/code><\/a>, \u0436\u0434\u0451\u0442 \u043e\u0442\u0432\u0435\u0442 \u0441 \u0442\u0435\u043c \u0436\u0435\u00a0<code>CorrelationId<\/code>. \u041d\u0430 \u0441\u0442\u043e\u0440\u043e\u043d\u0435 DAL:<\/p>\n<pre><code>public class TripLinkRouteBuilder : RouteBuilder{    protected override void Configure()    {        From(Rabbit.Queue(\"dal.controlpanel.trips.get.tracking.link\")                   .ConnectionFactory(\"RabbitMQConnectionFactory\"))            .RouteId(\"controlpanel_getTrackingLink\")            .SetHeader(\"routeId\", \"controlpanel_getTrackingLink\")            \/\/ \u0412\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f: \u0431\u0435\u0437 CorrelationId \u0440\u043e\u0443\u0442\u0438\u0442\u044c \u043d\u0435\u0447\u0435\u0433\u043e            .Filter(Header(\"CorrelationId\").IsNull())                .DoThrow&lt;ArgumentNullException&gt;(                    \"CorrelationId is required. RouteId='controlpanel_getTrackingLink'\")            .EndFilter()            .Log(\"GetTrackingLink DAL STARTED: CorrelationId=${header.CorrelationId}\")            .InvokeController()                            \/\/ \u2192 TripController.GetTrackingLink            .DbSaveChanges()                               \/\/ commit EF Core changes            .ApiPackResponse(                              \/\/ \u0443\u043f\u0430\u043a\u043e\u0432\u043a\u0430 \u0432 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 envelope                toData:  Property(\"data\"),                toMeta:  Property(\"meta\"),                code_dal: 200,                packJwt: true)            .WireTap(\"direct:\/\/delivery.info.send\")        \/\/ \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u0430\u044f \u043d\u043e\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u2014 fire-and-forget            .Log(\"GetTrackingLink DAL COMPLETED: CorrelationId=${header.CorrelationId}\")            .Respond();                                    \/\/ \u043e\u0442\u0432\u0435\u0442 \u043d\u0430 reply-to queue \u0441 \u0442\u0435\u043c \u0436\u0435 correlationId    }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><code>Rabbit.Queue(...).ConnectionFactory(...)<\/code>\u00a0\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 named-\u0444\u0430\u0431\u0440\u0438\u043a\u0443 \u0438\u0437 DI-registry (\u043a\u0430\u043a\u00a0<code>named SQL data source<\/code>\u00a0\u0432 \u043f\u0435\u0440\u0432\u043e\u043c \u043f\u0440\u043e\u0434\u0435 \u2014 \u0441\u0435\u043a\u0440\u0435\u0442\u044b \u043d\u0435 \u0442\u043e\u0440\u0447\u0430\u0442 \u0432 URI \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430).\u00a0<code>.Respond()<\/code>\u00a0\u2014 \u044d\u0442\u043e RabbitMQ RPC-pattern: \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442 \u0441\u0430\u043c \u0447\u0438\u0442\u0430\u0435\u0442\u00a0<code>reply-to<\/code>\u00a0\u0438\u00a0<code>correlation-id<\/code>\u00a0\u0438\u0437 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u043e\u0432 \u0432\u0445\u043e\u0434\u044f\u0449\u0435\u0433\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0438 \u043f\u0443\u0431\u043b\u0438\u043a\u0443\u0435\u0442 \u0442\u0435\u043b\u043e exchange&#8217;\u0430 \u043e\u0431\u0440\u0430\u0442\u043d\u043e. \u041d\u0438\u043a\u0430\u043a\u043e\u0433\u043e \u0440\u0443\u0447\u043d\u043e\u0433\u043e\u00a0<code>BasicProperties.ReplyTo<\/code>\u00a0\/\u00a0<code>BasicProperties.CorrelationId<\/code>\u00a0\u0432 \u043a\u043e\u0434\u0435.<\/p>\n<p>\u0413\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u044b\u0439\u00a0<code>OnException<\/code>\u00a0\u043d\u0430 \u0432\u0435\u0441\u044c RouteBuilder \u2014 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 envelope \u043e\u0448\u0438\u0431\u043a\u0438 + 500:<\/p>\n<pre><code>protected override void Configure(){    if (!HasExceptionRoute&lt;Exception&gt;())        OnException&lt;Exception&gt;()            .Log(\"Exception: ${exception.message}\", LogLevel.Error)            .SetHeader(\"code_dal\", (int)HttpStatusCode.InternalServerError)            .SetBody(e =&gt; new BaseErrorResponse            {                traceId     = e.In.GetHeader&lt;string&gt;(\"CorrelationId\") ?? \"no-correlation\",                code        = (int)HttpStatusCode.InternalServerError,                message     = e.Exception?.Message ?? \"Internal server error.\",                description = e.Exception?.ToString()            }.ToJson())            .ExceptionHandled()            .Stop()        .EndOnException();    \/\/ ConfigureGetTrackingLinkRoute() ... \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 routes \u0432\u044b\u0448\u0435}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><strong>Pub\/sub \u0447\u0435\u0440\u0435\u0437 RabbitMQ \u2014 email-\u043d\u043e\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.<\/strong>\u00a0\u041b\u044e\u0431\u043e\u0439 \u043c\u043e\u0434\u0443\u043b\u044c \u043c\u043e\u0436\u0435\u0442 \u0431\u0440\u043e\u0441\u0438\u0442\u044c exchange&#8217;\u0443\u00a0<code>lt.dal<\/code>\u00a0\u0441 routing key\u00a0<a href=\"http:\/\/send.email\" rel=\"noopener noreferrer nofollow\"><code>send.email<\/code><\/a>\u00a0\u2014 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 SMTP-\u0432\u043e\u0440\u043a\u0435\u0440 (\u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u043f\u0440\u043e\u0446\u0435\u0441\u0441, \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u043c\u043e\u0434\u0443\u043b\u044c Tsak) \u043f\u043e\u0434\u0431\u0435\u0440\u0451\u0442 \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442. \u041b\u043e\u043a\u0430\u043b\u044c\u043d\u0430\u044f \u0442\u043e\u0447\u043a\u0430 \u0432\u0445\u043e\u0434\u0430 \u2014\u00a0<code>direct:\/\/send-trip-email-notification<\/code>, \u0432 \u043d\u0435\u0451 \u043f\u0438\u0448\u0443\u0442 \u0432\u0441\u0435 RouteBuilder&#8217;\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u043c \u043d\u0443\u0436\u043d\u043e \u0443\u0432\u0435\u0434\u043e\u043c\u0438\u0442\u044c:<\/p>\n<pre><code>public class EmailNotificationRouteBuilder : RouteBuilder{    protected override void Configure()    {        From(\"direct:\/\/send-trip-email-notification\")            .RouteId(\"dal_sendTripEmailNotification\")            .Log(LogLevel.Debug,                \"SendTripEmailNotification: tripId=${property.tripId}, type=${property.emailType}\")            .Process((e, ct) =&gt;            {                \/\/ \u0418\u0437\u043e\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 scope \u043f\u043e\u0434 scope'd DbContext (route \u043e\u0442\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u0432 WireTap-\u0442\u0430\u0441\u043a\u0435)                using var scope = Context.GetServiceProvider()!.CreateScope();                var db     = scope.ServiceProvider.GetRequiredService&lt;LtContext&gt;();                var logger = Context.GetService&lt;ILogger&gt;();                var cfg    = Context.GetProperty&lt;IDictionary&lt;string, object?&gt;&gt;(\"EmailNotifications\");                if (!IsEnabled(cfg)) { e.In.SetHeader(\"code_dal\", 204); return; }                var tripId = e.GetProperty&lt;long&gt;(\"tripId\");                var trip   = db.Trips.AsNoTracking()                    .Include(t =&gt; t.Driver).ThenInclude(d =&gt; d!.IdNavigation).ThenInclude(a =&gt; a.Branch)                    .Include(t =&gt; t.Trippoints).ThenInclude(tp =&gt; tp.Location)                    .FirstOrDefault(t =&gt; t.Id == tripId);                if (trip?.Driver?.IdNavigation?.Branch is not { } branch)                { e.In.SetHeader(\"code_dal\", 404); return; }                \/\/ \u0410\u0434\u0440\u0435\u0441\u0430\u0442\u043e\u0432 \u0431\u0435\u0440\u0451\u043c \u0438\u0437 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a \u0444\u0438\u043b\u0438\u0430\u043b\u0430 \u2014 \u0441\u043f\u0438\u0441\u043e\u043a email'\u043e\u0432 \u0432 \u043e\u0434\u043d\u043e\u0439 \u0438\u0437 \u0434\u0432\u0443\u0445 \u043a\u043e\u043b\u043e\u043d\u043e\u043a                var emailType = e.GetProperty&lt;string&gt;(\"emailType\");                var recipients = EmailHelper.ParseAndValidateEmailList(                    emailType == \"trip-point-change\"                        ? branch.Trippointorderchangeemaillist                        : branch.Tripcompletionemaillist);                if (recipients.Count == 0)                { e.In.SetHeader(\"code_dal\", 204); return; }                var (subject, html) = BuildEmailContent(trip, emailType, cfg, e);                e.In.SetHeader(\"emailTo\",          string.Join(\",\", recipients));                e.In.SetHeader(\"emailSubject\",     subject);                e.In.SetHeader(\"emailContentType\", \"text\/html\");                e.In.SetBody(new { content = html }.ToJson());                e.In.SetHeader(\"code_dal\", 200);            })            \/\/ \u0422\u043e\u043b\u044c\u043a\u043e \u0435\u0441\u043b\u0438 \u043f\u0438\u0441\u044c\u043c\u043e \u0440\u0435\u0430\u043b\u044c\u043d\u043e \u0433\u043e\u0442\u043e\u0432\u043e \u043a \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0435 \u2014 \u043f\u0443\u0448\u0438\u043c \u0432 \u043e\u0447\u0435\u0440\u0435\u0434\u044c            .Filter(Header(\"code_dal\").IsEqualTo(200))                .To(Rabbit.Exchange(\"lt.dal\", type: \"topic\")                          .RoutingKey(\"send.email\")                          .ConnectionFactory(\"RabbitMQConnectionFactory\"))                .Log(\"Email sent to RabbitMQ exchange lt.dal\/send.email\")            .EndFilter();    }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0414\u0430\u043b\u044c\u0448\u0435 \u044d\u0442\u0443 direct-\u0442\u043e\u0447\u043a\u0443 \u0434\u0451\u0440\u0433\u0430\u044e\u0442 \u0447\u0435\u0440\u0435\u0437\u00a0<code>WireTap<\/code>\u00a0\u0438\u0437 \u043b\u044e\u0431\u043e\u0433\u043e \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u0435\u043d\u044f\u0435\u0442 \u043f\u043e\u0435\u0437\u0434\u043a\u0443:<\/p>\n<pre><code>\/\/ \u0413\u0434\u0435-\u0442\u043e \u0432 TripRouteBuilder, \u043f\u043e\u0441\u043b\u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0433\u043e UPDATE.SetProperty(\"tripId\",        Header(\"trip-id\")).SetProperty(\"emailType\",     Constant(\"trip-point-change\")).SetProperty(\"changeDescription\", e =&gt; \"Order added on point #\" + e.In.GetHeader&lt;int&gt;(\"point-seq\")).WireTap(\"direct:\/\/send-trip-email-notification\")<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><code>WireTap<\/code>\u00a0\u043a\u043e\u043f\u0438\u0440\u0443\u0435\u0442 exchange \u0438 \u043f\u0443\u0448\u0438\u0442 \u043a\u043e\u043f\u0438\u044e \u0432 \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 endpoint \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e, \u043d\u0435 \u0431\u043b\u043e\u043a\u0438\u0440\u0443\u044f \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u043f\u043e\u0442\u043e\u043a. \u041f\u043e\u043b\u0443\u0447\u0430\u0442\u0435\u043b\u044c \u2014\u00a0<code>direct:\/\/...<\/code>\u00a0\u0432 \u044d\u0442\u043e\u043c \u0436\u0435 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435, \u0434\u0430\u043b\u044c\u0448\u0435 \u2014 RabbitMQ. \u0415\u0441\u043b\u0438 SMTP-\u0432\u043e\u0440\u043a\u0435\u0440 \u043b\u0435\u0436\u0438\u0442 \u2014 \u043f\u0438\u0441\u044c\u043c\u0430 \u043a\u043e\u043f\u044f\u0442\u0441\u044f \u0432 \u043e\u0447\u0435\u0440\u0435\u0434\u0438, \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 HTTP API \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0430\u0435\u0442 \u043e\u0442\u0432\u0435\u0447\u0430\u0442\u044c.<\/p>\n<p><strong>Kafka \u2014 GPS-\u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u044b \u0441 \u043c\u043e\u0431\u0438\u043b\u043e\u043a.<\/strong>\u00a0\u0421\u0430\u043c\u044b\u0439 \u043d\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043d\u044b\u0439 \u043c\u0430\u0440\u0448\u0440\u0443\u0442 \u0442\u043e\u0439 \u0436\u0435 \u0441\u0438\u0441\u0442\u0435\u043c\u044b. \u0412\u043e\u0434\u0438\u0442\u0435\u043b\u0438 (~500 \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e) \u043b\u044c\u044e\u0442 \u0431\u0430\u0442\u0447\u0438 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442 \u0447\u0435\u0440\u0435\u0437 \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u0435 API \u2192 \u0442\u043e\u043f\u0438\u043a\u00a0<code>mobile.trips.location.sync<\/code>. \u041d\u0430 \u043a\u0430\u0436\u0434\u043e\u0439 \u043d\u043e\u0434\u0435 \u043a\u043b\u0430\u0441\u0442\u0435\u0440\u0430 \u0441\u0438\u0434\u0438\u0442\u00a0<strong>\u043e\u0434\u0438\u043d<\/strong>\u00a0consumer; Kafka Consumer Group \u0441\u0430\u043c \u0440\u0430\u0437\u0434\u0430\u0451\u0442 \u043f\u0430\u0440\u0442\u0438\u0446\u0438\u0438 \u043c\u0435\u0436\u0434\u0443 \u043d\u043e\u0434\u0430\u043c\u0438 (3 \u043f\u0430\u0440\u0442\u0438\u0446\u0438\u0438 \u2192 3 \u043d\u043e\u0434\u044b, \u043f\u043e \u043e\u0434\u043d\u043e\u0439 \u043d\u0430 \u043a\u0430\u0436\u0434\u0443\u044e). \u0414\u0430\u043b\u044c\u0448\u0435 \u2014 \u0433\u0440\u0443\u043f\u043f\u0438\u0440\u043e\u0432\u043a\u0430 \u043f\u043e\u00a0<code>tripId<\/code>, \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u044f\u044f \u0442\u043e\u0447\u043a\u0430 \u043a\u0430\u0436\u0434\u043e\u0433\u043e\u00a0<code>tripId<\/code>\u00a0\u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e \u0443\u0445\u043e\u0434\u0438\u0442 \u0432 Redis (<code>last GPS location<\/code>\u00a0\u0434\u043b\u044f \u0432\u0435\u0431-\u0434\u0430\u0448\u0431\u043e\u0440\u0434\u0430), \u043f\u043e\u043b\u043d\u044b\u0439 \u0431\u0430\u0442\u0447 \u0440\u0430\u0441\u0441\u044b\u043b\u0430\u0435\u0442\u0441\u044f \u043f\u043e RabbitMQ-\u043e\u0447\u0435\u0440\u0435\u0434\u044f\u043c DAL-\u043c\u043e\u0434\u0443\u043b\u0435\u0439 (\u043e\u0434\u043d\u0430 \u2014 \u043d\u0430 \u0437\u0430\u043f\u0438\u0441\u044c \u0432 \u0411\u0414, \u0432\u0442\u043e\u0440\u0430\u044f \u2014 \u043d\u0430 \u0434\u0435\u0442\u0435\u043a\u0446\u0438\u044e check-in\/check-out, \u0442\u0440\u0435\u0442\u044c\u044f \u2014 \u043d\u0430 \u043f\u0435\u0440\u0435\u0441\u0447\u0451\u0442 \u043c\u0435\u0442\u0440\u0438\u043a):<\/p>\n<pre><code>public class GpsKafkaBatchAggregatorBuilder : RouteBuilder{    protected override void Configure()    {        var cfg = Context.GetSection(GetType().Name);        \/\/ \u041e\u0434\u0438\u043d consumer \u043d\u0430 \u043d\u043e\u0434\u0443. \u041f\u0430\u0440\u0442\u0438\u0446\u0438\u0438 (0..N) \u0440\u0430\u0441\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442 Consumer Group.        \/\/ partition= \u041d\u0415 \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u043c \u2014 \u0438\u043d\u0430\u0447\u0435 \u0441\u043b\u043e\u043c\u0430\u0435\u043c \u0431\u0430\u043b\u0430\u043d\u0441\u0438\u0440\u043e\u0432\u043a\u0443.        From(Kafka.Topic(cfg.GetValue(\"kafka.Topic\", \"mobile.trips.location.sync\"))                  .Brokers(cfg.GetValue(\"kafka.Brokers\", \"localhost:9092\"))                  .GroupId(cfg.GetValue(\"kafka.GroupId\", \"gps-batch-aggregator\"))                  .MaxPollRecords(cfg.GetValue(\"kafka.MaxPollRecords\", 100))                  .AutoOffsetReset(\"earliest\"))            .RouteId(\"GpsKafkaBatchAggregator\")            .Transacted()                                       \/\/ Kafka EOS: commit offset \u0432 \u043e\u0434\u043d\u043e\u0439 tx \u0441 downstream            .Log(\"incoming batch: kafka.batch.size=${header.kafka.batch.size}\")            .To(\"direct:\/\/gps-batch-grouper\");        From(\"direct:\/\/gps-batch-grouper\")            .RouteId(\"GpsBatchGrouper\")            .Process(new GpsBatchGrouperProcessor(outputType: GroupingOutputType.Dictionary))            .Log(\"trips=${header.gps.trips.count}, points=${header.gps.total.points}\")            \/\/ \u0418\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u043c \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u044e\u044e \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u0443 \u043f\u043e \u043a\u0430\u0436\u0434\u043e\u043c\u0443 tripId \u2192 property \u0434\u043b\u044f Redis            .Process((e, ct) =&gt;            {                var grouped = e.In.GetBody&lt;Dictionary&lt;long, List&lt;LocationHistory&gt;&gt;&gt;();                var last = grouped.ToDictionary(                    kvp =&gt; kvp.Key.ToString(),                    kvp =&gt; new GpsLocationDto(                        ts:  kvp.Value.Max(l =&gt; l.ts).ToString(\"o\"),                        lat: kvp.Value.OrderByDescending(l =&gt; l.ts).First().lat,                        lon: kvp.Value.OrderByDescending(l =&gt; l.ts).First().lon).ToJson());                e.SetProperty(\"gpsLastLocationMap\", last);            })            \/\/ Fire-and-forget \u0432 Redis \u2014 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 pipeline \u0436\u0434\u0430\u0442\u044c \u043d\u0435 \u0434\u043e\u043b\u0436\u0435\u043d            .WireTap(\"direct:\/\/save-gps-last-location\")            \/\/ \u0420\u0430\u0437\u0434\u0430\u0447\u0430 \u0431\u0430\u0442\u0447\u0430 \u043f\u043e \u0442\u0440\u0451\u043c \u043e\u0447\u0435\u0440\u0435\u0434\u044f\u043c DAL            .Multicast().ParallelProcessing()                .To(Rabbit.Exchange(\"lt.dal\").RoutingKey(\"dal.trips.location.sync\"))                .To(Rabbit.Exchange(\"lt.dal\").RoutingKey(\"dal.gps.checkin_checkout\"))                .To(Rabbit.Exchange(\"lt.dal\").RoutingKey(\"dal.metrics.calculate\"))            .EndMulticast();        \/\/ \u0417\u0430\u043f\u0438\u0441\u044c last-location \u0432 Redis (TTL 24 \u0447\u0430\u0441\u0430). \u041a\u043b\u044e\u0447: gps:last:location:{tripId}        From(\"direct:\/\/save-gps-last-location\")            .RouteId(\"SaveGpsLastLocation\")            .Split(Property(\"gpsLastLocationMap\"))                .SetHeader(\"tripId\", e =&gt; ((KeyValuePair&lt;string, string&gt;)e.In.GetBody()).Key)                .SetBody(e =&gt;            ((KeyValuePair&lt;string, string&gt;)e.In.GetBody()).Value)                .To(\"redis:set:gps:last:location:${header.tripId}?ttl=86400\")            .EndSplit();    }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0421\u043e\u0441\u0435\u0434\u043d\u0438\u0439 consumer (<code>mobile.trips.unplanned.stops<\/code>) \u0434\u0435\u043b\u0430\u0435\u0442 \u0434\u0435\u0442\u0435\u043a\u0446\u0438\u044e \u043d\u0435\u0437\u0430\u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043e\u043a \u2014 \u0442\u0430\u043c \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0435\u043d\u00a0<strong>\u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0439<\/strong>\u00a0<code>OnException<\/code>. JSON, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0435 \u043f\u0430\u0440\u0441\u0438\u0442\u0441\u044f \u2014 \u044d\u0442\u043e\u00a0<strong>\u043d\u0435<\/strong>\u00a0\u0442\u0440\u0430\u043d\u0437\u0438\u0435\u043d\u0442\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430, retry \u043d\u0435 \u043f\u043e\u043c\u043e\u0436\u0435\u0442. \u041c\u0430\u0440\u0448\u0440\u0443\u0442 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0432 RabbitMQ-DLQ \u0441 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u043c\u0438 (partition, offset, preview \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b\u044c\u043d\u043e\u0433\u043e body) \u0438 \u043f\u0440\u043e\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0435\u0442 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043d\u0430\u0432\u0435\u0440\u0445 \u2014 consumer \u043f\u0430\u0434\u0430\u0435\u0442, Kafka \u043e\u0442\u043a\u0430\u0442\u044b\u0432\u0430\u0435\u0442 offset, \u043f\u043e\u0434\u043d\u0438\u043c\u0430\u0435\u0442\u0441\u044f \u0437\u0430\u043d\u043e\u0432\u043e, \u0438 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u043d\u0430\u044f \u043f\u0430\u0440\u0442\u0438\u0446\u0438\u044f \u0443\u0439\u0434\u0451\u0442 \u0432 DLQ \u043f\u0440\u0438 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439 \u043f\u043e\u043f\u044b\u0442\u043a\u0435. \u042d\u0442\u043e \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0439 pattern \u0434\u043b\u044f poison message:<\/p>\n<pre><code>From(Kafka.Topic(\"mobile.trips.unplanned.stops\").GroupId(\"unplanned-stops-processor\")          .Brokers(brokers).AutoOffsetReset(\"latest\"))    .RouteId(\"UnplannedStop-Kafka\")    .Transacted()    .To(\"direct:\/\/unplanned-stop-grouper\");From(\"direct:\/\/unplanned-stop-grouper\")    .RouteId(\"UnplannedStop-Grouper\")    .OnException&lt;JsonException&gt;()        .MaximumRedeliveries(0)    \/\/ JSON \u043d\u0435 \u0441\u0442\u0430\u043d\u0435\u0442 \u0432\u0430\u043b\u0438\u0434\u043d\u044b\u043c \u043e\u0442 \u043f\u043e\u0432\u0442\u043e\u0440\u0430        .Handled(false)            \/\/ \u043a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440 \u0414\u041e\u041b\u0416\u0415\u041d \u0443\u043f\u0430\u0441\u0442\u044c, \u0447\u0442\u043e\u0431 Kafka \u043d\u0435 \u0437\u0430\u043a\u043e\u043c\u043c\u0438\u0442\u0438\u043b\u0430 offset        .Process(e =&gt;        {            e.In.SetHeader(\"dlq.errorType\",     \"JsonException\");            e.In.SetHeader(\"dlq.errorMessage\",  e.Exception?.Message);            e.In.SetHeader(\"dlq.partition\",     e.In.GetHeader&lt;int&gt;(\"kafka.partition\"));            e.In.SetHeader(\"dlq.offset\",        e.In.GetHeader&lt;long&gt;(\"kafka.offset\"));            e.In.SetHeader(\"dlq.rawBodyPreview\",                Truncate(e.GetProperty&lt;string&gt;(\"OriginalKafkaBody\") ?? e.In.GetBody&lt;string&gt;(), 500));        })        .To(Rabbit.Exchange(\"lt.dlq\").RoutingKey(\"dal.dlq.gps.json\"))        .Log(LogLevel.Error,            \"JSON failed \u2192 DLQ: partition=${header.kafka.partition}, offset=${header.kafka.offset}\")    .EndOnException()    .Process(new UnplannedStopDetectorProcessor(cacheManager, lookbackMinutes: 60))    .To(\"direct:\/\/persist-unplanned-stops\");<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0448\u0442\u0443\u043a, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0432\u0438\u0434\u043d\u043e \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0430 \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u043c \u043f\u0440\u043e\u0434\u0435:<\/p>\n<ul>\n<li>\n<p><code><strong>partition=<\/strong><\/code><strong>\u00a0\u0432 Kafka URI \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u043d\u0435\u043b\u044c\u0437\u044f.<\/strong>\u00a0\u0427\u0443\u0442\u044c \u0432\u044b\u0448\u0435 \u043f\u0440\u043e\u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043e \u0432 \u043a\u043e\u0434\u0435 \u2014 \u044d\u0442\u043e \u0442\u0438\u043f\u0438\u0447\u043d\u0430\u044f \u0433\u0440\u0430\u0431\u043b\u044f. \u0415\u0441\u043b\u0438 \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u2014 Consumer Group \u043b\u043e\u043c\u0430\u0435\u0442\u0441\u044f, \u0432\u0441\u0435 \u043d\u043e\u0434\u044b \u043a\u043e\u043d\u043a\u0443\u0440\u0438\u0440\u0443\u044e\u0442 \u0437\u0430 \u043e\u0434\u043d\u0443 \u043f\u0430\u0440\u0442\u0438\u0446\u0438\u044e.<\/p>\n<\/li>\n<li>\n<p><code><strong>.Transacted()<\/strong><\/code><strong>\u00a0+\u00a0<\/strong><code><strong>.Multicast()<\/strong><\/code>\u00a0\u2014 \u0431\u0430\u0442\u0447 \u0443\u0445\u043e\u0434\u0438\u0442 \u0432\u043e \u0432\u0441\u0435 \u0442\u0440\u0438 \u043e\u0447\u0435\u0440\u0435\u0434\u0438 \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e (<code>ParallelProcessing<\/code>), \u043d\u043e Kafka offset \u043a\u043e\u043c\u043c\u0438\u0442\u0438\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u0435\u0441\u043b\u0438\u00a0<strong>\u0432\u0441\u0435<\/strong>\u00a0<code>.To(...)<\/code>\u00a0\u043e\u0442\u0440\u0430\u0431\u043e\u0442\u0430\u043b\u0438. \u0415\u0441\u043b\u0438 RabbitMQ \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u2014 \u0431\u0430\u0442\u0447 \u043f\u0440\u0438\u0435\u0434\u0435\u0442 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e, \u0431\u0435\u0437 \u043f\u043e\u0442\u0435\u0440\u044c.<\/p>\n<\/li>\n<li>\n<p><code><strong>WireTap<\/strong><\/code><strong>\u00a0\u0434\u043b\u044f Redis<\/strong>\u00a0\u2014 last-location \u043d\u0443\u0436\u0435\u043d \u043d\u0430 \u0434\u0430\u0448\u0431\u043e\u0440\u0434\u0435 \u00ab\u0437\u0434\u0435\u0441\u044c \u0438 \u0441\u0435\u0439\u0447\u0430\u0441\u00bb, \u043d\u043e \u0435\u0441\u043b\u0438 Redis \u043b\u0435\u0436\u0438\u0442, \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 pipeline \u043d\u0430 \u0437\u0430\u043f\u0438\u0441\u044c \u0432 \u0411\u0414 \u043d\u0435 \u0434\u043e\u043b\u0436\u0435\u043d \u0436\u0434\u0430\u0442\u044c. WireTap \u043a\u043e\u043f\u0438\u0440\u0443\u0435\u0442 exchange \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u043f\u043e\u0442\u043e\u043a.<\/p>\n<\/li>\n<li>\n<p><strong>DLQ \u0441\u00a0<\/strong><code><strong>dlq.rawBodyPreview<\/strong><\/code>\u00a0\u2014 \u043a\u043e\u0433\u0434\u0430 \u0438\u043d\u0436\u0435\u043d\u0435\u0440 \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u0442 DLQ-\u043e\u0447\u0435\u0440\u0435\u0434\u044c, \u0435\u043c\u0443 \u043d\u0443\u0436\u043d\u044b \u043d\u0435 \u00ab\u0447\u0442\u043e-\u0442\u043e \u0441\u043b\u043e\u043c\u0430\u043b\u043e\u0441\u044c\u00bb, \u0430 partition + offset + \u043f\u0435\u0440\u0432\u044b\u0435 500 \u0441\u0438\u043c\u0432\u043e\u043b\u043e\u0432 \u0438\u0441\u0445\u043e\u0434\u043d\u043e\u0433\u043e body. \u0421 \u044d\u0442\u0438\u043c \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u0438\u0439\u0442\u0438 \u043a \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u043c\u0443 \u0440\u0430\u0437\u0440\u0430\u0431\u0443 \u0438 \u0441\u043a\u0430\u0437\u0430\u0442\u044c \u00ab\u0432\u043e\u0442 \u044d\u0442\u043e \u0432\u044b \u0448\u043b\u0451\u0442\u0435\u00bb.<\/p>\n<\/li>\n<\/ul>\n<p>\u0427\u0442\u043e \u0432\u0430\u0436\u043d\u043e \u0443\u0432\u0438\u0434\u0435\u0442\u044c \u043d\u0430 \u044d\u0442\u043e\u043c \u043f\u0440\u0438\u043c\u0435\u0440\u0435:<\/p>\n<ul>\n<li>\n<p><strong>\u041e\u0434\u0438\u043d \u0438 \u0442\u043e\u0442 \u0436\u0435\u00a0<\/strong><code><strong>Rabbit.Queue(...)<\/strong><\/code><strong>\u00a0\/\u00a0<\/strong><a href=\"http:\/\/Rabbit.Exchange\" rel=\"noopener noreferrer nofollow\"><code><strong>Rabbit.Exchange<\/strong><\/code><\/a><code><strong>(...)<\/strong><\/code><strong>\u00a0builder<\/strong>\u00a0\u043f\u043e\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u0438 RPC (\u0441\u00a0<code>.Respond()<\/code>), \u0438 pub\/sub (\u0441\u00a0<code>.To(...)<\/code>) \u2014 \u0440\u0430\u0437\u043d\u0438\u0446\u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u0432 \u0444\u043e\u0440\u043c\u0435 \u0445\u0432\u043e\u0441\u0442\u0430 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430.<\/p>\n<\/li>\n<li>\n<p><code><strong>InvokeController()<\/strong><\/code><strong>\u00a0\/\u00a0<\/strong><code><strong>DbSaveChanges()<\/strong><\/code><strong>\u00a0\/\u00a0<\/strong><code><strong>ApiPackResponse(...)<\/strong><\/code>\u00a0\u2014 \u044d\u0442\u043e \u043d\u0435 \u0447\u0430\u0441\u0442\u044c \u0431\u0430\u0437\u043e\u0432\u043e\u0433\u043e Route, \u044d\u0442\u043e \u0448\u0430\u0433\u0438-\u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0441\u0434\u0435\u043b\u0430\u043b\u0438 \u0432\u043d\u0443\u0442\u0440\u0438 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 (<code>lt.Core.Route<\/code>). \u041b\u044e\u0431\u043e\u0439 \u0448\u0430\u0433 \u2014 \u044d\u0442\u043e\u00a0<code>IProcessor<\/code>, \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0432\u00a0<code>IRouteContext<\/code>, \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0447\u0435\u0440\u0435\u0437 extension method. \u0422\u043e \u0435\u0441\u0442\u044c DSL \u0440\u0430\u0441\u0448\u0438\u0440\u044f\u0435\u043c\u044b\u0439: \u043a\u043e\u043c\u0430\u043d\u0434\u0435 \u043d\u0443\u0436\u043d\u044b \u0441\u0432\u043e\u0438 first-class steps \u043f\u043e\u0434 \u00ab\u0432\u044b\u0437\u043e\u0432\u0438 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440 \u2192 \u0441\u043e\u0445\u0440\u0430\u043d\u0438 EF Core \u2192 \u0443\u043f\u0430\u043a\u0443\u0439 \u043e\u0442\u0432\u0435\u0442\u00bb \u2014 \u043f\u0438\u0448\u0443\u0442 \u0438\u0445 \u043e\u0434\u0438\u043d \u0440\u0430\u0437, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442 \u0432\u043e \u0432\u0441\u0435\u0445 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430\u0445.<\/p>\n<\/li>\n<li>\n<p><code><strong>WireTap<\/strong><\/code><strong>\u00a0+\u00a0<\/strong><code><strong>direct:\/\/<\/strong><\/code>\u00a0\u2014 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 Camel-\u043e\u0432\u0441\u043a\u0438\u0439 \u043f\u0430\u0442\u0442\u0435\u0440\u043d \u0434\u043b\u044f fire-and-forget&#8217;\u0430 \u0432\u043d\u0443\u0442\u0440\u0438 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430. \u041d\u0438\u043a\u0430\u043a\u0438\u0445\u00a0<a href=\"http:\/\/Task.Run\" rel=\"noopener noreferrer nofollow\"><code>Task.Run<\/code><\/a>, \u043d\u0438\u043a\u0430\u043a\u0438\u0445\u00a0<code>IHostedService<\/code>-\u043e\u0447\u0435\u0440\u0435\u0434\u0435\u0439 \u043f\u043e\u0434 \u043a\u0430\u043f\u043e\u0442\u043e\u043c \u2014 \u044d\u0442\u043e \u0432\u0441\u0451 \u0432 Route.<\/p>\n<\/li>\n<\/ul>\n<hr\/>\n<h4>\u0415\u0441\u043b\u0438 \u0445\u043e\u0447\u0435\u0442\u0441\u044f \u043f\u043e\u0442\u0440\u043e\u0433\u0430\u0442\u044c \u0440\u0443\u043a\u0430\u043c\u0438 \u2014\u00a0redb.Route.Demo<\/h4>\n<p>\u0412 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438 \u043b\u0435\u0436\u0438\u0442 \u043c\u043e\u0434\u0443\u043b\u044c\u00a0<a href=\"https:\/\/github.com\/redbase-app\/redb-route\/tree\/main\/redb.Route.Demo\" rel=\"noopener noreferrer nofollow\"><code>redb.Route.Demo<\/code><\/a>\u00a0\u2014 39 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043e\u0432 \u0432 9 \u0441\u0435\u043a\u0446\u0438\u044f\u0445, 18 \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u043e\u0432, \u0432\u0441\u0451 \u0432 \u043e\u0434\u043d\u043e\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0435, \u0432\u0441\u0451 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437\u00a0<code>dotnet run<\/code>. \u041f\u043e \u0441\u0443\u0442\u0438 \u2014 \u0435\u0434\u0438\u043d\u044b\u0439 reference implementation, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0435\u0441\u0442\u044c\u00a0<strong>\u043a\u0430\u0436\u0434\u0430\u044f<\/strong>\u00a0\u0444\u0438\u0447\u0430 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0430: RPC \u0447\u0435\u0440\u0435\u0437 RabbitMQ\/AMQP 1.0\/gRPC\/IBM MQ, WireTap \u0432 Kafka \u0438 \u0444\u0430\u0439\u043b, SQL+TransactionScope, Redis Pub\/Sub, TCP echo, WebSocket push, MQTT, SEDA, DirectVM cross-context, Timer\/Cron,\u00a0<code>CircuitBreaker<\/code>,\u00a0<code>Retry<\/code>\u00a0\u0441 backoff,\u00a0<code>DeadLetterChannel<\/code>,\u00a0<code>Aggregator<\/code>,\u00a0<code>Multicast<\/code>,\u00a0<code>RecipientList<\/code>,\u00a0<code>DynamicRouter<\/code>,\u00a0<code>Loop<\/code>,\u00a0<code>Resequencer<\/code>,\u00a0<code>Enrich<\/code>,\u00a0<code>IdempotentConsumer<\/code>,\u00a0<code>Throttle<\/code>, JSON Schema validation,\u00a0<code>Traced<\/code>+<code>Metered<\/code>, lifecycle listeners, named\u00a0<code>IRedbService<\/code>.<\/p>\n<p>\u0417\u0430\u0445\u043e\u0434\u043d\u043e\u0439 \u043c\u0430\u0440\u0448\u0440\u0443\u0442 \u2014\u00a0<code>POST \/api\/demo<\/code>. \u0414\u0430\u043b\u044c\u0448\u0435 \u0438\u0434\u0451\u0442 \u0442\u0438\u043f\u043e\u0432\u043e\u0439 ESB-pipeline:<\/p>\n<pre><code>HTTP \u2192 Throttle(10\/s) \u2192 IdempotentConsumer \u2192 JSON Schema validation  \u2192 Choice(mode) \u2192 Multicast(parallel)      \u251c\u2500 RabbitMQ RPC  \u2192 stamp.rabbit      \u251c\u2500 AMQP 1.0 RPC  \u2192 stamp.amqp      \u251c\u2500 gRPC RPC      \u2192 stamp.grpc      \u2514\u2500 IBM MQ RPC    \u2192 stamp.wmq  \u2192 BeginTransaction \u2192 SQL INSERT + SELECT \u2192 CommitTransaction  \u2192 WireTap fan-out \u2192 Kafka + File + Redis + MQTT + SEDA  \u2192 JSON response<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u042d\u0442\u043e \u0440\u043e\u0432\u043d\u043e \u0442\u043e, \u0447\u0442\u043e \u0432 \u043f\u0440\u043e\u0434\u0435 \u00ab\u0441\u043a\u043b\u0435\u0438\u0432\u0430\u0435\u0442\u00bb Camel \u0443 \u0434\u0436\u0430\u0432\u0438\u0441\u0442\u043e\u0432 \u2014 \u0442\u043e\u043b\u044c\u043a\u043e \u0437\u0434\u0435\u0441\u044c \u044d\u0442\u043e runnable demo \u043d\u0430 1500 \u0441\u0442\u0440\u043e\u043a C#, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u043e\u0434\u043d\u043e\u0439 \u043a\u043e\u043c\u0430\u043d\u0434\u043e\u0439 \u043f\u0440\u043e\u0442\u0438\u0432\u00a0<code>docker compose<\/code>\u00a0\u0441 RabbitMQ\/Postgres\/Kafka\/Redis\/MQTT.<\/p>\n<p>\u0421\u0430\u043c\u0430\u044f \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u0430\u044f \u0441\u0435\u043a\u0446\u0438\u044f \u0434\u043b\u044f \u0442\u0435\u0445, \u043a\u0442\u043e \u0434\u0443\u043c\u0430\u0435\u0442 \u00ab\u0430 \u043a\u0430\u043a Route \u0434\u0440\u0443\u0436\u0438\u0442 \u0441 redb.Core\u00bb \u2014\u00a0<code>NamedRedbRoutes.cs<\/code>. \u0418\u0437 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430 \u043f\u043e\u0434\u043d\u0438\u043c\u0430\u0435\u0442\u0441\u044f\u00a0<strong>named<\/strong>\u00a0<code>IRedbService<\/code>\u00a0(\u043c\u043e\u0436\u043d\u043e \u0434\u0435\u0440\u0436\u0430\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439 \u043a \u0440\u0430\u0437\u043d\u044b\u043c \u0411\u0414 \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e \u2014\u00a0<code>pg-test<\/code>,\u00a0<code>mssql-test<\/code>), CRUD \u0438\u0434\u0451\u0442 \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e \u0447\u0435\u0440\u0435\u0437\u00a0<code>ProcessWithRedb(\"instance-name\", async (redb, ex, ct) =&gt; ...)<\/code>:<\/p>\n<pre><code>internal sealed class NamedRedbRoutes : RouteBuilder{    protected override void Configure()    {        From(\"timer:\/\/named-redb-check?period=30000&amp;delay=5000\")            .RouteId(\"demo-named-redb-pgsql\")            .Log(\"[NAMED-REDB] checking pg-test instance...\")            \/\/ \u041c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u2014 \u0432\u0435\u0440\u0441\u0438\u044f \u0411\u0414, \u0438\u043c\u044f cache-domain            .ProcessWithRedb(\"pg-test\", (redb, ex) =&gt;            {                ex.In.Headers[\"pg-db-type\"]      = redb.dbType    ?? \"n\/a\";                ex.In.Headers[\"pg-db-version\"]   = redb.dbVersion ?? \"n\/a\";                ex.In.Headers[\"pg-cache-domain\"] = redb.CacheDomain ?? \"n\/a\";            })            .Log(\"[NAMED-REDB] pg-test: ${header.pg-db-type} ${header.pg-db-version}\")            \/\/ SAVE \u2014 RedbObject&lt;TProps&gt; \u0441 \u0430\u0432\u0442\u043e\u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0435\u0439 \u0441\u0445\u0435\u043c\u044b \u043f\u043e [RedbScheme]            .ProcessWithRedb(\"pg-test\", async (redb, ex, ct) =&gt;            {                var item = new RedbObject&lt;DemoItemProps&gt;                {                    name  = $\"Demo-{DateTime.UtcNow:HHmmss}\",                    Props = new DemoItemProps                    {                        Title       = \"Named Redb Demo\",                        Description = $\"Created at {DateTime.UtcNow:O}\",                        Priority    = Random.Shared.Next(1, 10)                    }                };                var id = await redb.SaveAsync(item);                ex.In.Headers[\"saved-id\"] = id.ToString();            })            \/\/ LOAD by id            .ProcessWithRedb(\"pg-test\", async (redb, ex, ct) =&gt;            {                var id = long.Parse(ex.In.Headers[\"saved-id\"]!.ToString()!);                var obj = await redb.LoadAsync&lt;DemoItemProps&gt;(id);                ex.In.Headers[\"loaded\"] = obj is null                    ? \"NOT FOUND\"                    : $\"{obj.name} (priority={obj.Props?.Priority})\";            })            \/\/ QUERY \u0441 LINQ-Where \u043f\u0440\u044f\u043c\u043e \u043f\u043e props            .ProcessWithRedb(\"pg-test\", async (redb, ex, ct) =&gt;            {                var items = await redb.Query&lt;DemoItemProps&gt;()                    .Where(p =&gt; p.Priority &gt; 3)                    .Take(5)                    .ToListAsync();                ex.In.Headers[\"query-count\"] = items.Count.ToString();            })            \/\/ DELETE            .ProcessWithRedb(\"pg-test\", async (redb, ex, ct) =&gt;            {                var id = long.Parse(ex.In.Headers[\"saved-id\"]!.ToString()!);                await redb.DeleteAsync(id);            })            .Log(\"[NAMED-REDB] PG CRUD cycle complete\");        \/\/ \u0422\u043e\u0442 \u0436\u0435 \u0448\u0430\u0431\u043b\u043e\u043d, \u043d\u043e \u043d\u0430 MSSql-\u0438\u043d\u0441\u0442\u0430\u043d\u0441\u0435 \u2014 provider \u0430\u0431\u0441\u0442\u0440\u0430\u0433\u0438\u0440\u043e\u0432\u0430\u043d        From(\"timer:\/\/named-redb-mssql?period=30000&amp;delay=8000\")            .RouteId(\"demo-named-redb-mssql\")            .ProcessWithRedb(\"mssql-test\", async (redb, ex, ct) =&gt; { \/* save \/ load \/ query \/ delete *\/ });    }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0427\u0442\u043e \u0442\u0443\u0442 \u0432\u0438\u0434\u043d\u043e:<\/p>\n<ul>\n<li>\n<p><code><strong>ProcessWithRedb(\"name\", ...)<\/strong><\/code>\u00a0\u2014 \u044d\u0442\u043e extension \u0432 \u043f\u0430\u043a\u0435\u0442\u0435\u00a0<code>redb.Route.RedbCore<\/code>. \u041f\u043e \u0438\u043c\u0435\u043d\u0438 \u0434\u043e\u0441\u0442\u0430\u0451\u0442 \u043d\u0443\u0436\u043d\u044b\u0439\u00a0<code>IRedbService<\/code>\u00a0\u0438\u0437 \u0440\u0435\u0435\u0441\u0442\u0440\u0430 (<code>Redb<\/code>\u00a0\u0441\u0435\u043a\u0446\u0438\u044f \u0432 \u043a\u043e\u043d\u0444\u0438\u0433\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430), \u0441\u043a\u043e\u0443\u043f\u0438\u0442 \u0435\u0433\u043e \u043d\u0430 \u0432\u0440\u0435\u043c\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 exchange&#8217;\u0430 \u0438 \u043e\u0442\u0434\u0430\u0451\u0442 \u0432 \u043b\u044f\u043c\u0431\u0434\u0443. \u041d\u0438\u043a\u0430\u043a\u043e\u0433\u043e\u00a0<code>IServiceProvider.GetRequiredService&lt;IRedbService&gt;()<\/code>\u00a0\u0432 \u043a\u043e\u0434\u0435 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430.<\/p>\n<\/li>\n<li>\n<p><code><strong>SaveAsync<\/strong><\/code><strong>\u00a0\/\u00a0<\/strong><code><strong>LoadAsync<\/strong><\/code><strong>\u00a0\/\u00a0<\/strong><code><strong>Query&lt;T&gt;().Where(...).ToListAsync()<\/strong><\/code><strong>\u00a0\/\u00a0<\/strong><code><strong>DeleteAsync<\/strong><\/code>\u00a0\u2014 \u044d\u0442\u043e \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439\u00a0<code>IRedbService<\/code>\u00a0API, \u0442\u043e\u0442 \u0436\u0435 \u0447\u0442\u043e \u0438 \u0432 \u043e\u0431\u044b\u0447\u043d\u043e\u043c <a href=\"http:\/\/ASP.NET\" rel=\"noopener noreferrer nofollow\">ASP.NET<\/a>-\u0441\u0435\u0440\u0432\u0438\u0441\u0435. \u041c\u0430\u0440\u0448\u0440\u0443\u0442 \u043d\u0435 \u0434\u0435\u043b\u0430\u0435\u0442 \u043d\u0438\u0447\u0435\u0433\u043e \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e\u0433\u043e \u2014 \u043e\u043d \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u0443\u0436\u0435 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0441\u0435\u0440\u0432\u0438\u0441.<\/p>\n<\/li>\n<li>\n<p><strong>\u041d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0438\u043c\u0435\u043d\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u0438\u043d\u0441\u0442\u0430\u043d\u0441\u043e\u0432 \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e<\/strong>\u00a0\u2014\u00a0<code>pg-test<\/code>\u00a0\u0438\u00a0<code>mssql-test<\/code>\u00a0\u0436\u0438\u0432\u0443\u0442 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e, \u043c\u043e\u0436\u043d\u043e \u0438\u0437 \u043e\u0434\u043d\u043e\u0433\u043e \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430 \u043f\u0438\u0441\u0430\u0442\u044c \u0432 Postgres \u0438 \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e \u0437\u0435\u0440\u043a\u0430\u043b\u0438\u0442\u044c \u0432 MSSql.<\/p>\n<\/li>\n<li>\n<p><strong>\u0421\u0445\u0435\u043c\u0430 (<\/strong><code><strong>[RedbScheme]<\/strong><\/code><strong>-\u043a\u043b\u0430\u0441\u0441\u044b\u00a0<\/strong><code><strong>DemoItemProps<\/strong><\/code><strong>)<\/strong>\u00a0\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u043e\u0434\u0438\u043d \u0440\u0430\u0437 \u043f\u0440\u0438 \u0441\u0442\u0430\u0440\u0442\u0435 \u043c\u043e\u0434\u0443\u043b\u044f (<code>InitRoute.cs<\/code>\u00a0\u0434\u0451\u0440\u0433\u0430\u0435\u0442\u00a0<code>redb.InitializeAsync(ensureCreated: true)<\/code>), \u0434\u0430\u043b\u044c\u0448\u0435 \u043c\u0430\u0440\u0448\u0440\u0443\u0442 \u043f\u0440\u043e\u0441\u0442\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0441 \u0442\u0438\u043f\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u043c\u0438\u00a0<code>RedbObject&lt;TProps&gt;<\/code>.<\/p>\n<\/li>\n<\/ul>\n<p>\u0422\u043e \u0435\u0441\u0442\u044c \u0432 \u043f\u0440\u043e\u0434\u0435 \u0438\u0437 \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u0431\u043b\u043e\u043a\u0430 (<code>tsum<\/code>\u00a0\u2014 SQL polling \u0438\u0437 SAP \u2192\u00a0<code>redb.Query&lt;TransportationRoute&gt;()<\/code>\u00a0\u2192 \u0430\u043f\u0434\u0435\u0439\u0442) \u043b\u0435\u0436\u0438\u0442 \u0440\u043e\u0432\u043d\u043e \u0442\u0430 \u0436\u0435 \u0441\u0432\u044f\u0437\u043a\u0430, \u0447\u0442\u043e \u0438 \u0437\u0434\u0435\u0441\u044c, \u0432 Demo \u2014 \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0431\u0451\u0440\u043d\u0443\u0442\u0430\u044f \u0432 \u0440\u0435\u0430\u043b\u044c\u043d\u0443\u044e \u0431\u0438\u0437\u043d\u0435\u0441-\u043b\u043e\u0433\u0438\u043a\u0443. \u0425\u043e\u0442\u0438\u0442\u0435 \u043f\u043e\u0442\u0440\u043e\u0433\u0430\u0442\u044c \u0431\u0435\u0437 \u0447\u0443\u0436\u043e\u0433\u043e \u043f\u0440\u043e\u0434\u0430 \u2014\u00a0<code>git clone<\/code>, \u043f\u043e\u0434\u043d\u0438\u043c\u0438\u0442\u0435\u00a0<code>docker compose<\/code>,\u00a0<code>dotnet run --project redb.Route.Demo<\/code>, \u0438 \u043e\u043d\u043e \u043e\u0442\u0432\u0435\u0442\u0438\u0442 \u043d\u0430\u00a0<code>POST <\/code><a href=\"http:\/\/localhost:5088\/api\/demo\" rel=\"noopener noreferrer nofollow\"><code>http:\/\/localhost:5088\/api\/demo<\/code><\/a>\u00a0JSON-\u043e\u043c \u0441\u043e \u0432\u0441\u0435\u043c\u0438 \u0448\u0442\u0430\u043c\u043f\u0430\u043c\u0438 \u043e\u0442 \u0432\u0441\u0435\u0445 \u0431\u0440\u043e\u043a\u0435\u0440\u043e\u0432.<\/p>\n<hr\/>\n<h4>\u00ab\u042f \u043b\u044e\u0431\u043b\u044e \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u044b \u0438\u0437 ASP.NET\u00bb \u2014 \u043f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430,\u00a0redb.Route.Controllers<\/h4>\n<p>\u041e\u0442\u0434\u0435\u043b\u044c\u043d\u0430\u044f \u043a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u044f \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0435\u0432, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043f\u043e\u0434 \u0442\u0430\u043a\u0438\u043c\u0438 \u0441\u0442\u0430\u0442\u044c\u044f\u043c\u0438 \u0441\u0442\u0430\u0431\u0438\u043b\u044c\u043d\u043e \u043f\u043e\u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f: \u00ab\u0437\u0430\u0447\u0435\u043c \u043c\u043d\u0435 \u0432\u0430\u0448 fluent-DSL, \u044f \u0445\u043e\u0447\u0443\u00a0<code>[HttpGet(\"{id}\")]<\/code>\u00a0\u0438\u00a0<code>[FromBody] Order order<\/code>, \u043a\u0430\u043a \u043f\u0440\u0438\u0432\u044b\u043a \u0432 <a href=\"http:\/\/ASP.NET\" rel=\"noopener noreferrer nofollow\">ASP.NET<\/a>, \u0438 \u0447\u0442\u043e\u0431\u044b IDE \u043f\u043e\u0434\u0447\u0451\u0440\u043a\u0438\u0432\u0430\u043b\u0430 \u043d\u0435\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u0440\u043e\u0443\u0442\u044b\u00bb. \u0414\u043b\u044f \u043d\u0438\u0445 \u0435\u0441\u0442\u044c \u043f\u0430\u043a\u0435\u0442\u00a0<a href=\"https:\/\/www.nuget.org\/packages\/redb.Route.Controllers\/\" rel=\"noopener noreferrer nofollow\"><code>redb.Route.Controllers<\/code><\/a>\u00a0\u2014 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u044b \u0441 \u0442\u0435\u043c\u0438 \u0436\u0435 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u0430\u043c\u0438, \u0447\u0442\u043e \u0438 \u0432 <a href=\"http:\/\/ASP.NET\" rel=\"noopener noreferrer nofollow\">ASP.NET<\/a>, \u043d\u043e\u00a0<strong>transport-agnostic<\/strong>: \u043e\u0434\u0438\u043d \u0438 \u0442\u043e\u0442 \u0436\u0435 \u043a\u043b\u0430\u0441\u0441 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0437\u0430 HTTP, SignalR, gRPC \u0438\u043b\u0438 \u043b\u044e\u0431\u044b\u043c \u0434\u0440\u0443\u0433\u0438\u043c InOut-\u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u043e\u043c.<\/p>\n<pre><code>[Route(\"orders\")]public class OrdersController : RedbController{    [HttpGet(\"{id}\")]    public Task&lt;Order&gt; GetOrder(int id, [FromHeader(\"X-Tenant\")] string tenant)    {        var redb = Context.GetRedbService();              \/\/ \u0442\u043e\u0442 \u0436\u0435 IRedbService        return redb.Query&lt;OrderProps&gt;()            .Where(o =&gt; o.Id == id &amp;&amp; o.Tenant == tenant)            .FirstAsync();    }    [HttpPost]    public async Task&lt;long&gt; CreateOrder([FromBody] Order order, CancellationToken ct)    {        var redb = Context.GetRedbService();        return await redb.SaveAsync(new RedbObject&lt;OrderProps&gt; { Props = order.ToProps() });    }    [HttpDelete(\"{id}\")]    public Task DeleteOrder([FromRoute(\"id\")] int id) =&gt;        Context.GetRedbService().DeleteAsync(id);}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u0440\u0438\u0432\u044f\u0437\u043a\u0430 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430 \u043a \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u0443 \u2014 \u043e\u0434\u043d\u0430 \u0441\u0442\u0440\u043e\u0447\u043a\u0430:<\/p>\n<pre><code>\/\/ HTTP \u2014 \u0447\u0438\u0442\u0430\u0435\u0442 redbHttp.Method \/ redbHttp.Path \u0438\u0437 \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u0430From(\"http:\/\/0.0.0.0:8080\/api\")    .RedbHttpController&lt;OrdersController&gt;();\/\/ SignalR \u2014 \u0442\u043e\u0442 \u0436\u0435 \u043a\u043b\u0430\u0441\u0441, dispatch \u043f\u043e redbSignalR.MethodFrom(\"signalr:\/\/bridge\")    .RedbSignalRController&lt;OrdersController&gt;();\/\/ gRPC \u2014 dispatch \u043f\u043e dispatch-method \u0438\u0437 metadataFrom(\"grpc:\/\/0.0.0.0:5000\")    .RedbGrpcController&lt;OrdersController&gt;();\/\/ \u0418\u043b\u0438 \u0441\u0440\u0430\u0437\u0443 \u043f\u0430\u0447\u043a\u043e\u0439 \u043f\u043e \u0441\u0431\u043e\u0440\u043a\u0435 \u2014 \u043a\u0430\u043a MapControllers() \u0432 ASP.NETvar registry = new ControllerRegistry();registry.RegisterAssembly(typeof(OrdersController).Assembly);From(\"http:\/\/0.0.0.0:8080\/api\").RedbHttpController(registry);<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0427\u0442\u043e \u0442\u0443\u0442 \u0432\u0430\u0436\u043d\u043e \u043f\u043e\u043d\u0438\u043c\u0430\u0442\u044c:<\/p>\n<ul>\n<li>\n<p><code>[Route]<\/code>,\u00a0<code>[HttpGet\/Post\/Put\/Delete\/Patch]<\/code>,\u00a0<code>[FromBody]<\/code>,\u00a0<code>[FromHeader]<\/code>,\u00a0<code>[FromQuery]<\/code>,\u00a0<code>[FromRoute]<\/code>\u00a0\u2014\u00a0<strong>\u0442\u0435 \u0436\u0435 \u0441\u0430\u043c\u044b\u0435 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u044b, \u0447\u0442\u043e \u0432\u044b \u0437\u043d\u0430\u0435\u0442\u0435 \u0438\u0437 <\/strong><a href=\"http:\/\/ASP.NET\" rel=\"noopener noreferrer nofollow\"><strong>ASP.NET<\/strong><\/a>. \u041a\u0440\u0438\u0432\u0430\u044f \u043e\u0431\u0443\u0447\u0435\u043d\u0438\u044f \u043d\u0443\u043b\u0435\u0432\u0430\u044f.<\/p>\n<\/li>\n<li>\n<p>\u041a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440 \u043d\u0430\u0441\u043b\u0435\u0434\u0443\u0435\u0442\u0441\u044f \u043e\u0442\u00a0<code>RedbController<\/code>\u00a0\u0438 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u00a0<code>Context<\/code>\u00a0(\u0442\u043e\u0442 \u0441\u0430\u043c\u044b\u0439\u00a0<code>IRouteContext<\/code>) \u0438\u00a0<code>Exchange<\/code>\u00a0\u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e \u2014 \u043d\u0438\u043a\u0430\u043a\u043e\u0433\u043e\u00a0<code>IServiceProvider<\/code>-\u0438\u043d\u044a\u0435\u043a\u0446\u0438\u0438 \u0432 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440 \u043d\u0435 \u043d\u0443\u0436\u043d\u043e. \u0425\u043e\u0442\u0438\u0442\u0435 DI \u2014 \u0431\u0435\u0440\u0451\u0442\u0435 \u0441\u0435\u0440\u0432\u0438\u0441 \u0447\u0435\u0440\u0435\u0437\u00a0<code>Context.GetService&lt;T&gt;()<\/code>.<\/p>\n<\/li>\n<li>\n<p><strong>\u041e\u0434\u0438\u043d \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440 \u2014 \u0442\u0440\u0438 \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u0430.<\/strong>\u00a0\u0422\u043e\u0442 \u0436\u0435\u00a0<code>OrdersController<\/code>\u00a0\u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u043d\u0430\u00a0<code>GET \/orders\/42<\/code>\u00a0\u0447\u0435\u0440\u0435\u0437 HTTP, \u043d\u0430\u00a0<code>Send(\"GetOrder\", 42)<\/code>\u00a0\u0447\u0435\u0440\u0435\u0437 SignalR-\u0445\u0430\u0431 \u0438 \u043d\u0430 gRPC-\u0432\u044b\u0437\u043e\u0432 \u0441\u00a0<code>dispatch-method=GetOrder<\/code>. \u042d\u0442\u043e \u0442\u0430 \u0441\u0430\u043c\u0430\u044f \u00abadapter on top of transport\u00bb-\u0438\u0434\u0435\u044f, \u0440\u0430\u0434\u0438 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043e\u0431\u044b\u0447\u043d\u043e \u043f\u0438\u0448\u0443\u0442\u00a0<a href=\"https:\/\/github.com\/jbogard\/MediatR\" rel=\"noopener noreferrer nofollow\">Mediator<\/a>-\u043e\u0431\u0432\u044f\u0437\u043a\u0438 \u0440\u0443\u043a\u0430\u043c\u0438.<\/p>\n<\/li>\n<li>\n<p>\u042d\u0442\u043e\u00a0<strong>\u043d\u0435 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 pipeline-\u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442<\/strong>, \u044d\u0442\u043e\u00a0<code>IProcessor<\/code>\u00a0\u0432\u043d\u0443\u0442\u0440\u0438 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430. \u0414\u043e \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u0430 \u043c\u043e\u0436\u043d\u043e \u0432\u043b\u0435\u043f\u0438\u0442\u044c\u00a0<code>IdempotentConsumer<\/code>,\u00a0<code>Throttle<\/code>,\u00a0<code>OnException&lt;T&gt;<\/code>,\u00a0<code>WireTap<\/code>\u00a0\u0432 audit \u2014 \u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u043e\u0442\u043e\u043c \u043e\u0442\u0434\u0430\u0442\u044c \u0432 controller dispatcher. \u041d\u0438\u043a\u0430\u043a\u043e\u0433\u043e \u00ab\u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440 vs route\u00bb \u2014 \u044d\u0442\u043e\u00a0<strong>\u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440 \u0412\u041d\u0423\u0422\u0420\u0418 route<\/strong>:<\/p>\n<\/li>\n<\/ul>\n<pre><code>From(\"http:\/\/0.0.0.0:8080\/api\")    .Throttle(500).Per(TimeSpan.FromSeconds(1))    .IdempotentConsumer(Header(\"Idempotency-Key\"), repository: \"redb\")    .WireTap(\"seda:\/\/audit\")    .RedbHttpController&lt;OrdersController&gt;();<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u043e\u0434 \u043a\u0430\u043f\u043e\u0442\u043e\u043c \u2014\u00a0<code>ControllerRegistry<\/code>\u00a0\u0441\u043a\u0430\u043d\u0438\u0440\u0443\u0435\u0442 \u0441\u0431\u043e\u0440\u043a\u0443, \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u0442 route table \u0438\u0437 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u043e\u0432, \u043d\u0430 \u043a\u0430\u0436\u0434\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441 dispatcher \u0447\u0438\u0442\u0430\u0435\u0442 headers, \u043c\u0430\u0442\u0447\u0438\u0442 template, \u0431\u0438\u043d\u0434\u0438\u0442 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b (<code>{id}<\/code>\u00a0\u2192\u00a0<code>[FromRoute]<\/code>, query \u2192\u00a0<code>[FromQuery]<\/code>, \u0442\u0435\u043b\u043e \u2192\u00a0<code>[FromBody]<\/code>, \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u043e\u0435 \u043f\u043e \u043a\u043e\u043d\u0432\u0435\u043d\u0446\u0438\u0438 \u0438\u043c\u0435\u043d\u0438). \u041e\u0442\u0432\u0435\u0442 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442\u0441\u044f \u0432\u00a0<a href=\"http:\/\/exchange.In\" rel=\"noopener noreferrer nofollow\"><code>exchange.In<\/code><\/a><code>.Body<\/code>, \u0441\u0442\u0430\u0442\u0443\u0441 \u2014 \u0432\u00a0<code>status.code<\/code>\u00a0(200 \u0434\u043b\u044f \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f, 204 \u0434\u043b\u044f\u00a0<code>Task<\/code>, 404 \u0434\u043b\u044f no-match, 500 \u0434\u043b\u044f \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f). \u041f\u043e\u043b\u043d\u0430\u044f \u0442\u0430\u0431\u043b\u0438\u0446\u0430 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u043e\u0432 \u0438 conventions \u2014 \u0432\u00a0<a href=\"https:\/\/github.com\/redbase-app\/redb-route\/tree\/main\/src\/redb.Route.Controllers\" rel=\"noopener noreferrer nofollow\"><code>redb.Route.Controllers\/<\/code><\/a><a href=\"http:\/\/README.md\" rel=\"noopener noreferrer nofollow\"><code>README.md<\/code><\/a>.<\/p>\n<p>\u0422\u043e \u0435\u0441\u0442\u044c \u044d\u0442\u043e \u043a\u043e\u043c\u043f\u0440\u043e\u043c\u0438\u0441\u0441 \u0440\u043e\u0432\u043d\u043e \u0434\u043b\u044f \u0442\u043e\u0433\u043e \u043b\u0430\u0433\u0435\u0440\u044f, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0438 \u0432 2026-\u043c \u043d\u0435 \u0433\u043e\u0442\u043e\u0432 \u043e\u0442\u043a\u0430\u0437\u0430\u0442\u044c\u0441\u044f \u043e\u0442\u00a0<code>[HttpGet(\"{id}\")]<\/code>: \u0432\u043d\u0435\u0448\u043d\u0435 \u2014 \u043f\u0440\u0438\u0432\u044b\u0447\u043d\u044b\u0439 <a href=\"http:\/\/ASP.NET\" rel=\"noopener noreferrer nofollow\">ASP.NET<\/a>-style controller, \u0432\u043d\u0443\u0442\u0440\u0438 \u2014 \u043e\u0431\u044b\u0447\u043d\u044b\u0439 Route-pipeline \u0441\u043e \u0432\u0441\u0435\u043c\u0438 EIP-\u043f\u043b\u044e\u0448\u043a\u0430\u043c\u0438 \u0438 \u0431\u0435\u0437 \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0438 \u043a Kestrel. \u0415\u0441\u043b\u0438 \u0432\u0430\u0448 \u00abHTTP API\u00bb \u0437\u0430\u0432\u0442\u0440\u0430 \u0434\u043e\u043b\u0436\u0435\u043d \u0437\u0430\u043e\u0434\u043d\u043e \u0441\u043b\u0443\u0448\u0430\u0442\u044c SignalR \u0438 gRPC \u2014 \u043f\u0435\u0440\u0435\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043d\u0430\u0434\u043e, \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u0435\u0449\u0451 \u0434\u0432\u0430\u00a0<code>From(...).Redb*Controller&lt;OrdersController&gt;()<\/code>.<\/p>\n<hr\/>\n<h4>redb.Tsak \u2014 \u0433\u0434\u0435 \u044d\u0442\u043e \u0445\u043e\u0441\u0442\u0438\u0442\u044c \u0432 \u043f\u0440\u043e\u0434\u0435<\/h4>\n<p>redb.Route \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442\u00a0<em>\u0447\u0442\u043e<\/em>\u00a0\u0434\u0435\u043b\u0430\u0435\u0442 \u043c\u0430\u0440\u0448\u0440\u0443\u0442. \u041a\u043e\u0433\u0434\u0430 \u0443 \u0432\u0430\u0441 \u043e\u0434\u0438\u043d \u043f\u0440\u043e\u0435\u043a\u0442 \u2014 \u043e\u043d \u0436\u0438\u0432\u0451\u0442 \u0432\u043d\u0443\u0442\u0440\u0438\u00a0<code>dotnet run<\/code>. \u041a\u043e\u0433\u0434\u0430 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043e\u0432 30 \u0438 \u043e\u043d\u0438 \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0430\u0442 \u0440\u0430\u0437\u043d\u044b\u043c \u043a\u043e\u043c\u0430\u043d\u0434\u0430\u043c \u2014 \u043d\u0443\u0436\u0435\u043d runtime-\u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440.<\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/197\/6ad\/e24\/1976ade2445d97633670597dffebe04b.png\" alt=\"tsak\" title=\"tsak\" width=\"1606\" height=\"791\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/197\/6ad\/e24\/1976ade2445d97633670597dffebe04b.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/197\/6ad\/e24\/1976ade2445d97633670597dffebe04b.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>tsak<\/figcaption><\/div>\n<\/figure>\n<p><strong>redb.Tsak<\/strong>\u00a0\u2014 \u044d\u0442\u043e runtime container \u0434\u043b\u044f \u043c\u043e\u0434\u0443\u043b\u0435\u0439 redb.Route. \u041a\u0430\u0436\u0434\u044b\u0439 \u043c\u043e\u0434\u0443\u043b\u044c (<code>.dll<\/code>\u00a0\u0438\u043b\u0438\u00a0<code>.tpkg<\/code>-\u0431\u0430\u043d\u0434\u043b) \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442\u0441\u044f \u0432 \u0438\u0437\u043e\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439\u00a0<code>AssemblyLoadContext<\/code>, \u0438\u043c\u0435\u0435\u0442 \u0441\u0432\u043e\u0439\u00a0<code>IRouteContext<\/code>, \u0441\u0432\u043e\u0439 DI, \u0441\u0432\u043e\u0438 \u043a\u043e\u043d\u0444\u0438\u0433\u0438. Tsak \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 lifecycle&#8217;\u043e\u043c \u043d\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e: \u0434\u043e\u0431\u0430\u0432\u0438\u043b \u043c\u043e\u0434\u0443\u043b\u044c \u2014 \u043e\u043d \u0441\u0442\u0430\u0440\u0442\u0430\u043d\u0443\u043b, \u0443\u0431\u0440\u0430\u043b \u2014 \u0432\u044b\u0433\u0440\u0443\u0437\u0438\u043b\u0441\u044f, \u043e\u0431\u043d\u043e\u0432\u0438\u043b \u2014 hot-reload \u0431\u0435\u0437 \u0440\u0435\u0441\u0442\u0430\u0440\u0442\u0430 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430.<\/p>\n<p>\u0427\u0442\u043e \u0435\u0441\u0442\u044c \u0438\u0437 \u043a\u043e\u0440\u043e\u0431\u043a\u0438:<\/p>\n<ul>\n<li>\n<p><strong>REST API<\/strong>\u00a0\u2014 32 endpoint&#8217;\u0430: \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430\u043c\u0438, \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430\u043c\u0438, \u043c\u043e\u0434\u0443\u043b\u044f\u043c\u0438, \u043a\u043b\u0430\u0441\u0442\u0435\u0440\u043e\u043c, scheduler&#8217;\u043e\u043c, \u043b\u043e\u0433\u0430\u043c\u0438, \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\u043c\u0438<\/p>\n<\/li>\n<li>\n<p><strong>CLI<\/strong>\u00a0\u2014 30 \u043a\u043e\u043c\u0430\u043d\u0434:\u00a0<code>tsak module upload<\/code>,\u00a0<code>tsak context start<\/code>,\u00a0<code>tsak route stop<\/code>,\u00a0<code>tsak context list<\/code>\u00a0\u2014 \u0443\u0434\u043e\u0431\u043d\u043e \u0434\u043b\u044f CI\/CD<\/p>\n<\/li>\n<li>\n<p><strong>Blazor Server dashboard<\/strong>\u00a0\u2014 10 \u0441\u0442\u0440\u0430\u043d\u0438\u0446: CPU\/RAM\/GC, per-route latency, ring-buffer \u043b\u043e\u0433\u0438, \u0441\u0442\u0430\u0442\u0443\u0441 watchdog<\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/86c\/076\/8e3\/86c0768e31f0ecda7fa8a6b0bfe1e155.jpg\" alt=\"endpoints\" title=\"endpoints\" width=\"1280\" height=\"961\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/86c\/076\/8e3\/86c0768e31f0ecda7fa8a6b0bfe1e155.jpg 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/86c\/076\/8e3\/86c0768e31f0ecda7fa8a6b0bfe1e155.jpg 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>endpoints<\/figcaption><\/div>\n<\/figure>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/600\/491\/df1\/600491df18375433d1ccb5186c95ba38.jpg\" alt=\"monitoring\" title=\"monitoring\" width=\"1280\" height=\"940\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/600\/491\/df1\/600491df18375433d1ccb5186c95ba38.jpg 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/600\/491\/df1\/600491df18375433d1ccb5186c95ba38.jpg 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>monitoring<\/figcaption><\/div>\n<\/figure>\n<\/li>\n<li>\n<p><strong>Hot-reload<\/strong>\u00a0\u2014 \u043a\u043b\u0430\u0434\u0451\u0448\u044c \u043e\u0431\u043d\u043e\u0432\u043b\u0451\u043d\u043d\u0443\u044e\u00a0<code>.dll<\/code>\u00a0\u0432\u00a0<code>Libs\/<\/code>, Tsak \u043f\u043e\u0434\u0445\u0432\u0430\u0442\u044b\u0432\u0430\u0435\u0442, \u0441\u0442\u0430\u0440\u0430\u044f \u0432\u0435\u0440\u0441\u0438\u044f \u0432\u044b\u0433\u0440\u0443\u0436\u0430\u0435\u0442\u0441\u044f \u0441 graceful drain<\/p>\n<\/li>\n<li>\n<p><strong>\u041a\u043b\u0430\u0441\u0442\u0435\u0440<\/strong>\u00a0\u2014 leader election + \u0430\u0432\u0442\u043e\u043f\u0435\u0440\u0435\u0440\u0430\u0441\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u043e\u0432 \u043c\u0435\u0436\u0434\u0443 \u043d\u043e\u0434\u0430\u043c\u0438,\u00a0<strong>\u0447\u0435\u0440\u0435\u0437 redb-\u0431\u0430\u0437\u0443, \u0431\u0435\u0437 Redis \/ ZooKeeper \/ Consul<\/strong><\/p>\n<\/li>\n<li>\n<p><strong>Quartz scheduler<\/strong>\u00a0\u2014\u00a0<code>RAMJobStore<\/code>\u00a0\u0434\u043b\u044f standalone,\u00a0<code>AdoJobStore<\/code>\u00a0\u0434\u043b\u044f \u043a\u043b\u0430\u0441\u0442\u0435\u0440\u0430 (\u0441\u0445\u0435\u043c\u0430 \u0441\u043e\u0437\u0434\u0430\u0451\u0442\u0441\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438)<\/p>\n<\/li>\n<li>\n<p><strong>OpenTelemetry<\/strong>\u00a0\u2014 Activities + Meters \u043d\u0430 \u043a\u0430\u0436\u0434\u044b\u0439 \u043c\u0430\u0440\u0448\u0440\u0443\u0442 \u0438 \u0448\u0430\u0433, Prometheus scrape<\/p>\n<\/li>\n<\/ul>\n<p>\u0414\u0435\u043f\u043b\u043e\u0439 \u043d\u043e\u0432\u043e\u0433\u043e \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430:<\/p>\n<pre><code class=\"bash\"># \u0412\u0430\u0440\u0438\u0430\u043d\u0442 1 \u2014 \u0433\u043e\u043b\u0430\u044f DLL. \u0411\u0440\u043e\u0441\u0430\u0435\u043c \u0441\u0431\u043e\u0440\u043a\u0443 \u0432 Libs\/, Tsak \u0443\u0432\u0438\u0434\u0438\u0442, \u043f\u043e\u0434\u043d\u0438\u043c\u0435\u0442# \u043c\u043e\u0434\u0443\u043b\u044c \u0441 graceful drain \u0441\u0442\u0430\u0440\u043e\u0439 \u0432\u0435\u0440\u0441\u0438\u0438. \u041f\u043e\u0434\u0445\u043e\u0434\u0438\u0442 \u0434\u043b\u044f \u0434\u0435\u0432-\u0446\u0438\u043a\u043b\u0430.cp Orders.dll \/tsak\/worker\/Libs\/# \u0412\u0430\u0440\u0438\u0430\u043d\u0442 2 \u2014 .tpkg-\u0431\u0430\u043d\u0434\u043b (ZIP \u0441 manifest.json + DLL + &lt;Module&gt;.config.json).# \u0421\u043e\u0431\u0438\u0440\u0430\u0435\u0442\u0441\u044f \u043f\u0440\u044f\u043c\u043e \u0438\u0437 .csproj \u2014 \u0443 redb.Route.Demo \u0435\u0441\u0442\u044c Target \"PackTpkg\"# (AfterTargets=\"Build\"), \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043a\u043b\u0430\u0434\u0451\u0442 .tpkg \u0440\u044f\u0434\u043e\u043c \u0438 \u043a\u043e\u043f\u0438\u0440\u0443\u0435\u0442 \u0432 Tsak Libs.# \u0422\u0430\u043a \u0435\u0434\u0435\u0442 \u0432 \u043f\u0440\u043e\u0434: \u043e\u0434\u0438\u043d \u0444\u0430\u0439\u043b, \u0432\u0435\u0440\u0441\u0438\u043e\u043d\u0438\u0440\u0443\u0435\u043c\u044b\u0439, \u0441 \u043a\u043e\u043d\u0444\u0438\u0433\u043e\u043c \u0438 \u043c\u0430\u043d\u0438\u0444\u0435\u0441\u0442\u043e\u043c \u0432\u043d\u0443\u0442\u0440\u0438.cp Orders.tpkg \/tsak\/worker\/Libs\/# \u0438\u043b\u0438 \u0447\u0435\u0440\u0435\u0437 CLI \u043f\u043e\u0432\u0435\u0440\u0445 REST APItsak module upload orders --file Orders.tpkgtsak context start orders# \u041e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u043e\u0434\u0438\u043d \u043c\u0430\u0440\u0448\u0440\u0443\u0442 \u0431\u0435\u0437 \u0440\u0435\u0441\u0442\u0430\u0440\u0442\u0430 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430tsak route stop orders order-pipeline# \u0427\u0442\u043e \u0441\u0435\u0439\u0447\u0430\u0441 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442tsak context listtsak route list orders<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u0430\u043f\u043a\u0430\u00a0<code>Libs\/<\/code>\u00a0\u2014 \u044d\u0442\u043e \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u0430\u044f convention-\u0442\u043e\u0447\u043a\u0430 \u043c\u043e\u0434\u0443\u043b\u0435\u0439. \u0412 \u0438\u0441\u0445\u043e\u0434\u043d\u0438\u043a\u0430\u0445 \u043e\u043d\u0430 \u043b\u0435\u0436\u0438\u0442 \u0440\u043e\u0432\u043d\u043e \u043f\u043e \u0442\u0430\u043a\u0438\u043c \u0436\u0435 \u043f\u0443\u0442\u044f\u043c:\u00a0<code>redb.Tsak\/src\/redb.Tsak.Worker\/Libs\/<\/code>\u00a0\u0434\u043b\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0438\u0437 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u044f,\u00a0<code>worker\/Libs\/<\/code>\u00a0\u0432\u00a0<a href=\"https:\/\/github.com\/redbase-app\/redb-tsak\/releases\" rel=\"noopener noreferrer nofollow\">\u0433\u043e\u0442\u043e\u0432\u044b\u0445 \u0440\u0435\u043b\u0438\u0437\u043d\u044b\u0445 \u0431\u0438\u043d\u0430\u0440\u043d\u0438\u043a\u0430\u0445<\/a>. \u0412\u00a0<a href=\"https:\/\/github.com\/redbase-app\/redb-tsak\/pkgs\/container\/redb-tsak-stack\" rel=\"noopener noreferrer nofollow\">Docker-\u043e\u0431\u0440\u0430\u0437\u0435<\/a>\u00a0\u2014\u00a0<code>\/app\/worker\/Libs\/<\/code>, \u043c\u043e\u043d\u0442\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u043a\u0430\u043a volume; \u0434\u043e\u0431\u0430\u0432\u0438\u043b\u0438\u00a0<code>.dll<\/code>\u00a0\u0438\u043b\u0438\u00a0<code>.tpkg<\/code>\u00a0\u0441\u043d\u0430\u0440\u0443\u0436\u0438 \u2014 \u0432\u043d\u0443\u0442\u0440\u0438 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430 Tsak \u043f\u043e\u0434\u043d\u044f\u043b \u043c\u043e\u0434\u0443\u043b\u044c \u0431\u0435\u0437 \u0440\u0435\u0441\u0442\u0430\u0440\u0442\u0430.<\/p>\n<p>\u041c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0439 \u043f\u0440\u0438\u043c\u0435\u0440 MSBuild-\u0442\u0430\u0440\u0433\u0435\u0442\u0430 \u0434\u043b\u044f \u0443\u043f\u0430\u043a\u043e\u0432\u043a\u0438\u00a0<code>.tpkg<\/code>\u00a0\u043f\u0440\u044f\u043c\u043e \u0438\u0437 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u043c\u043e\u0434\u0443\u043b\u044f \u2014 \u043c\u0430\u043d\u0438\u0444\u0435\u0441\u0442 + DLL + \u043a\u043e\u043d\u0444\u0438\u0433 \u0432 \u043e\u0434\u0438\u043d ZIP, \u0438 \u0441\u0440\u0430\u0437\u0443 \u043a\u043e\u043f\u0438\u044f \u0432\u00a0<code>Libs\/<\/code>\u00a0Tsak&#8217;\u0430 (\u043a\u0430\u043a \u0441\u0434\u0435\u043b\u0430\u043d\u043e \u0432\u00a0<a href=\"https:\/\/github.com\/redbase-app\/redb-route\/blob\/main\/redb.Route.Demo\/redb.Route.Demo.csproj\" rel=\"noopener noreferrer nofollow\"><code>redb.Route.Demo.csproj<\/code><\/a>):<\/p>\n<pre><code class=\"xml\">&lt;PropertyGroup&gt;  &lt;TsakModuleName&gt;Orders&lt;\/TsakModuleName&gt;  &lt;TsakLibsDir&gt;$(MSBuildThisFileDirectory)..\\..\\tsak\\worker\\Libs&lt;\/TsakLibsDir&gt;&lt;\/PropertyGroup&gt;&lt;Target Name=\"PackTpkg\" AfterTargets=\"Build\"&gt;  &lt;PropertyGroup&gt;    &lt;_Staging&gt;$(IntermediateOutputPath)tpkg&lt;\/_Staging&gt;    &lt;_Tpkg&gt;$(MSBuildThisFileDirectory)output\\$(TsakModuleName).tpkg&lt;\/_Tpkg&gt;  &lt;\/PropertyGroup&gt;  &lt;RemoveDir Directories=\"$(_Staging)\" \/&gt;  &lt;MakeDir   Directories=\"$(_Staging)\" \/&gt;  &lt;Copy SourceFiles=\"$(MSBuildThisFileDirectory)manifest.json\"            DestinationFolder=\"$(_Staging)\" \/&gt;  &lt;Copy SourceFiles=\"$(TargetPath)\"                                        DestinationFolder=\"$(_Staging)\" \/&gt;  &lt;Copy SourceFiles=\"$(MSBuildThisFileDirectory)$(TsakModuleName).config.json\"        DestinationFolder=\"$(_Staging)\"        Condition=\"Exists('$(MSBuildThisFileDirectory)$(TsakModuleName).config.json')\" \/&gt;  &lt;ZipDirectory SourceDirectory=\"$(_Staging)\" DestinationFile=\"$(_Tpkg)\" Overwrite=\"true\" \/&gt;  &lt;Copy SourceFiles=\"$(_Tpkg)\" DestinationFolder=\"$(TsakLibsDir)\" \/&gt;  &lt;Touch Files=\"$(TsakLibsDir)\\$(TsakModuleName).tpkg\" \/&gt;&lt;\/Target&gt;<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><code>dotnet build<\/code>\u00a0\u2192\u00a0<code>Orders.tpkg<\/code>\u00a0\u0432\u00a0<code>Libs\/<\/code>\u00a0\u2192 Tsak \u0435\u0433\u043e \u043f\u043e\u0434\u043d\u044f\u043b. \u0412 CI\/CD \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0441\u044f \u043e\u0434\u043d\u0430 \u0430\u0440\u0442\u0435\u0444\u0430\u043a\u0442-\u0441\u0443\u0449\u043d\u043e\u0441\u0442\u044c \u0441 \u043f\u0440\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u043d\u043e\u0439 \u0432\u0435\u0440\u0441\u0438\u0435\u0439, \u043a\u043e\u043d\u0444\u0438\u0433\u043e\u043c \u0438 \u043c\u0430\u043d\u0438\u0444\u0435\u0441\u0442\u043e\u043c \u2014 \u0434\u0435\u043f\u043b\u043e\u0439 \u0441\u0432\u043e\u0434\u0438\u0442\u0441\u044f \u043a \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u0438 \u0444\u0430\u0439\u043b\u0430.<\/p>\n<p><strong>\u041c\u0430\u0440\u0448\u0440\u0443\u0442\u044b \u0441\u00a0<\/strong><code><strong>cluster=true<\/strong><\/code><strong>\u00a0\u0438\u0434\u0443\u0442 \u0447\u0435\u0440\u0435\u0437 redb-\u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u043e\u0440.<\/strong>\u00a0\u0412 \u043a\u043b\u0430\u0441\u0442\u0435\u0440\u043d\u043e\u043c \u0440\u0435\u0436\u0438\u043c\u0435 \u043c\u0430\u0440\u0448\u0440\u0443\u0442 \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u043c\u0435\u0442\u0438\u0442\u044c \u043a\u0430\u043a \u043a\u043b\u0430\u0441\u0442\u0435\u0440\u043d\u044b\u0439 \u2014 \u0442\u043e\u0433\u0434\u0430 Tsak \u0441\u0430\u043c \u0440\u0430\u0441\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442 \u0435\u0433\u043e \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u044b \u043c\u0435\u0436\u0434\u0443 \u043d\u043e\u0434\u0430\u043c\u0438 \u0438 \u0441\u043b\u0435\u0434\u0438\u0442, \u0447\u0442\u043e \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0443\u0436\u043d\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043a\u043e\u043f\u0438\u0439 \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043e. \u0421\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435, partitioning, \u0431\u0430\u043b\u0430\u043d\u0441\u0438\u0440\u043e\u0432\u043a\u0430 \u2014 \u0447\u0435\u0440\u0435\u0437 redb\u00a0<code><em>objects<\/em><\/code><em>\/<\/em><code>values<\/code>. Quartz \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0441\u0432\u043e\u0438\u00a0<code>AdoJobStore<\/code>-\u0442\u0430\u0431\u043b\u0438\u0446\u044b \u0432 \u0442\u043e\u0439 \u0436\u0435 \u0411\u0414, \u043d\u043e \u044d\u0442\u043e \u0435\u0433\u043e \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0435 \u0442\u0430\u0431\u043b\u0438\u0446\u044b.<\/p>\n<p>\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043d\u043e\u0434\u0443 \u0432 \u043a\u043b\u0430\u0441\u0442\u0435\u0440 \u2014 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0435\u0449\u0451 \u043e\u0434\u0438\u043d \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 Tsak \u0441 \u0442\u043e\u0439 \u0436\u0435 \u0441\u0442\u0440\u043e\u043a\u043e\u0439 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u0411\u0414. \u041d\u0438\u043a\u0430\u043a\u043e\u0433\u043e ZooKeeper \/ Consul \/ Redis \u043a\u0430\u043a \u0432\u043d\u0435\u0448\u043d\u0435\u0439 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438.<\/p>\n<p>\u0422\u043e\u0442 \u0436\u0435 \u0441\u0430\u043c\u044b\u0439\u00a0<code>RouteBuilder<\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u044b \u043d\u0430\u043f\u0438\u0441\u0430\u043b\u0438 \u0434\u043b\u044f \u043e\u0431\u044b\u0447\u043d\u043e\u0433\u043e\u00a0<code>IHostedService<\/code>, \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0432 Tsak \u0431\u0435\u0437 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439 \u2014 \u0442\u043e\u0442 \u0436\u0435\u00a0<code>Configure()<\/code>, \u0442\u043e\u0442 \u0436\u0435\u00a0<code>IExchange<\/code>, \u0442\u0435 \u0436\u0435\u00a0<code>OnException<\/code>\u00a0\u0438\u00a0<code>.Transacted()<\/code>.<\/p>\n<p>351 \u043f\u0440\u043e\u0445\u043e\u0434\u044f\u0449\u0438\u0439 \u0442\u0435\u0441\u0442, Apache 2.0.<\/p>\n<blockquote>\n<p><strong>\u041f\u0440\u043e Tsak \u2014 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0430\u044f \u0431\u043e\u043b\u044c\u0448\u0430\u044f \u0441\u0442\u0430\u0442\u044c\u044f.<\/strong>\u00a0\u0417\u0434\u0435\u0441\u044c \u044d\u0442\u043e \u0443\u043f\u043e\u043c\u044f\u043d\u0443\u0442\u043e \u043a\u0440\u0443\u043f\u043d\u044b\u043c\u0438 \u043c\u0430\u0437\u043a\u0430\u043c\u0438, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e Tsak \u2014 \u044d\u0442\u043e \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u043f\u0440\u043e\u0434\u0443\u043a\u0442 \u0441\u043e \u0441\u0432\u043e\u0435\u0439 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u043e\u0439:\u00a0<code>AssemblyLoadContext<\/code>-\u0438\u0437\u043e\u043b\u044f\u0446\u0438\u044f, 5-\u0441\u043b\u043e\u0439\u043d\u044b\u0439 \u043a\u043e\u043d\u0444\u0438\u0433-pipeline, leader election \u043d\u0430 redb-\u043e\u0431\u044a\u0435\u043a\u0442\u0430\u0445 \u0431\u0435\u0437 \u0432\u043d\u0435\u0448\u043d\u0435\u0433\u043e \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u043e\u0440\u0430, Blazor-\u0434\u0430\u0448\u0431\u043e\u0440\u0434, REST API, CLI, hot-reload c graceful drain. \u0412 \u043e\u0434\u043d\u0443 Route-\u0441\u0442\u0430\u0442\u044c\u044e \u044d\u0442\u043e \u043d\u0435 \u0432\u043b\u0435\u0437\u0430\u0435\u0442. \u0415\u0441\u043b\u0438 \u0437\u0430\u0439\u0434\u0451\u0442 \u2014 \u043d\u0430\u043f\u0438\u0448\u0443 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e.<\/p>\n<\/blockquote>\n<hr\/>\n<h4>\u0413\u0434\u0435 Route \u0436\u0438\u0432\u0451\u0442 \u0432 \u044d\u043a\u043e\u0441\u0438\u0441\u0442\u0435\u043c\u0435 redb<\/h4>\n<p>redb.Route \u2014 \u044d\u0442\u043e \u043d\u0435 \u043e\u0434\u0438\u043d\u043e\u0447\u043d\u044b\u0439 \u043f\u0430\u043a\u0435\u0442, \u0430 \u0441\u043b\u043e\u0439 \u0432 \u0441\u0442\u043e\u043f\u043a\u0435 \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u044b \u043f\u0438\u0448\u0435\u043c \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0435 \u0433\u043e\u0434\u044b \u0438 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0441\u043e\u0431\u0438\u0440\u0430\u044e\u0442\u0441\u044f \u0434\u0440\u0443\u0433 \u0438\u0437 \u0434\u0440\u0443\u0433\u0430. \u041f\u043e \u0443\u0440\u043e\u0432\u043d\u044f\u043c \u0441\u043d\u0438\u0437\u0443 \u0432\u0432\u0435\u0440\u0445:<\/p>\n<ul>\n<li>\n<p><code><strong>redb.Core<\/strong><\/code>\u00a0\u2014 \u044f\u0434\u0440\u043e: \u0442\u0438\u043f\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u0430\u044f \u043e\u0431\u044a\u0435\u043a\u0442\u043d\u0430\u044f \u043c\u043e\u0434\u0435\u043b\u044c (<code>RedbObject&lt;TProps&gt;<\/code>), code-first \u0441\u0445\u0435\u043c\u044b (<code>[RedbScheme]<\/code>), expression-LINQ-\u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440, \u043a\u044d\u0448\u0438, \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f, \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f, security. \u0412\u0441\u0451 \u0430\u0431\u0441\u0442\u0440\u0430\u043a\u0442\u043d\u043e, \u0431\u0435\u0437 \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0438 \u043a \u0421\u0423\u0411\u0414.<\/p>\n<\/li>\n<li>\n<p><a href=\"http:\/\/redb.Core.Pro\" rel=\"noopener noreferrer nofollow\"><code><strong>redb.Core.Pro<\/strong><\/code><\/a>\u00a0\u2014 \u043f\u043b\u0430\u0442\u043d\u0430\u044f \u043d\u0430\u0434\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043d\u0430\u0434 Core: change tracking, materialization, \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u0441\u0445\u0435\u043c, \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u044b\u0435 query\/scheme-\u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u044b.<\/p>\n<\/li>\n<li>\n<p><code><strong>redb.Postgres<\/strong><\/code><strong>\u00a0\/\u00a0<\/strong><code><strong>redb.MSSql<\/strong><\/code>\u00a0\u2014 Free-\u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u044b: \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f\u00a0<code>IRedbContext<\/code>\u00a0\u0438 \u0444\u0430\u0431\u0440\u0438\u0447\u043d\u044b\u0445 \u043c\u0435\u0442\u043e\u0434\u043e\u0432\u00a0<code>RedbServiceBase<\/code>\u00a0\u043f\u043e\u0432\u0435\u0440\u0445 Npgsql \/ <a href=\"http:\/\/Microsoft.Data\" rel=\"noopener noreferrer nofollow\">Microsoft.Data<\/a>.SqlClient.<\/p>\n<\/li>\n<li>\n<p><a href=\"http:\/\/redb.Postgres.Pro\" rel=\"noopener noreferrer nofollow\"><code><strong>redb.Postgres.Pro<\/strong><\/code><\/a><strong>\u00a0\/\u00a0<\/strong><a href=\"http:\/\/redb.MSSql.Pro\" rel=\"noopener noreferrer nofollow\"><code><strong>redb.MSSql.Pro<\/strong><\/code><\/a>\u00a0\u2014 Pro-\u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u044b: \u043f\u043e\u0434\u0442\u044f\u0433\u0438\u0432\u0430\u044e\u0442 <a href=\"http:\/\/Core.Pro\" rel=\"noopener noreferrer nofollow\">Core.Pro<\/a>-\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 (materialization, \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438) \u043d\u0430 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u0443\u044e \u0421\u0423\u0411\u0414.<\/p>\n<\/li>\n<li>\n<p><code><strong>redb.Identity<\/strong><\/code>\u00a0\u2014 \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u044b\u0439 identity-\u0441\u0435\u0440\u0432\u0435\u0440, \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0439 \u043f\u043e\u0432\u0435\u0440\u0445 redb-\u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432: \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438, \u0440\u043e\u043b\u0438, JWT, refresh, LDAP-\u0444\u0435\u0434\u0435\u0440\u0430\u0446\u0438\u044f. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0438 \u0432 Route-\u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430\u0445 (\u0442\u043e\u0442 \u0436\u0435\u00a0<code>Http.Listen().UseJwt(...)<\/code>), \u0438 \u0441\u043d\u0430\u0440\u0443\u0436\u0438.<\/p>\n<\/li>\n<li>\n<p><code><strong>redb.Export<\/strong><\/code>\u00a0\u2014 \u044d\u043a\u0441\u043f\u043e\u0440\u0442\/\u0431\u044d\u043a\u0430\u043f redb-\u0434\u0430\u043d\u043d\u044b\u0445 \u0432 \u0444\u0430\u0439\u043b\u044b. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0432 TsUM-\u043f\u0440\u043e\u0434\u0435 \u043a\u0430\u043a cron-\u043c\u0430\u0440\u0448\u0440\u0443\u0442 (<code>Cron.Every(...).Process(BackupRedbBase)<\/code>).<\/p>\n<\/li>\n<li>\n<p><code><strong>redb.Route<\/strong><\/code>\u00a0\u2014 \u0442\u043e, \u043f\u0440\u043e \u0447\u0442\u043e \u044d\u0442\u0430 \u0441\u0442\u0430\u0442\u044c\u044f. \u0411\u0435\u0440\u0451\u0442\u00a0<code>IRedbService<\/code>\u00a0\u0438\u0437 \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u0430, \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442 EIP-DSL, \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u044b, OpenTelemetry. \u041c\u0430\u0440\u0448\u0440\u0443\u0442\u044b \u2014 \u043e\u0431\u044b\u0447\u043d\u044b\u0435 C#-\u043a\u043b\u0430\u0441\u0441\u044b, schema-aware:\u00a0<code>ProcessWithRedb(\"pg-test\", ctx =&gt; ctx.SaveAsync(obj))<\/code>.<\/p>\n<\/li>\n<li>\n<p><code><strong>redb.Tsak<\/strong><\/code>\u00a0\u2014 runtime-\u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u0434\u043b\u044f Route-\u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043e\u0432: hot-reload, \u043a\u043b\u0430\u0441\u0442\u0435\u0440, \u0434\u0430\u0448\u0431\u043e\u0440\u0434. \u041f\u043e\u0432\u0435\u0440\u0445 Core + \u043e\u0434\u043d\u043e\u0433\u043e \u0438\u0437 \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u043e\u0432 + Route + Identity (\u0434\u043b\u044f \u043b\u043e\u0433\u0438\u043d\u0430 \u0432 Blazor-UI).<\/p>\n<\/li>\n<li>\n<p><code><strong>redb.Doc.Web<\/strong><\/code>\u00a0\u2014 \u0441\u0430\u0439\u0442 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438, \u043d\u0430\u043f\u0438\u0441\u0430\u043d \u043d\u0430 Blazor \u043f\u043e\u0432\u0435\u0440\u0445 \u0442\u043e\u0433\u043e \u0436\u0435 \u0441\u0442\u0435\u043a\u0430: \u043a\u043e\u043d\u0442\u0435\u043d\u0442-\u043e\u0431\u044a\u0435\u043a\u0442\u044b \u043b\u0435\u0436\u0430\u0442 \u043a\u0430\u043a\u00a0<code>RedbObject&lt;DocPageProps&gt;<\/code>\u00a0\u0432 Postgres, \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044f \u2014 \u0447\u0435\u0440\u0435\u0437 Tree-\u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440 Core, \u043f\u043e\u0438\u0441\u043a \u2014 \u0447\u0435\u0440\u0435\u0437 query-\u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440. \u0422\u043e \u0435\u0441\u0442\u044c \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f \u043f\u0440\u043e redb \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442\u00a0<strong>\u043d\u0430 \u0441\u0430\u043c\u043e\u043c redb<\/strong>.<\/p>\n<\/li>\n<\/ul>\n<p>\u0417\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u0447\u0435\u0441\u0442\u043d\u043e \u043e\u0434\u043d\u043e\u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0435: Route \u043d\u0435 \u0437\u043d\u0430\u0435\u0442 \u043f\u0440\u043e Tsak, Tsak \u043d\u0435 \u0437\u043d\u0430\u0435\u0442 \u043f\u0440\u043e Doc.Web, Core \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u0437\u043d\u0430\u0435\u0442 \u043f\u0440\u043e \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u043e\u0432. \u041c\u043e\u0436\u043d\u043e \u0432\u0437\u044f\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e Core + Postgres \u0438 \u043f\u0438\u0441\u0430\u0442\u044c \u0441\u0432\u043e\u0451 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0431\u0435\u0437 Route \u0438 Tsak; \u043c\u043e\u0436\u043d\u043e \u0432\u0437\u044f\u0442\u044c Core + Postgres + Route \u0438 \u0445\u043e\u0441\u0442\u0438\u0442\u044c \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u044b \u0432 \u043e\u0431\u044b\u0447\u043d\u043e\u043c\u00a0<code>IHostedService<\/code>; \u043c\u043e\u0436\u043d\u043e \u0441\u043e\u0431\u0440\u0430\u0442\u044c \u0432\u0441\u044e \u0441\u0442\u043e\u043f\u043a\u0443 \u0446\u0435\u043b\u0438\u043a\u043e\u043c \u0438 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c Tsak + Identity + \u0434\u0430\u0448\u0431\u043e\u0440\u0434 + \u0441\u0430\u0439\u0442.<\/p>\n<p>\u042d\u0442\u0430 \u0441\u0442\u0430\u0442\u044c\u044f \u2014 \u043f\u0440\u043e \u043e\u0434\u0438\u043d \u0441\u043b\u043e\u0439 (Route + Tsak). \u041f\u0440\u043e Core, Identity \u0438 Doc.Web \u0431\u0443\u0434\u0443\u0442 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0435.<\/p>\n<hr\/>\n<h4>\u0427\u0442\u043e redb.Route \u041d\u0415 \u0434\u0435\u043b\u0430\u0435\u0442<\/h4>\n<p>\u0427\u0435\u0441\u0442\u043d\u043e \u2014 \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0438\u043d\u0430\u0447\u0435 \u0447\u0435\u0440\u0435\u0437 \u0434\u0432\u0435 \u043d\u0435\u0434\u0435\u043b\u0438 \u043f\u0440\u0438\u0434\u0443\u0442 \u0441 issue.<\/p>\n<p><strong>Durable saga \u0441 DB-state machine.<\/strong>\u00a0\u042d\u0442\u043e \u043a MassTransit \/ NServiceBus \/ Wolverine. \u0423 redb.Route \u0435\u0441\u0442\u044c\u00a0<code>Saga()<\/code>\u00a0\u0441\u00a0<strong>\u043a\u043e\u043c\u043f\u0435\u043d\u0441\u0438\u0440\u0443\u044e\u0449\u0438\u043c\u0438 \u0448\u0430\u0433\u0430\u043c\u0438<\/strong>: forward + reverse, \u0435\u0441\u043b\u0438 \u0447\u0442\u043e-\u0442\u043e \u0443\u043f\u0430\u043b\u043e \u043f\u043e\u0441\u0435\u0440\u0435\u0434\u0438\u043d\u0435 \u2014 \u043e\u0442\u043a\u0430\u0442\u044b\u0432\u0430\u0435\u043c\u0441\u044f \u0432 \u043e\u0431\u0440\u0430\u0442\u043d\u043e\u043c \u043f\u043e\u0440\u044f\u0434\u043a\u0435. \u041d\u043e \u0441\u0430\u043c\u043e\u0439 state-machine (\u0433\u0434\u0435 \u00ab\u0437\u0430\u043a\u0430\u0437 \u0432 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0438 PaymentPending, \u0436\u0434\u0451\u043c webhook \u043e\u0442 Stripe \u0442\u0440\u0438 \u0434\u043d\u044f, \u043d\u0435 \u0442\u0435\u0440\u044f\u0435\u043c \u043f\u0440\u0438 \u0440\u0435\u0441\u0442\u0430\u0440\u0442\u0435 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430\u00bb) \u2014 \u043d\u0435\u0442. \u041c\u043e\u0436\u043d\u043e \u0441\u043e\u0431\u0440\u0430\u0442\u044c \u0440\u0443\u043a\u0430\u043c\u0438 \u0447\u0435\u0440\u0435\u0437\u00a0<code>IdempotentConsumer<\/code>\u00a0+ persistent backend \u0432 redb, \u043d\u043e \u044d\u0442\u043e \u043d\u0435 managed \u0440\u0435\u0448\u0435\u043d\u0438\u0435.<\/p>\n<p><strong>Managed transactional outbox container.<\/strong>\u00a0\u0412 MassTransit\/Wolverine \u0435\u0441\u0442\u044c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0441\u043e\u0437\u0434\u0430\u0451\u0442 \u0442\u0430\u0431\u043b\u0438\u0446\u0443\u00a0<code>outbox_messages<\/code>, \u043f\u0438\u0448\u0435\u0442 \u0432 \u043d\u0435\u0451 \u0432 \u0442\u043e\u0439 \u0436\u0435 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438, \u0447\u0442\u043e \u0431\u0438\u0437\u043d\u0435\u0441-\u0434\u0430\u043d\u043d\u044b\u0435, \u0430 \u0432 \u0444\u043e\u043d\u0435 \u043f\u0443\u0431\u043b\u0438\u043a\u0443\u0435\u0442. \u0423 Route \u044d\u0442\u043e \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u0442\u0441\u044f \u0438\u0437 \u043f\u0440\u0438\u043c\u0438\u0442\u0438\u0432\u043e\u0432:\u00a0<code>Sql.Poll(...)<\/code>\u00a0+\u00a0<code>IdempotentConsumer<\/code>\u00a0+\u00a0<code>.Transacted()<\/code>. \u0422\u043e \u0435\u0441\u0442\u044c\u00a0<strong>outbox-\u043f\u0430\u0442\u0442\u0435\u0440\u043d \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442<\/strong>, \u043d\u043e \u0430\u0432\u0442\u043e\u043c\u0430\u0442-\u0438\u043d\u0441\u0442\u0430\u043b\u043b\u044f\u0442\u043e\u0440 \u0442\u0430\u0431\u043b\u0438\u0446\u044b \u0438 \u0444\u043e\u043d\u043e\u0432\u044b\u0439 daemon \u2014 \u043d\u0435 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u044b, \u043f\u0438\u0448\u0443\u0442\u0441\u044f \u043a\u0430\u043a \u0447\u0430\u0441\u0442\u044c \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430.<\/p>\n<p><strong>300+ \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432, \u043a\u0430\u043a \u0443 Camel.<\/strong>\u00a022 \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u0430 \u0437\u0430\u043a\u0440\u044b\u0432\u0430\u044e\u0442 \u0431\u043e\u043b\u044c\u0448\u0443\u044e \u0447\u0430\u0441\u0442\u044c \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0445 \u0437\u0430\u0434\u0430\u0447 (Kafka, RabbitMQ, IBM MQ, MQTT, HTTP, gRPC, SQL, SFTP, S3, LDAP, Mail, Quartz), \u043d\u043e \u0435\u0441\u043b\u0438 \u0432\u0430\u043c \u043d\u0443\u0436\u0435\u043d, \u0441\u043a\u0430\u0436\u0435\u043c, Salesforce-bulk-API-\u043a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440 \u0438\u043b\u0438 ServiceNow-REST-\u043a\u043b\u0438\u0435\u043d\u0442 \u2014 \u044d\u0442\u043e \u0432\u0441\u0451 \u0435\u0449\u0451 \u00ab\u043d\u0430\u043f\u0438\u0448\u0438 \u0441\u0432\u043e\u0439 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0447\u0435\u0440\u0435\u0437\u00a0<code>IConsumer<\/code>\/<code>IProducer<\/code>\u00bb, \u0438\u043b\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c generic HTTP\/gRPC \u043f\u043e\u0432\u0435\u0440\u0445 \u0438\u0445 API.<\/p>\n<p><strong>XML-DSL \u0438 \u0430\u0432\u0442\u043e\u0434\u0435\u043f\u043b\u043e\u0439 \u043a\u0430\u043a \u0443 Camel K.<\/strong>\u00a0\u0422\u043e\u043b\u044c\u043a\u043e C#-fluent. \u041d\u0438\u043a\u0430\u043a\u0438\u0445 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043e\u0432 \u0438\u0437 YAML. \u041d\u0430 \u043d\u0430\u0448 \u0432\u0437\u0433\u043b\u044f\u0434 \u2014 \u043f\u043b\u044e\u0441 (intellisense, \u0440\u0435\u0444\u0430\u043a\u0442\u043e\u0440\u0438\u043d\u0433, \u0442\u0438\u043f\u044b), \u043d\u043e \u043a\u043e\u043c\u0443-\u0442\u043e \u0434\u0435\u043a\u043b\u0430\u0440\u0430\u0442\u0438\u0432\u043d\u044b\u0439 YAML \u0443\u0434\u043e\u0431\u043d\u0435\u0435.<\/p>\n<hr\/>\n<h4>Free vs Pro<\/h4>\n<p>\u041d\u0438\u043a\u0430\u043a\u043e\u0439 Pro-\u0432\u0435\u0440\u0441\u0438\u0438 redb.Route \u043d\u0435\u0442. Apache 2.0 \u0446\u0435\u043b\u0438\u043a\u043e\u043c: \u0432\u0441\u0435 22 \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u0430, \u0432\u0441\u0435 EIP-\u043f\u0430\u0442\u0442\u0435\u0440\u043d\u044b, expression engine,\u00a0<code>Transacted()<\/code>, OpenTelemetry, retry\/DLC, Tsak runtime container, cluster, hot-reload, dashboard.<\/p>\n<p>(Pro-\u0432\u0435\u0440\u0441\u0438\u044f \u0435\u0441\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u0443 redb.Core \u2014 \u0442\u0430\u043c \u043e\u043f\u0442\u0438\u043c\u0438\u0437\u0430\u0446\u0438\u0438 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430: compiled queries, parallel materialization, change tracking. Route \u043f\u0440\u043e \u044d\u0442\u043e \u0432\u043e\u043e\u0431\u0449\u0435 \u043d\u0435 \u0437\u043d\u0430\u0435\u0442 \u2014 \u043e\u043d \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043f\u043e\u0432\u0435\u0440\u0445 \u043b\u044e\u0431\u043e\u0433\u043e redb-\u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u0430, \u043b\u044e\u0431\u043e\u0439 \u0432\u0435\u0440\u0441\u0438\u0438.)<\/p>\n<hr\/>\n<h4>\u0427\u0442\u043e \u0432 3.0.0 \u0441\u0432\u0435\u0436\u0435\u0433\u043e<\/h4>\n<p>\u041f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e \u0441\u043e \u0441\u0442\u0430\u0442\u044c\u0451\u0439\u00a0<a href=\"https:\/\/habr.com\/ru\/articles\/1042058\/\" rel=\"noopener noreferrer nofollow\">\u00abFree\/Pro query parity\u00bb \u043f\u0440\u043e redb.Core 3.0.0<\/a>\u00a0\u0443 Route \u0442\u043e\u0436\u0435 \u0432\u044b\u0448\u043b\u0430 \u043c\u0430\u0436\u043e\u0440\u043a\u0430. \u0413\u043b\u0430\u0432\u043d\u043e\u0435:<\/p>\n<ul>\n<li>\n<p><strong>\u041e\u0434\u0438\u043d canonical compiler.<\/strong>\u00a0\u0420\u0430\u043d\u044c\u0448\u0435 \u0432\u043d\u0443\u0442\u0440\u0438 \u0436\u0438\u043b \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u044b\u0439 v2-\u0441\u0442\u0435\u043a (<code>OldRouteCompiler<\/code>,\u00a0<code>IRouteDefinition2<\/code>,\u00a0<code>BlockStack<\/code>). \u0423\u0434\u0430\u043b\u0438\u043b\u0438. \u0422\u0435\u043f\u0435\u0440\u044c AST \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430 \u2014 \u044d\u0442\u043e \u0434\u0435\u0440\u0435\u0432\u043e\u00a0<code>IProcessorDefinition<\/code>\u00a0\u043d\u043e\u0434, \u043a\u0430\u0436\u0434\u0430\u044f \u043a\u043e\u043c\u043f\u0438\u043b\u0438\u0440\u0443\u0435\u0442 \u0441\u0435\u0431\u044f \u0447\u0435\u0440\u0435\u0437\u00a0<code>CreateProcessor(IRouteContext)<\/code>. \u041d\u0438\u043a\u0430\u043a\u0438\u0445 bridge-\u043a\u043b\u0430\u0441\u0441\u043e\u0432, \u043d\u0438\u043a\u0430\u043a\u043e\u0433\u043e \u00abv2 DSL \u2192 bridge \u2192 legacy compiler\u00bb \u2014 \u0442\u0430 \u0436\u0435 \u043c\u043e\u0434\u0435\u043b\u044c, \u0447\u0442\u043e \u0443 Camel \u0432\u043d\u0443\u0442\u0440\u0438.<\/p>\n<\/li>\n<li>\n<p><strong>Dynamic endpoints (<\/strong><code><strong>ToD<\/strong><\/code><strong>, dynamic\u00a0<\/strong><code><strong>WireTap<\/strong><\/code><strong>, dynamic\u00a0<\/strong><code><strong>Enrich<\/strong><\/code><strong>).<\/strong>\u00a0URI \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u0435\u043b\u044f \u0432\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u0442\u0441\u044f \u043d\u0430 \u043a\u0430\u0436\u0434\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0447\u0435\u0440\u0435\u0437 string template,\u00a0<code>IExpression<\/code>\u00a0\u0438\u043b\u0438\u00a0<code>Func&lt;IExchange, string&gt;<\/code>. \u042d\u0442\u043e Camel&#8217;\u043e\u0432\u0441\u043a\u0438\u0439\u00a0<code>toD(...)<\/code>\u00a0\u043e\u0434\u0438\u043d-\u0432-\u043e\u0434\u0438\u043d.<\/p>\n<\/li>\n<li>\n<p><strong>String-template DSL \u0434\u043b\u044f\u00a0<\/strong><code><strong>SetBody<\/strong><\/code><strong>\u00a0\/\u00a0<\/strong><code><strong>SetHeader<\/strong><\/code><strong>\u00a0\/\u00a0<\/strong><code><strong>SetProperty<\/strong><\/code><strong>\u00a0\/\u00a0<\/strong><code><strong>Log<\/strong><\/code><strong>.<\/strong>\u00a0<code>${header.x}<\/code>,\u00a0<code>${body.OrderId}<\/code>\u00a0\u2014 \u043a\u043e\u043c\u043f\u0438\u043b\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u043e\u0434\u0438\u043d \u0440\u0430\u0437 \u043f\u0440\u0438 build, \u043d\u0435 \u0438\u043d\u0442\u0435\u0440\u043f\u0440\u0435\u0442\u0438\u0440\u0443\u044e\u0442\u0441\u044f.<\/p>\n<\/li>\n<li>\n<p><code><strong>OnException<\/strong><\/code><strong>\u00a0parity \u0441 Camel.<\/strong>\u00a0\u0414\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u044b\u00a0<code>LogStackTrace(bool)<\/code>,\u00a0<code>LogExhausted(bool)<\/code>, \u0447\u0442\u043e\u0431\u044b fluent-\u043d\u0430\u0431\u043e\u0440 \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u043b \u0441 Camel&#8217;\u043e\u0432\u0441\u043a\u0438\u043c\u00a0<code>onException(...)<\/code>.<\/p>\n<\/li>\n<li>\n<p><strong>Reference-\u0442\u0435\u0441\u0442\u044b<\/strong>\u00a0\u043d\u0430 Choice \/ TryCatch \/ Filter (~1200 \u0441\u0442\u0440\u043e\u043a), \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043f\u0438\u043d\u044f\u0442 \u0441\u0435\u043c\u0430\u043d\u0442\u0438\u043a\u0443 \u0438 \u043b\u043e\u0432\u044f\u0442 \u0440\u0435\u0433\u0440\u0435\u0441\u0441\u0438\u0438.<\/p>\n<\/li>\n<\/ul>\n<p>\u041f\u043e\u043b\u043d\u044b\u0439 CHANGELOG:\u00a0<a href=\"https:\/\/github.com\/redbase-app\/redb-route\/blob\/main\/CHANGELOG.md\" rel=\"noopener noreferrer nofollow\">redb.Route\/<\/a><a href=\"http:\/\/CHANGELOG.md\" rel=\"noopener noreferrer nofollow\">CHANGELOG.md<\/a>.<\/p>\n<hr\/>\n<h4>\u0421\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u0435 \u0432 \u043e\u0434\u043d\u043e\u0439 \u0442\u0430\u0431\u043b\u0438\u0446\u0435<\/h4>\n<div>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th>\n<p align=\"left\">\n<\/th>\n<th>\n<p align=\"left\">Apache Camel<\/p>\n<\/th>\n<th>\n<p align=\"left\">MassTransit<\/p>\n<\/th>\n<th>\n<p align=\"left\">NServiceBus<\/p>\n<\/th>\n<th>\n<p align=\"left\">Wolverine<\/p>\n<\/th>\n<th>\n<p align=\"left\"><strong>redb.Route<\/strong><\/p>\n<\/th>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">\u041f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0430<\/p>\n<\/td>\n<td>\n<p align=\"left\">JVM<\/p>\n<\/td>\n<td>\n<p align=\"left\">.NET<\/p>\n<\/td>\n<td>\n<p align=\"left\">.NET<\/p>\n<\/td>\n<td>\n<p align=\"left\">.NET<\/p>\n<\/td>\n<td>\n<p align=\"left\"><strong>.NET 8\/9\/10<\/strong><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">\u041b\u0438\u0446\u0435\u043d\u0437\u0438\u044f<\/p>\n<\/td>\n<td>\n<p align=\"left\">Apache 2.0<\/p>\n<\/td>\n<td>\n<p align=\"left\"><strong>\u041a\u043e\u043c\u043c\u0435\u0440\u0447\u0435\u0441\u043a\u0430\u044f<\/strong>\u00a0(v9,\u00a0<a href=\"https:\/\/massient.com\/\" rel=\"noopener noreferrer nofollow\">Massient<\/a>)<\/p>\n<\/td>\n<td>\n<p align=\"left\"><strong>\u041a\u043e\u043c\u043c\u0435\u0440\u0447\u0435\u0441\u043a\u0430\u044f<\/strong><\/p>\n<\/td>\n<td>\n<p align=\"left\">MIT<\/p>\n<\/td>\n<td>\n<p align=\"left\"><strong>Apache 2.0<\/strong><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">\u0424\u043e\u043a\u0443\u0441<\/p>\n<\/td>\n<td>\n<p align=\"left\">EIP routing<\/p>\n<\/td>\n<td>\n<p align=\"left\">Message bus + Saga<\/p>\n<\/td>\n<td>\n<p align=\"left\">Message bus + Saga<\/p>\n<\/td>\n<td>\n<p align=\"left\">Mediator + Saga<\/p>\n<\/td>\n<td>\n<p align=\"left\"><strong>ESB \/ EIP routing<\/strong><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">\u0422\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u044b<\/p>\n<\/td>\n<td>\n<p align=\"left\">300+<\/p>\n<\/td>\n<td>\n<p align=\"left\">5<\/p>\n<\/td>\n<td>\n<p align=\"left\">7<\/p>\n<\/td>\n<td>\n<p align=\"left\">4<\/p>\n<\/td>\n<td>\n<p align=\"left\"><strong>22 + 5 \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0445<\/strong><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">EIP-\u043f\u0430\u0442\u0442\u0435\u0440\u043d\u044b<\/p>\n<\/td>\n<td>\n<p align=\"left\">80+<\/p>\n<\/td>\n<td>\n<p align=\"left\">Saga, R\/R, Outbox<\/p>\n<\/td>\n<td>\n<p align=\"left\">Saga, R\/R<\/p>\n<\/td>\n<td>\n<p align=\"left\">Saga, R\/R, Outbox<\/p>\n<\/td>\n<td>\n<p align=\"left\"><strong>30+<\/strong><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">Expression engine<\/p>\n<\/td>\n<td>\n<p align=\"left\">Simple Language (\u0438\u043d\u0442\u0435\u0440\u043f\u0440\u0435\u0442\u0438\u0440\u0443\u0435\u043c\u044b\u0439)<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u2014<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u2014<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u2014<\/p>\n<\/td>\n<td>\n<p align=\"left\"><strong>Compiled (<\/strong><code><strong>System.Linq.Expressions<\/strong><\/code><strong>)<\/strong><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">\u041a\u043e\u043d\u0444\u0438\u0433<\/p>\n<\/td>\n<td>\n<p align=\"left\">XML \u0438\u043b\u0438 Java DSL<\/p>\n<\/td>\n<td>\n<p align=\"left\">C# fluent<\/p>\n<\/td>\n<td>\n<p align=\"left\">C# fluent<\/p>\n<\/td>\n<td>\n<p align=\"left\">C# fluent<\/p>\n<\/td>\n<td>\n<p align=\"left\"><strong>C# fluent only<\/strong><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">Durable saga<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0414\u0430<\/p>\n<\/td>\n<td>\n<p align=\"left\"><strong>\u0414\u0430 (DB-backed)<\/strong><\/p>\n<\/td>\n<td>\n<p align=\"left\"><strong>\u0414\u0430 (DB-backed)<\/strong><\/p>\n<\/td>\n<td>\n<p align=\"left\"><strong>\u0414\u0430 (Marten \/ EF Core)<\/strong><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0422\u043e\u043b\u044c\u043a\u043e \u043a\u043e\u043c\u043f\u0435\u043d\u0441\u0438\u0440\u0443\u044e\u0449\u0438\u0435 \u0448\u0430\u0433\u0438 + persistent\u00a0<code>IdempotentConsumer<\/code><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">Managed outbox<\/p>\n<\/td>\n<td>\n<p align=\"left\">Plugin<\/p>\n<\/td>\n<td>\n<p align=\"left\"><strong>\u0414\u0430<\/strong><\/p>\n<\/td>\n<td>\n<p align=\"left\"><strong>\u0414\u0430<\/strong><\/p>\n<\/td>\n<td>\n<p align=\"left\"><strong>\u0414\u0430<\/strong><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0421\u043e\u0431\u0438\u0440\u0430\u0435\u0442\u0441\u044f \u0438\u0437\u00a0<code>Sql.Poll(...).Transacted()<\/code>\u00a0+\u00a0<code>IdempotentConsumer<\/code><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">Publisher confirms \/ Kafka EOS<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0414\u0430<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0414\u0430<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0414\u0430<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0414\u0430<\/p>\n<\/td>\n<td>\n<p align=\"left\"><strong>\u0414\u0430<\/strong><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">Runtime container<\/p>\n<\/td>\n<td>\n<p align=\"left\">Camel K \/ JBoss Fuse<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u2014<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u2014<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u2014<\/p>\n<\/td>\n<td>\n<p align=\"left\"><strong>redb.Tsak<\/strong>\u00a0(hot-reload, cluster)<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<p>\u041a\u0440\u0430\u0442\u043a\u043e: \u0443 Camel \u2014 \u0431\u043e\u043b\u044c\u0448\u0435 \u0432\u0441\u0435\u0433\u043e, \u043d\u043e \u043e\u043d JVM. \u0423 \u0442\u0440\u0451\u0445 .NET-\u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432 \u2014 \u043e\u0442\u043b\u0438\u0447\u043d\u0430\u044f durable saga + managed outbox, \u043d\u043e\u00a0<strong>\u043d\u0435 ESB<\/strong>, \u0438 \u043f\u0440\u0438 \u044d\u0442\u043e\u043c\u00a0<strong>\u0434\u0432\u0435 \u0438\u0437 \u0442\u0440\u0451\u0445 \u0443\u0436\u0435 \u043a\u043e\u043c\u043c\u0435\u0440\u0447\u0435\u0441\u043a\u0438\u0435<\/strong>\u00a0(MassTransit v9 \u043f\u0435\u0440\u0435\u0435\u0445\u0430\u043b \u0432\u00a0<a href=\"https:\/\/massient.com\/\" rel=\"noopener noreferrer nofollow\">Massient, Inc.<\/a>, NServiceBus \u0431\u044b\u043b \u043f\u043b\u0430\u0442\u043d\u044b\u043c \u0438\u0437\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e). \u0421\u0432\u043e\u0431\u043e\u0434\u043d\u044b\u043c \u043e\u0441\u0442\u0430\u043b\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e Wolverine \u043d\u0430 MIT \u2014 \u043d\u043e \u044d\u0442\u043e mediator+saga, \u0431\u0435\u0437 20+ \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u043e\u0432 \u0438 \u0431\u0435\u0437 \u043f\u043e\u043b\u043d\u043e\u0433\u043e EIP-\u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0430. \u0423 redb.Route \u2014 \u0432\u0441\u0435 30+ \u0445\u043e\u0434\u043e\u0432\u044b\u0445 EIP, 22 \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u0430, compiled expressions, transactional pipelines, \u0438 Tsak \u043a\u0430\u043a runtime-\u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u0441 \u043a\u043b\u0430\u0441\u0442\u0435\u0440\u043e\u043c. Apache 2.0 \u0446\u0435\u043b\u0438\u043a\u043e\u043c. \u0415\u0441\u043b\u0438 \u0432\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u00ab<strong>Apache Camel \u0432 .NET<\/strong>, \u0438 \u0436\u0435\u043b\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u0431\u0435\u0437 \u0441\u0447\u0451\u0442\u0430 \u043d\u0430 \u043b\u0438\u0446\u0435\u043d\u0437\u0438\u044e\u00bb \u2014 \u044d\u0442\u043e redb.Route.<\/p>\n<hr\/>\n<h4>\u0421\u0441\u044b\u043b\u043a\u0438<\/h4>\n<p><a href=\"https:\/\/github.com\/redbase-app\" rel=\"noopener noreferrer nofollow\">GitHub org (\u0432\u0441\u0435 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438)<\/a>\u00a0<a href=\"https:\/\/github.com\/redbase-app\/redb-route\" rel=\"noopener noreferrer nofollow\">\u0420\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 redb.Route<\/a>\u00a0<a href=\"https:\/\/github.com\/redbase-app\/redb-tsak\" rel=\"noopener noreferrer nofollow\">\u0420\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 redb.Tsak<\/a>\u00a0<a href=\"https:\/\/github.com\/redbase-app\/redb-tsak\/releases\" rel=\"noopener noreferrer nofollow\">\u0413\u043e\u0442\u043e\u0432\u044b\u0435 \u0431\u0438\u043d\u0430\u0440\u043d\u0438\u043a\u0438 redb.Tsak (releases)<\/a>\u00a0<a href=\"https:\/\/github.com\/redbase-app\/redb-tsak\/pkgs\/container\/redb-tsak-stack\" rel=\"noopener noreferrer nofollow\">Docker-\u043e\u0431\u0440\u0430\u0437 redb-tsak-stack (GHCR)<\/a>\u00a0<a href=\"http:\/\/README.md\" rel=\"noopener noreferrer nofollow\">README.md<\/a><a href=\"https:\/\/github.com\/redbase-app\/redb-route\/blob\/main\/README.md\" rel=\"noopener noreferrer nofollow\"> redb.Route \u0441 \u043f\u043e\u043b\u043d\u043e\u0439 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0435\u0439<\/a>\u00a0<a href=\"https:\/\/github.com\/redbase-app\/redb-route\/tree\/main\/redb.Route.Demo\" rel=\"noopener noreferrer nofollow\">redb.Route.Demo \u2014 39 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043e\u0432, 18 \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u043e\u0432, runnable reference<\/a>\u00a0<a href=\"https:\/\/github.com\/redbase-app\/redb-route\/blob\/main\/CHANGELOG.md\" rel=\"noopener noreferrer nofollow\">CHANGELOG 3.0.0<\/a>\u00a0<a href=\"https:\/\/redbase.app\/\" rel=\"noopener noreferrer nofollow\">\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f \u0438 \u043f\u0440\u0438\u043c\u0435\u0440\u044b (EN)<\/a>\u00a0<a href=\"https:\/\/www.nuget.org\/profiles\/relikt\" rel=\"noopener noreferrer nofollow\">NuGet \u2014 \u0432\u0441\u0435 43 \u043f\u0430\u043a\u0435\u0442\u0430\u00a0<code>redb.*<\/code>\u00a0(~20 800 \u0437\u0430\u0433\u0440\u0443\u0437\u043e\u043a)<\/a>\u00a0\u2014 Route, Core, \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u044b, 22 \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u0430, Tsak\u00a0<a href=\"https:\/\/redbase.app\/architecture\" rel=\"noopener noreferrer nofollow\">\u0410\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430 redb.Core<\/a>\u00a0\u2014 \u043d\u0430 \u0447\u0451\u043c \u0432\u0441\u0451 \u044d\u0442\u043e \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u043e\u00a0<a href=\"https:\/\/habr.com\/ru\/articles\/1042058\/\" rel=\"noopener noreferrer nofollow\">\u041f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0430\u044f \u0441\u0442\u0430\u0442\u044c\u044f \u2014 \u043f\u0440\u043e redb.Core \/ Free vs Pro<\/a><\/p>\n<p>\u0415\u0441\u043b\u0438 \u0434\u043e\u0447\u0438\u0442\u0430\u043b\u0438 \u2014 \u0441\u043f\u0430\u0441\u0438\u0431\u043e. \u041a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438, \u0431\u0430\u0433\u0438, EXPLAIN-\u043f\u043b\u0430\u043d\u044b \u2014 \u0432\u0441\u0451 \u0432\u00a0<a href=\"https:\/\/github.com\/redbase-app\/redb-route\/discussions\" rel=\"noopener noreferrer nofollow\">GitHub Discussions<\/a>.<\/p>\n<\/div>\n<p>\u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/1042392\/\">https:\/\/habr.com\/ru\/articles\/1042392\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>redb\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430\u0423 \u0432\u0430\u0441 \u043d\u0435 5 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u2014 \u0443 \u0432\u0430\u0441\u00a0\u0434\u0435\u0441\u044f\u0442\u043a\u0438. \u0411\u044d\u043a\u0435\u043d\u0434, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0440\u043e\u0441 \u0442\u0440\u0438 \u0433\u043e\u0434\u0430: \u043c\u043e\u043d\u043e\u043b\u0438\u0442, \u0440\u0430\u0441\u043a\u043e\u043b\u043e\u0442\u044b\u0439 \u043d\u0430 \u043a\u0443\u0441\u043a\u0438, GPS-\u0444\u0438\u0434 \u043e\u0442 \u0430\u0432\u0442\u043e\u043f\u0430\u0440\u043a\u0430, \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044f, \u0432\u0435\u0431-\u043a\u0430\u0431\u0438\u043d\u0435\u0442 \u0434\u0438\u0441\u043f\u0435\u0442\u0447\u0435\u0440\u0430, \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 SAP \/ 1\u0421 \/ \u0440\u0435\u0433\u0443\u043b\u044f\u0442\u043e\u0440\u0430\u043c\u0438 \/ \u043c\u0430\u0440\u043a\u0435\u0442\u043f\u043b\u0435\u0439\u0441\u0430\u043c\u0438, \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 SMTP-\u0432\u043e\u0440\u043a\u0435\u0440, \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 PDF-\u0433\u0435\u043d\u0435\u0440\u0430\u0442\u043e\u0440, \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0448\u0435\u0434\u0443\u043b\u0435\u0440 \u043d\u043e\u0447\u043d\u044b\u0445 \u043f\u0435\u0440\u0435\u0441\u0447\u0451\u0442\u043e\u0432. \u041c\u0435\u0436\u0434\u0443 \u043d\u0438\u043c\u0438 \u2014 Kafka (\u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043a\u043b\u0430\u0441\u0442\u0435\u0440\u043e\u0432, \u043f\u043e \u0442\u043e\u043f\u0438\u043a\u0443 \u043d\u0430 \u0434\u043e\u043c\u0435\u043d), RabbitMQ (RPC + pub\/sub + DLQ), Redis (\u043a\u044d\u0448, last-known-state, pub\/sub-\u043a\u0430\u043d\u0430\u043b\u044b), \u043f\u0430\u0440\u0430 HTTP-\u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u043e\u0432 \u043d\u0430\u0440\u0443\u0436\u0443, SFTP \u0441 \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u043e\u043c, SQL-polling outbox-\u0442\u0430\u0431\u043b\u0438\u0446\u044b \u0441\u0442\u0430\u0440\u043e\u0433\u043e \u043c\u043e\u043d\u043e\u043b\u0438\u0442\u0430, MQTT \u0441 \u0442\u0440\u0435\u043a\u0435\u0440\u043e\u0432, IBM MQ \u0434\u043b\u044f \u043e\u0434\u043d\u043e\u0433\u043e \u0434\u0440\u0435\u0432\u043d\u0435\u0433\u043e \u0431\u0430\u043d\u043a\u043e\u0432\u0441\u043a\u043e\u0433\u043e \u043a\u043e\u043d\u0442\u0443\u0440\u0430, SignalR-\u0445\u0430\u0431\u044b \u0434\u043b\u044f real-time-\u0434\u0430\u0448\u0431\u043e\u0440\u0434\u043e\u0432. \u041d\u0430 \u043a\u0430\u0436\u0434\u043e\u043c \u0441\u0442\u044b\u043a\u0435 \u2014 \u0441\u0432\u043e\u0439 \u0440\u0435\u0442\u0440\u0430\u0439, \u0441\u0432\u043e\u0439 DLQ (\u0438\u043b\u0438 \u043d\u0435\u0442 DLQ), \u0441\u0432\u043e\u044f \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f, \u0441\u0432\u043e\u0438 \u043c\u0435\u0442\u0440\u0438\u043a\u0438 (\u0438\u043b\u0438 \u043d\u0435\u0442 \u043c\u0435\u0442\u0440\u0438\u043a), \u0441\u0432\u043e\u044f \u0431\u043e\u0439\u043b\u0435\u0440\u043f\u043b\u0435\u0439\u0442-\u043e\u0431\u0432\u044f\u0437\u043a\u0430 \u0438\u0437 \u043a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440\u043e\u0432 \u0438\u00a0try\/catch.\u041a\u0430\u0436\u0434\u044b\u0439 \u0438\u0437 \u044d\u0442\u0438\u0445 \u0441\u0442\u044b\u043a\u043e\u0432 \u0436\u0438\u0432\u0451\u0442 \u0441\u0432\u043e\u0435\u0439 \u0436\u0438\u0437\u043d\u044c\u044e \u0432\u00a0Program.cs\u00a0\u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0435\u0433\u043e \u0441\u0435\u0440\u0432\u0438\u0441\u0430. \u041a\u0430\u0436\u0434\u044b\u0439 \u2014 \u044d\u0442\u043e hand-rolled \u0446\u0438\u043a\u043b:\/\/ \u0413\u0434\u0435-\u0442\u043e \u0432 OrdersService:var consumer = new ConsumerBuilder&lt;Null, string&gt;(consumerConfig).Build();consumer.Subscribe(&#171;orders&#187;);while (!stoppingToken.IsCancellationRequested){    try    {        var msg = consumer.Consume(stoppingToken);        var dto = JsonSerializer.Deserialize&lt;OrderDto&gt;(msg.Value);        if (dto.Type != &#171;new&#187;) continue;          \/\/ filter        var retries = 0;        while (true)                              \/\/ retry        {            try { await PublishToRabbit(dto); break; }            catch (Exception ex) when (retries++ &lt; 3)            { await Task.Delay(TimeSpan.FromSeconds(retries * retries)); }        }        consumer.Commit(msg);                     \/\/ ack    }    catch (ConsumeException ex) { _logger.LogError(ex, &#171;kafka&#187;); }    catch (Exception ex)        { _logger.LogError(ex, &#171;send&#187;); }}\u0418 \u0442\u0430\u043a\u0438\u0445 80 \u0441\u0442\u0440\u043e\u043a \u2014 \u043d\u0430 \u043a\u0430\u0436\u0434\u043e\u0435 \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435. \u0423 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u2014 \u0441\u0432\u043e\u0439 \u0440\u0435\u0442\u0440\u0430\u0439, \u0441\u0432\u043e\u0439 DLQ (\u0438\u043b\u0438 \u043d\u0435\u0442 DLQ), \u0441\u0432\u043e\u044f \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f, \u0441\u0432\u043e\u0438 \u043c\u0435\u0442\u0440\u0438\u043a\u0438 (\u0438\u043b\u0438 \u043d\u0435\u0442 \u043c\u0435\u0442\u0440\u0438\u043a). \u041d\u0430 code review \u044d\u0442\u043e \u00ab\u043d\u0443 \u0434\u0430, \u0447\u0451\u00bb. \u0427\u0435\u0440\u0435\u0437 \u0433\u043e\u0434 \u043d\u0430 \u043e\u043d\u0431\u043e\u0440\u0434\u0438\u043d\u0433\u0435 \u043d\u043e\u0432\u043e\u0433\u043e \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430 \u2014 \u00ab\u0430 \u0433\u0434\u0435 \u0442\u0443\u0442 \u0432\u043e\u043e\u0431\u0449\u0435 \u0447\u0442\u043e \u043b\u0435\u0436\u0438\u0442\u00bb.\u041d\u0430 JVM \u044d\u0442\u0443 \u0437\u0430\u0434\u0430\u0447\u0443 20 \u043b\u0435\u0442 \u043d\u0430\u0437\u0430\u0434 \u0440\u0435\u0448\u0438\u043b\u0438\u00a0Apache Camel\u00a0\u0438 \u0431\u043e\u043b\u0435\u0435 \u043e\u0431\u0449\u0438\u0439\u00a0Enterprise Integration Patterns\u00a0(\u043a\u043d\u0438\u0436\u043a\u0430 Gregor Hohpe \u0438 Bobby Woolf, \u0442\u0430 \u0441\u0430\u043c\u0430\u044f \u00ab\u0436\u0451\u043b\u0442\u0430\u044f\u00bb). \u0418\u0434\u0435\u044f \u043f\u0440\u043e\u0441\u0442\u0430\u044f: \u043c\u0430\u0440\u0448\u0440\u0443\u0442 \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043a\u0430\u043a pipeline\u00a0From \u2192 Process \u2192 To, EIP-\u043f\u0430\u0442\u0442\u0435\u0440\u043d\u044b (Splitter, Aggregator, Content-Based Router, WireTap, Dead Letter Channel, Idempotent Consumer, Saga) \u2014 first-class \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b DSL. \u0422\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u043e\u0432 \u2014 300+: \u043e\u0442 Kafka \u0434\u043e LDAP, \u043e\u0442 S3 \u0434\u043e IBM MQ.\u0412 .NET \u044d\u0442\u043e\u0433\u043e \u043d\u0435 \u0431\u044b\u043b\u043e.MassTransit, NServiceBus, Wolverine \u2014 \u043e\u0442\u043b\u0438\u0447\u043d\u044b\u0435\u00a0message bus&#8217;\u044b, \u043d\u043e \u044d\u0442\u043e \u043d\u0435 \u043e\u0434\u043d\u043e \u0438 \u0442\u043e \u0436\u0435. \u0423 \u043d\u0438\u0445 4\u20137 \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u043e\u0432 (Kafka, RabbitMQ, Azure SB, SQS, \u0438\u043d\u043e\u0433\u0434\u0430 SQL), \u0444\u043e\u043a\u0443\u0441 \u2014 durable saga + handler classes. EIP-\u043a\u0430\u0442\u0430\u043b\u043e\u0433 \u043e\u043d\u0438 \u0437\u0430\u043a\u0440\u044b\u0432\u0430\u044e\u0442 \u043d\u0430 3-4 \u043f\u0430\u0442\u0442\u0435\u0440\u043d\u0430 (Saga, Request\/Response, Outbox). \u0410 \u043a\u043e\u0433\u0434\u0430 \u043d\u0443\u0436\u043d\u043e \u00ab\u0437\u0430\u0431\u0435\u0440\u0438 XML \u0438\u0437 SQL stored proc \u2192 \u0440\u0430\u0441\u043f\u0430\u0440\u0441\u0438 \u2192 \u043f\u043e\u043b\u043e\u0436\u0438 \u0432 RabbitMQ \u2192 \u043f\u0440\u043e\u0434\u0443\u0431\u043b\u0438\u0440\u0443\u0439 \u0432 SFTP-\u0430\u0440\u0445\u0438\u0432 \u0441 retention 30 \u0434\u043d\u0435\u0439\u00bb \u2014 \u043f\u0438\u0448\u0435\u0448\u044c \u0440\u0443\u043a\u0430\u043c\u0438 \u0432\u0441\u044e \u044d\u0442\u0443 \u043a\u043b\u0430\u0441\u0441\u0438\u0447\u0435\u0441\u043a\u0443\u044e \u043a\u0430\u0448\u0443 \u0438\u0437 \u043a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440\u043e\u0432, \u043f\u0440\u043e\u0434\u044e\u0441\u0435\u0440\u043e\u0432 \u0438\u00a0try\/catch.\u0418 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u043f\u043e\u0432\u043e\u0440\u043e\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0441\u043b\u0443\u0447\u0438\u043b\u0441\u044f \u0431\u0443\u043a\u0432\u0430\u043b\u044c\u043d\u043e \u0441\u0435\u0439\u0447\u0430\u0441:\u00a0MassTransit v9 \u0441\u0442\u0430\u043b \u043a\u043e\u043c\u043c\u0435\u0440\u0447\u0435\u0441\u043a\u0438\u043c. \u041f\u0440\u043e\u0435\u043a\u0442 \u043f\u0435\u0440\u0435\u0435\u0445\u0430\u043b \u043a \u043d\u043e\u0432\u043e\u0439 \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u0438\u00a0Massient, Inc., \u043d\u0430 \u0433\u043b\u0430\u0432\u043d\u043e\u0439 \u043d\u043e\u0432\u043e\u0439 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438\u00a0masstransit.massient.com\u00a0\u043a\u043d\u043e\u043f\u043a\u0430 \u00abGet a License\u00bb, \u0432 \u0434\u043e\u043a\u0430\u0445 \u2014\u00a0\u00abConfigure the license key\u00bb, Customer Support \u0438 Usage Telemetry. \u0414\u043e\u0441\u043b\u043e\u0432\u043d\u043e \u0441 \u0438\u0445 \u0441\u0430\u0439\u0442\u0430:\u00a0\u00abMassient is the new company behind the commercial release of MassTransit v9.\u00bb\u00a0\u0422\u043e \u0435\u0441\u0442\u044c \u0438\u0437 \u00ab\u0442\u0440\u0451\u0445 .NET-\u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u00bb \u0434\u0432\u0435 \u2014 \u043f\u043b\u0430\u0442\u043d\u044b\u0435 (NServiceBus \u0434\u0430\u0432\u043d\u043e, MassTransit v9 \u0442\u0435\u043f\u0435\u0440\u044c), \u043e\u0441\u0442\u0430\u0451\u0442\u0441\u044f Wolverine \u043d\u0430 MIT \u2014 \u0438 \u043e\u043d, \u043e\u043f\u044f\u0442\u044c-\u0442\u0430\u043a\u0438, \u043d\u0435 ESB, \u0430 mediator + saga, \u0431\u0435\u0437 \u0434\u0432\u0443\u0445 \u0434\u0435\u0441\u044f\u0442\u043a\u043e\u0432 \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u043e\u0432 \u0438 \u0431\u0435\u0437 EIP-\u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0430. \u0421\u0432\u043e\u0431\u043e\u0434\u043d\u043e\u0433\u043e Camel-\u0443\u0440\u043e\u0432\u043d\u044f \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u0430 \u043f\u043e\u0434 .NET \u0434\u043e \u0441\u0438\u0445 \u043f\u043e\u0440 \u043d\u0435 \u0431\u044b\u043b\u043e.\u0425\u043e\u0442\u0435\u043b\u043e\u0441\u044c: \u043e\u043f\u0438\u0441\u0430\u0442\u044c \u043c\u0430\u0440\u0448\u0440\u0443\u0442 \u043a\u0430\u043a pipeline \u043d\u0430 C#, \u0447\u0442\u043e\u0431 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0441\u0430\u043c\u0430 \u0437\u043d\u0430\u043b\u0430 \u043f\u0440\u043e Kafka, RabbitMQ, SQL, SFTP, HTTP, MQTT, retry, DLQ, transactional ack, \u0438 \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u0431\u044b\u043b \u043f\u043e\u043b\u043d\u044b\u0439 \u043d\u0430\u0431\u043e\u0440 EIP-\u043f\u0430\u0442\u0442\u0435\u0440\u043d\u043e\u0432 \u0438\u0437 \u043a\u0430\u043d\u043e\u043d\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u043a\u043d\u0438\u0436\u043a\u0438.\u041d\u0430\u043f\u0438\u0441\u0430\u043b\u0438. \u0412\u044b\u043b\u043e\u0436\u0438\u043b\u0438 \u043f\u043e\u0434 Apache 2.0.Production case\u042d\u0442\u043e\u0442 \u0442\u0435\u043a\u0441\u0442 \u2014 \u043d\u0435 \u0442\u0435\u043e\u0440\u0438\u044f.\u00a0redb.Route \u2014 \u044d\u0442\u043e ESB-\u043a\u0430\u0440\u043a\u0430\u0441 \u0443\u0440\u043e\u0432\u043d\u044f Apache Camel, \u0438 \u043c\u044b \u044d\u0442\u043e \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u0435\u043c \u043d\u0435 \u043d\u0430 \u0431\u0443\u043c\u0430\u0433\u0435: 22 \u0432\u043d\u0435\u0448\u043d\u0438\u0445 \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u0430 + 5 \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0445 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432, 30+ EIP-\u043f\u0430\u0442\u0442\u0435\u0440\u043d\u043e\u0432 \u043a\u0430\u043a first-class \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b DSL, compiled expression engine \u043d\u0430\u00a0System.Linq.Expressions, transactional pipelines \u0441\u00a0ITransactedAction\u00a0(Kafka EOS, RabbitMQ publisher confirms + tx channels, IBM MQ syncpoint, AMQP 1.0, SQL via\u00a0TransactionScope), persistent\u00a0IdempotentConsumer,\u00a0Saga\u00a0\u0441 \u043a\u043e\u043c\u043f\u0435\u043d\u0441\u0438\u0440\u0443\u044e\u0449\u0438\u043c\u0438 \u0448\u0430\u0433\u0430\u043c\u0438,\u00a0CircuitBreaker,\u00a0Throttle,\u00a0Aggregator,\u00a0Resequencer,\u00a0Scatter-Gather,\u00a0RecipientList,\u00a0DynamicRouter,\u00a0Enrich\/PollEnrich,\u00a0Claim Check, OpenTelemetry \u043d\u0430 \u043a\u0430\u0436\u0434\u044b\u0439 \u0448\u0430\u0433, runtime-\u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u0441 hot-reload \u0438 \u043a\u043b\u0430\u0441\u0442\u0435\u0440\u043e\u043c \u0431\u0435\u0437 \u0432\u043d\u0435\u0448\u043d\u0435\u0433\u043e \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u043e\u0440\u0430. 351 \u043f\u0440\u043e\u0445\u043e\u0434\u044f\u0449\u0438\u0439 \u0442\u0435\u0441\u0442, \u0432\u043a\u043b\u044e\u0447\u0430\u044f \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0435\u00a0DSL reference-suites\u00a0\u043f\u043e\u0434 Camel-\u0441\u0435\u043c\u0430\u043d\u0442\u0438\u043a\u0443 Choice\/When\/Otherwise, TryCatchFinally \u0438 Filter (~1500 \u0441\u0442\u0440\u043e\u043a \u0442\u043e\u043b\u044c\u043a\u043e \u0432 \u0441\u043f\u0435\u0446\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f\u0445). \u042d\u0442\u043e \u043d\u0435 \u00ab\u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a-\u043e\u0431\u0451\u0440\u0442\u043a\u0430 \u043d\u0430\u0434\u00a0IHostedService\u00bb, \u044d\u0442\u043e \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u044b\u0439 ESB.\u0418 \u043e\u043d \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0432 \u043f\u0440\u043e\u0434\u0435. \u041f\u0435\u0440\u0432\u044b\u0439 \u2014 \u043a\u0440\u0443\u043f\u043d\u0430\u044f \u043b\u043e\u0433\u0438\u0441\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u044f (~150k \u0437\u0430\u043a\u0430\u0437\u043e\u0432\/\u043c\u0435\u0441, ~20k B2B-\u043a\u043b\u0438\u0435\u043d\u0442\u043e\u0432, \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u0430\u0432\u0442\u043e\u043f\u0430\u0440\u043a, 600+ \u0433\u043e\u0440\u043e\u0434\u043e\u0432). \u0412\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u044f\u044f TMS \u2014 ~500 \u0432\u043e\u0434\u0438\u0442\u0435\u043b\u0435\u0439 + ~50 \u0434\u0438\u0441\u043f\u0435\u0442\u0447\u0435\u0440\u043e\u0432, 3-\u043d\u043e\u0434\u043e\u0432\u044b\u0439 \u043a\u043b\u0430\u0441\u0442\u0435\u0440 (Xeon, 4 \u044f\u0434\u0440\u0430 \/ 8 \u0413\u0411 \/ 50 \u0413\u0411 SSD \u043d\u0430 \u043d\u043e\u0434\u0443), ~3 \u043c\u0435\u0441\u044f\u0446\u0430 \u0441\u0442\u0430\u0431\u0438\u043b\u044c\u043d\u043e\u0439 \u0440\u0430\u0431\u043e\u0442\u044b, 10\u201315% CPU \u043f\u043e\u0434 \u043f\u043e\u043b\u043d\u043e\u0439 \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u043e\u0439.\u0427\u0435\u0440\u0435\u0437 redb.Route \u0442\u0443\u0434\u0430 \u0437\u0430\u0432\u0435\u0434\u0435\u043d\u044b:\u00a0SAP S\/4\u00a0(\u0447\u0435\u0440\u0435\u0437 SQL polling stored procedure \u2192 XML \u2192 redb),\u00a0Kafka\u00a0(\u0444\u0438\u0434 \u0441 GPS-\u0442\u0440\u0435\u043a\u0435\u0440\u043e\u0432),\u00a0RabbitMQ\u00a0(\u0432\u043d\u0443\u0442\u0440\u0438\u0441\u0435\u0440\u0432\u0438\u0441\u043d\u0430\u044f \u0448\u0438\u043d\u0430 \u0441\u043e\u0431\u044b\u0442\u0438\u0439),\u00a0HTTP API\u00a0\u0434\u043b\u044f UI \u0434\u0438\u0441\u043f\u0435\u0442\u0447\u0435\u0440\u0430,\u00a0\u041c\u0435\u0440\u043a\u0443\u0440\u0438\u0439 \/ \u0415\u0413\u0410\u0418\u0421 \/ \u0427\u0435\u0441\u0442\u043d\u044b\u0439 \u0417\u041d\u0410\u041a \/ \u0424\u0413\u0418\u0421 \u0417\u0435\u0440\u043d\u043e\u00a0(\u0440\u043e\u0441\u0441\u0438\u0439\u0441\u043a\u0438\u0435 \u0440\u0435\u0433\u0443\u043b\u044f\u0442\u043e\u0440\u044b),\u00a0LDAP\/AD\u00a0\u0434\u043b\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438,\u00a0cron-backup\u00a0\u0447\u0435\u0440\u0435\u0437\u00a0redb.Export\u00a0\u0441 \u0440\u043e\u0442\u0430\u0446\u0438\u0435\u0439. \u041e\u0434\u0438\u043d\u00a0InitRoute-\u0444\u0430\u0439\u043b \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u0442 22\u00a0RouteBuilder-\u043a\u043b\u0430\u0441\u0441\u0430. \u041a\u0430\u0436\u0434\u044b\u0439 \u2014 \u0438\u0437\u043e\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 pipeline, \u0441\u043e \u0441\u0432\u043e\u0438\u043c\u0438\u00a0OnException-\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430\u043c\u0438, \u0441\u0432\u043e\u0438\u043c\u00a0RouteId, \u0441\u0432\u043e\u0438\u043c\u0438 \u043c\u0435\u0442\u0440\u0438\u043a\u0430\u043c\u0438 OpenTelemetry.\u0412\u0442\u043e\u0440\u043e\u0439 \u043f\u0440\u043e\u0434 \u2014 \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0430 \u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0438 \u0442\u043e\u0433\u043e \u0436\u0435 \u0437\u0430\u043a\u0430\u0437\u0447\u0438\u043a\u0430,\u00a0\u0434\u0435\u0441\u044f\u0442\u043a\u0438 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432\u00a0\u0432 \u043e\u0434\u043d\u043e\u043c \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438 (~37 .NET-\u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432, ~50+\u00a0RouteBuilder-\u043a\u043b\u0430\u0441\u0441\u043e\u0432). \u041c\u043e\u0434\u0443\u043b\u0438 \u043e\u0431\u0449\u0430\u044e\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437 RabbitMQ (RPC + pub\/sub), \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0439 event-bus, Kafka \u0441 Consumer Group \u0434\u043b\u044f GPS-\u0441\u0442\u0440\u0438\u043c\u0430 \u0441 \u043c\u043e\u0431\u0438\u043b\u043e\u043a, Redis \u0434\u043b\u044f last-known-location-\u043a\u044d\u0448\u0430, RabbitMQ-DLQ \u043f\u043e\u0434 poison messages, \u0438 \u0432\u0441\u0451 \u044d\u0442\u043e \u0437\u0430\u0432\u044f\u0437\u0430\u043d\u043e \u043d\u0430 \u0435\u0434\u0438\u043d\u044b\u0439 ESB-\u043a\u0430\u0440\u043a\u0430\u0441. \u0422\u043e \u0435\u0441\u0442\u044c \u044d\u0442\u043e \u043d\u0435 \u00ab5 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u0441 REST API\u00bb \u2014 \u044d\u0442\u043e \u0434\u0435\u0441\u044f\u0442\u043a\u0438 \u043d\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e \u0434\u0435\u043f\u043b\u043e\u044f\u0449\u0438\u0445\u0441\u044f \u043c\u043e\u0434\u0443\u043b\u0435\u0439, \u0443 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0441\u0432\u043e\u0439\u00a0IRouteContext, \u0441\u0432\u043e\u0439 \u043d\u0430\u0431\u043e\u0440 endpoint&#8217;\u043e\u0432, \u0441\u0432\u043e\u0439 lifecycle. \u0421\u0440\u0430\u0432\u043d\u0438\u043c\u0430\u044f \u043f\u043e \u0441\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0430 \u043d\u0430 JVM \u0431\u044b\u043b\u0430 \u0431\u044b \u2014 Apache Camel + Karaf. \u0423 \u043d\u0430\u0441 \u2014 redb.Route + Tsak.\u0418 \u0432\u0441\u0451 \u044d\u0442\u043e \u2014\u00a0\u0432 \u0442\u0440\u0451\u0445 \u0441\u0442\u0435\u043d\u0434\u0430\u0445: dev \/ test \/ prod, \u043a\u0430\u0436\u0434\u044b\u0439 \u2014 3-\u043d\u043e\u0434\u043e\u0432\u044b\u0439 \u043a\u043b\u0430\u0441\u0442\u0435\u0440. \u0422\u043e \u0435\u0441\u0442\u044c\u00a0RouteBuilder-\u043a\u043b\u0430\u0441\u0441\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0432\u044b \u0432\u0438\u0434\u0438\u0442\u0435 \u0434\u0430\u043b\u044c\u0448\u0435 \u043f\u043e \u0441\u0442\u0430\u0442\u044c\u0435, \u043f\u0440\u044f\u043c\u043e \u0441\u0435\u0439\u0447\u0430\u0441 \u043a\u0440\u0443\u0442\u044f\u0442\u0441\u044f \u0432 \u0434\u0435\u0432\u044f\u0442\u0438 \u0438\u043d\u0441\u0442\u0430\u043d\u0441\u0430\u0445 Tsak \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e: \u0442\u0440\u0438 \u043d\u043e\u0434\u044b \u043f\u043e\u0434\u043d\u0438\u043c\u0430\u044e\u0442 \u043e\u0434\u0438\u043d\u0430\u043a\u043e\u0432\u044b\u0435 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u044b, redb-\u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u043e\u0440 \u0440\u0430\u0437\u0434\u0430\u0451\u0442 \u043c\u0435\u0436\u0434\u0443 \u043d\u0438\u043c\u0438 \u043a\u043b\u0430\u0441\u0442\u0435\u0440\u043d\u044b\u0435 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u044b (Kafka Consumer Group \u2014 \u0431\u0430\u043b\u0430\u043d\u0441\u0438\u0440\u043e\u0432\u043a\u043e\u0439 \u043f\u0430\u0440\u0442\u0438\u0446\u0438\u0439, RabbitMQ \u2014 \u043a\u043e\u043d\u043a\u0443\u0440\u0435\u043d\u0442\u043d\u044b\u0435 consumer&#8217;\u044b \u043d\u0430 \u0442\u043e\u0439 \u0436\u0435 \u043e\u0447\u0435\u0440\u0435\u0434\u0438, HTTP-\u0444\u0430\u0441\u0430\u0434\u044b \u2014 \u0437\u0430 L4-\u0431\u0430\u043b\u0430\u043d\u0441\u0435\u0440\u043e\u043c, cron \u2014 leader-election \u0447\u0435\u0440\u0435\u0437 redb-\u043e\u0431\u044a\u0435\u043a\u0442\u044b, \u0447\u0442\u043e\u0431\u044b \u043a\u0440\u043e\u043d-\u0437\u0430\u0434\u0430\u0447\u0430 \u043e\u0442\u0440\u0430\u0431\u043e\u0442\u0430\u043b\u0430 \u0440\u043e\u0432\u043d\u043e \u043e\u0434\u0438\u043d \u0440\u0430\u0437 \u043d\u0430 \u043a\u043b\u0430\u0441\u0442\u0435\u0440). \u0414\u0435\u0432-\u0441\u0442\u0435\u043d\u0434 \u2014 \u0434\u043b\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0438 feature-\u0432\u0435\u0442\u043e\u043a, test \u2014 \u043f\u0440\u0438\u0451\u043c\u043e\u0447\u043d\u044b\u0435 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0438 \u0438 \u0440\u0435\u0433\u0440\u0435\u0441\u0441 \u043f\u0435\u0440\u0435\u0434 \u0440\u0435\u043b\u0438\u0437\u043e\u043c, prod \u2014 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0430. \u041c\u0435\u0436\u0434\u0443 \u0441\u0442\u0435\u043d\u0434\u0430\u043c\u0438 \u0440\u0430\u0437\u044a\u0435\u0437\u0436\u0430\u0435\u0442\u0441\u044f \u0442\u043e\u0442 \u0436\u0435\u00a0.tpkg-\u043f\u0430\u043a\u0435\u0442: \u043c\u0435\u043d\u044f\u0435\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u043a\u043e\u043d\u0444\u0438\u0433 \u0438 \u0441\u0442\u0440\u043e\u043a\u0430 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u0411\u0414. Cluster + hot-reload + \u0442\u0440\u0438 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u044b\u0445 \u0441\u0442\u0435\u043d\u0434\u0430 \u2014 \u044d\u0442\u043e \u0438 \u0435\u0441\u0442\u044c \u00abproduction-ready\u00bb, \u0430 \u043d\u0435 \u00ab\u0443 \u043c\u0435\u043d\u044f \u043d\u0430 \u043d\u043e\u0443\u0442\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442\u00bb.\u0422\u0440\u0435\u0442\u0438\u0439 \u043f\u0440\u043e\u0434 \u2014 \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0430 (~672k \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432, ~8M \u0441\u0432\u043e\u0439\u0441\u0442\u0432), \u0442\u0430\u043c Route \u0438\u0433\u0440\u0430\u0435\u0442 \u0440\u043e\u043b\u044c \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0435\u0439 \u0448\u0438\u043d\u044b \u043c\u0435\u0436\u0434\u0443 \u043c\u043e\u0434\u0443\u043b\u044f\u043c\u0438. \u0420\u0435\u0430\u043b\u044c\u043d\u044b\u0439 \u043a\u043e\u0434 \u0432\u0441\u0435\u0445 \u0442\u0440\u0451\u0445 \u2014 \u043d\u0438\u0436\u0435.\u0418 \u044d\u0442\u043e \u043d\u0435 \u00ab\u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0435 \u0441\u0431\u043e\u0440\u043a\u0438 \u0438\u0437 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u044f\u00bb. \u0412\u0441\u044f \u0441\u0442\u043e\u043f\u043a\u0430 3.0.0 \u043e\u043f\u0443\u0431\u043b\u0438\u043a\u043e\u0432\u0430\u043d\u0430 \u043d\u0430 nuget.org \u2014\u00a043 \u043f\u0430\u043a\u0435\u0442\u0430 \u043f\u043e\u0434 \u043f\u0440\u0435\u0444\u0438\u043a\u0441\u043e\u043c\u00a0redb.*, \u0441\u0443\u043c\u043c\u0430\u0440\u043d\u043e ~20 800 \u0437\u0430\u0433\u0440\u0443\u0437\u043e\u043a\u00a0\u043d\u0430 \u043c\u043e\u043c\u0435\u043d\u0442 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u044f \u0441\u0442\u0430\u0442\u044c\u0438. \u041f\u043e \u0443\u0431\u044b\u0432\u0430\u043d\u0438\u044e:\u00a0redb.Route\u00a0(~1600),\u00a0redb.Core\u00a0(~1590),\u00a0redb.Core.Pro\u00a0(~1160),\u00a0redb.Templates,\u00a0redb.Postgres\u00a0\/\u00a0redb.MSSql\u00a0(~1000 \u043a\u0430\u0436\u0434\u044b\u0439), \u0434\u0430\u043b\u044c\u0448\u0435 Pro-\u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u044b,\u00a0redb.CLI,\u00a0redb.Export, \u0438 \u0432\u0435\u0441\u044c \u0437\u043e\u043e\u043f\u0430\u0440\u043a\u00a0redb.Route.*\u00a0\u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u043e\u0432 (Kafka, RabbitMQ, IBM MQ, MQTT, AMQP, Redis, gRPC, SignalR, WebSocket, TCP, SFTP, FTP, File, S3, Mail, LDAP, Quartz, Elasticsearch, AzureServiceBus, Firebase, Http, Sql) +\u00a0redb.Tsak.*\u00a0(Core, Core.Pro, Client, CLI, Contracts, Templates). \u0422\u043e \u0435\u0441\u0442\u044c \u0432\u0441\u0451, \u043f\u0440\u043e \u0447\u0442\u043e \u044d\u0442\u0430 \u0441\u0442\u0430\u0442\u044c\u044f \u2014 \u0440\u0435\u0430\u043b\u044c\u043d\u043e \u043b\u0435\u0436\u0438\u0442, \u0432\u0435\u0440\u0441\u0438\u043e\u043d\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0438 \u043f\u043e\u0434\u0442\u044f\u0433\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437\u00a0dotnet add package.\u041a\u0430\u043a \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0427\u0442\u043e\u0431\u044b \u043d\u0435 \u0432\u0440\u0430\u0442\u044c \u0438\u0433\u0440\u0443\u0448\u0435\u0447\u043d\u044b\u043c\u00a0From \u2192 Filter \u2192 To, \u0441\u0440\u0430\u0437\u0443 \u0431\u043e\u0435\u0432\u043e\u0439 shape. \u042d\u0442\u043e \u0443\u043f\u0440\u043e\u0449\u0451\u043d\u043d\u044b\u0439 \u043d\u0430\u0431\u0440\u043e\u0441\u043e\u043a \u0438\u0437 \u043b\u043e\u0433\u0430-\u0442\u0440\u0435\u043a\u0438\u043d\u0433\u043e\u0432\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b: HTTP-\u0444\u0430\u0441\u0430\u0434 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u043c\u0430\u0441\u0441\u0438\u0432 GPS-\u0442\u043e\u0447\u0435\u043a, throttle \u043d\u0430 200 rps, \u0438\u0434\u0435\u043c\u043f\u043e\u0442\u0435\u043d\u0442\u043d\u043e\u0441\u0442\u044c \u043f\u043e\u00a0MessageId, \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f JSON, \u0432\u0435\u0442\u0432\u043b\u0435\u043d\u0438\u0435 \u043f\u043e \u0442\u0438\u043f\u0443 \u0441\u043e\u0431\u044b\u0442\u0438\u044f,\u00a0Multicast\u00a0\u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e \u0432 \u0442\u0440\u0438 \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u0430 (Kafka \u043d\u0430 \u0430\u0433\u0440\u0435\u0433\u0430\u0446\u0438\u044e, RabbitMQ \u043d\u0430 DAL-\u0437\u0430\u043f\u0438\u0441\u044c, Redis-\u043a\u044d\u0448 last-known-location \u0444\u043e\u043d\u043e\u043c \u0447\u0435\u0440\u0435\u0437\u00a0WireTap), \u0430 \u043d\u0430\u0440\u0443\u0436\u0443 \u2014 JSON-\u043e\u0442\u0432\u0435\u0442:public class GpsHttpFacadeRoutes : RouteBuilder{    protected override void Configure()    {        OnException&lt;JsonException&gt;()            .Handled()            .SetHeader(&#171;HTTP_SC&#187;, Constant(400))            .To(&#171;direct:\/\/error-handler&#187;)        .EndOnException();        From(&#171;http:\/\/0.0.0.0:5090\/integration\/gps?methods=POST&#187;)            .RouteId(&#171;gps-http-facade&#187;)            .Throttle(200).Per(TimeSpan.FromSeconds(1))                      \/\/ \u043d\u0435 \u0431\u043e\u043b\u044c\u0448\u0435 200 rps            .IdempotentConsumer(Header(&#171;MessageId&#187;), repository: &#171;redb&#187;)     \/\/ \u0434\u0443\u0431\u043b\u0438 \u043e\u0442\u0441\u0435\u043a\u0430\u0435\u043c            .Process(ValidateGpsBatch)                                       \/\/ JSON \u2192 List&lt;GpsPoint&gt;            .Choice()                .When(Header(&#171;eventType&#187;).IsEqualTo(&#171;location&#187;))                    .Multicast().ParallelProcessing()                        .To(&#171;kafka:mobile.trips.location.sync?key=${header.tripId}&#187;)                        .To(&#171;rabbitmq:?exchange=lt.dal&amp;routingKey=dal.trips.location.sync&#187;)                        .WireTap(&#171;redis:set:gps:last:location:${header.tripId}?ttl=86400&#187;)                    .EndMulticast()                .When(Header(&#171;eventType&#187;).IsEqualTo(&#171;checkin&#187;))                    .To(&#171;rabbitmq:?exchange=lt.dal&amp;routingKey=dal.gps.checkin_checkout&#187;)                .Otherwise()                    .Log(LogLevel.Warning, &#171;unknown eventType: ${header.eventType}&#187;)            .EndChoice()            .SetBody(Constant(&#171;&#187;&#187;{ &#171;status&#187;: &#171;accepted&#187; }&#187;&#187;&#187;))            .SetHeader(&#171;Content-Type&#187;, Constant(&#171;application\/json&#187;));    }}\u042d\u0442\u043e\u0442 \u043e\u0434\u0438\u043d&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-481982","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/481982","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=481982"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/481982\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=481982"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=481982"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=481982"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}