{"id":485995,"date":"2026-07-02T22:04:45","date_gmt":"2026-07-02T22:04:45","guid":{"rendered":"https:\/\/savepearlharbor.com\/?p=485995"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=485995","title":{"rendered":"redb.Route: \u0434\u0432\u0430 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430 \u0437\u0430 \u0432\u0435\u0447\u0435\u0440 \u2014 \u043e\u0442 \u043e\u0442\u043b\u0430\u0434\u043e\u0447\u043d\u043e\u0433\u043e \u0432\u043e\u0440\u043a\u0435\u0440\u0430 \u0434\u043e \u044d\u043d\u0442\u0435\u0440\u043f\u0440\u0430\u0439\u0437\u0430 \u043d\u0430 Tsak"},"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\/416\/c17\/00a\/416c1700a8791afe2bdb243011d6de09.png\" alt=\"redb.tsak worker\" title=\"redb.tsak worker\" width=\"1016\" height=\"651\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/416\/c17\/00a\/416c1700a8791afe2bdb243011d6de09.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/416\/c17\/00a\/416c1700a8791afe2bdb243011d6de09.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>redb.tsak worker<\/figcaption><\/div>\n<\/figure>\n<p><strong>\u0421\u0435\u0440\u0438\u044f:<\/strong>\u00a0redb ecosystem \/ redb.Route redb.Tsak<\/p>\n<p>\u0415\u0441\u0442\u044c \u0443 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0433\u043e \u043a\u043e\u0434\u0430 \u043e\u0434\u043d\u0430 \u043d\u0435\u043f\u0440\u0438\u044f\u0442\u043d\u0430\u044f \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e\u0441\u0442\u044c. \u041d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u043f\u0430\u0440\u0443 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043e\u0432 \u2014 \u00ab\u043f\u0440\u0438\u043d\u044f\u043b HTTP, \u043f\u043e\u043b\u043e\u0436\u0438\u043b \u0432 \u0431\u0430\u0437\u0443, \u043e\u0442\u0434\u0430\u043b \u043e\u0431\u0440\u0430\u0442\u043d\u043e\u00bb \u2014 \u0434\u0435\u043b\u043e \u043d\u0430 \u043f\u043e\u043b\u0447\u0430\u0441\u0430. \u0410 \u0432\u043e\u0442 \u0434\u043e\u0432\u0435\u0441\u0442\u0438 \u044d\u0442\u043e \u0434\u043e \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f, \u043a\u043e\u0433\u0434\u0430 \u043e\u043d\u043e \u043a\u0440\u0443\u0442\u0438\u0442\u0441\u044f \u0432 \u043f\u0440\u043e\u0434\u0435, \u0441\u0430\u043c\u043e \u043f\u043e\u0434\u043d\u0438\u043c\u0430\u0435\u0442\u0441\u044f, \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u043c\u0435\u0442\u0440\u0438\u043a\u0438, \u0443\u043c\u0435\u0435\u0442 \u043e\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0442\u044c\/\u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0435 \u043a\u0443\u0441\u043a\u0438 \u0440\u0443\u043a\u0430\u043c\u0438 \u0438 \u0440\u0430\u0437\u0432\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0431\u0435\u0437 \u043f\u0435\u0440\u0435\u0441\u0431\u043e\u0440\u043a\u0438 \u2014 \u044d\u0442\u043e \u043e\u0431\u044b\u0447\u043d\u043e \u0441\u043e\u0432\u0441\u0435\u043c \u0434\u0440\u0443\u0433\u0430\u044f \u0438\u0441\u0442\u043e\u0440\u0438\u044f \u0438 \u0441\u043e\u0432\u0441\u0435\u043c \u0434\u0440\u0443\u0433\u043e\u0439 \u0441\u0442\u0435\u043a.<\/p>\n<p>\u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u043f\u043e\u043a\u0430\u0436\u0443, \u0447\u0442\u043e \u0432 \u0441\u0432\u044f\u0437\u043a\u0435\u00a0<strong>redb.Route + redb.Tsak<\/strong>\u00a0\u044d\u0442\u043e \u0431\u0443\u043a\u0432\u0430\u043b\u044c\u043d\u043e \u043e\u0434\u0438\u043d \u0438 \u0442\u043e\u0442 \u0436\u0435 \u043a\u043e\u0434. \u041c\u044b:<\/p>\n<ol>\n<li>\n<p>\u043d\u0430\u043f\u0438\u0448\u0435\u043c\u00a0<strong>\u0434\u0432\u0430 \u043f\u0440\u043e\u0441\u0442\u0435\u0439\u0448\u0438\u0445 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430<\/strong>\u00a0(POST \u2014 \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c, GET \u2014 \u0434\u043e\u0441\u0442\u0430\u0442\u044c \u043f\u043e \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0443) \u043d\u0430 redb.Route;<\/p>\n<\/li>\n<li>\n<p>\u0441\u0434\u0435\u043b\u0430\u0435\u043c\u00a0<strong>\u0441\u0432\u043e\u0439 \u0432\u043e\u0440\u043a\u0435\u0440 \u0434\u043b\u044f \u043e\u0442\u043b\u0430\u0434\u043a\u0438<\/strong>\u00a0\u2014 \u043e\u0431\u044b\u0447\u043d\u043e\u0435 \u043a\u043e\u043d\u0441\u043e\u043b\u044c\u043d\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043c\u043e\u0436\u043d\u043e \u0433\u043e\u043d\u044f\u0442\u044c \u043f\u043e\u0434 F5 \u0441 \u0442\u043e\u0447\u043a\u0430\u043c\u0438 \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u0430;<\/p>\n<\/li>\n<li>\n<p>\u0430 \u043f\u043e\u0442\u043e\u043c,\u00a0<strong>\u043d\u0435 \u043c\u0435\u043d\u044f\u044f \u043d\u0438 \u0441\u0442\u0440\u043e\u0447\u043a\u0438 \u0432 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430\u0445<\/strong>, \u0443\u043f\u0430\u043a\u0443\u0435\u043c \u0432\u0441\u0451 \u0432 \u043c\u043e\u0434\u0443\u043b\u044c \u0438 \u0437\u0430\u043a\u0438\u043d\u0435\u043c \u0432 Tsak \u2014 \u0438 \u0442\u043e\u0442 \u0436\u0435 \u043a\u0440\u043e\u0448\u0435\u0447\u043d\u044b\u0439 \u043f\u0440\u043e\u0435\u043a\u0442 \u043f\u043e\u043b\u0443\u0447\u0438\u0442 \u0434\u0430\u0448\u0431\u043e\u0440\u0434, hot-reload, \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430\u043c\u0438 \u0438 \u044d\u043d\u0442\u0435\u0440\u043f\u0440\u0430\u0439\u0437-\u0434\u0435\u043f\u043b\u043e\u0439 (\u0434\u043e\u043a\u0435\u0440\u043e\u043c \u0438 \u0431\u0435\u0437 \u0434\u043e\u043a\u0435\u0440\u0430).<\/p>\n<\/li>\n<\/ol>\n<p>\u041f\u0440\u043e\u0435\u043a\u0442 \u0446\u0435\u043b\u0438\u043a\u043e\u043c \u043b\u0435\u0436\u0438\u0442 \u0432 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438 (<code>redb.Route\/demos\/EchoWorkerDemo<\/code>), \u0438 \u044f \u0432\u0441\u0442\u0430\u0432\u043b\u044e \u0435\u0433\u043e \u0441\u044e\u0434\u0430 \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u2014 \u043c\u043e\u0436\u043d\u043e \u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u044f\u0442\u044c.<\/p>\n<blockquote>\n<p><strong>\u0426\u0438\u043a\u043b \u043f\u0440\u043e redb \u0438 redb.Route.<\/strong>\u00a0\u042d\u0442\u043e \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0435\u043d\u0438\u0435 \u0441\u0435\u0440\u0438\u0438, \u0441\u0432\u0435\u0436\u0438\u0435 \u0441\u0442\u0430\u0442\u044c\u0438 \u2014 \u0441\u0432\u0435\u0440\u0445\u0443:<\/p>\n<ul>\n<li>\n<p><a href=\"https:\/\/habr.com\/ru\/articles\/1054148\/\" rel=\"noopener noreferrer nofollow\">redb.Route \u2014 \u0443\u0445\u043e\u0434\u0438\u043c \u043e\u0442 MassTransit, \u0438\u0434\u0451\u043c \u043a Apache Camel: Kafka, Scatter\u2011Gather \u0438 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/habr.com\/ru\/articles\/1049222\/\" rel=\"noopener noreferrer nofollow\">Apache Camel \u043f\u043e\u0434 .NET: HTTP-\u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440 \u0431\u0435\u0437 <\/a><a href=\"http:\/\/ASP.NET\" rel=\"noopener noreferrer nofollow\">ASP.NET<\/a><a href=\"https:\/\/habr.com\/ru\/articles\/1049222\/\" rel=\"noopener noreferrer nofollow\"> MVC + Content-Based Router<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/habr.com\/ru\/articles\/1042392\/\" rel=\"noopener noreferrer nofollow\">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<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/habr.com\/ru\/articles\/1042058\/\" rel=\"noopener noreferrer nofollow\">redb \u2014 \u0442\u0438\u043f\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0435 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435 \u0434\u043b\u044f .NET \u043f\u043e\u0432\u0435\u0440\u0445 Postgres\/MSSQL: \u0431\u0435\u0437 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0439, \u0431\u0435\u0437 Include, \u0441 \u043f\u043e\u043b\u043d\u044b\u043c LINQ<\/a><\/p>\n<\/li>\n<\/ul>\n<p>\u041f\u043e\u043b\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a \u2014 \u0432\u00a0<a href=\"https:\/\/habr.com\/ru\/users\/grelikt\/articles\/\" rel=\"noopener noreferrer nofollow\">\u043f\u0440\u043e\u0444\u0438\u043b\u0435<\/a>. \u0418\u0441\u0445\u043e\u0434\u043d\u0438\u043a\u0438:\u00a0<a href=\"https:\/\/github.com\/redbase-app\/\" rel=\"noopener noreferrer nofollow\">github.com\/redbase-app<\/a>. \u041f\u0440\u043e \u0441\u0430\u043c\u0443 \u0411\u0414 redb:\u00a0<a href=\"http:\/\/redb.ru\" rel=\"noopener noreferrer nofollow\">redb.ru<\/a>.<\/p>\n<p>\u0417\u0434\u0435\u0441\u044c \u2014 \u0432\u0432\u043e\u0434\u043d\u0430\u044f \u043f\u0440\u043e \u0432\u043e\u0440\u043a\u0435\u0440\u044b \u0438 \u0434\u0435\u043f\u043b\u043e\u0439; \u043f\u0440\u043e \u043a\u043b\u0430\u0441\u0442\u0435\u0440, \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u043e\u0440 \u0438 failover \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e.<\/p>\n<\/blockquote>\n<hr\/>\n<h3>\u0427\u0442\u043e \u0432\u043e\u043e\u0431\u0449\u0435 \u0434\u0435\u043b\u0430\u0435\u043c<\/h3>\n<p>\u041c\u0438\u043d\u0438-\u0441\u0435\u0440\u0432\u0438\u0441 \u0437\u0430\u043c\u0435\u0442\u043e\u043a. \u0414\u0432\u0435 \u0440\u0443\u0447\u043a\u0438 \u043d\u0430 \u043e\u0434\u043d\u043e\u043c HTTP-\u043f\u043e\u0440\u0442\u0443:<\/p>\n<ul>\n<li>\n<p><code>POST \/api\/notes<\/code>\u00a0\u0441 \u0442\u0435\u043b\u043e\u043c\u00a0<code>{\"tag\":\"work\",\"text\":\"hello\"}<\/code>\u00a0\u2014 \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0437\u0430\u043c\u0435\u0442\u043a\u0443 \u0432 \u0431\u0430\u0437\u0443 redb (\u043d\u0430 SQLite);<\/p>\n<\/li>\n<li>\n<p><code>GET \/api\/notes?tag=work<\/code>\u00a0\u2014 \u0432\u0435\u0440\u043d\u0443\u0442\u044c \u0437\u0430\u043c\u0435\u0442\u043a\u0438 \u0441 \u044d\u0442\u0438\u043c \u0442\u0435\u0433\u043e\u043c; \u0432\u044b\u0431\u043e\u0440\u043a\u0430 \u2014 \u0441\u0435\u0440\u0432\u0435\u0440\u043d\u044b\u043c \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u043c\u00a0<code>Where(...).ToListAsync()<\/code>, \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 \u0431\u0435\u0440\u0451\u0442\u0441\u044f \u043f\u0440\u044f\u043c\u043e \u0438\u0437 query-\u0441\u0442\u0440\u043e\u043a\u0438.<\/p>\n<\/li>\n<\/ul>\n<p>\u041d\u0438\u0447\u0435\u0433\u043e \u043b\u0438\u0448\u043d\u0435\u0433\u043e. \u0417\u0430\u0434\u0430\u0447\u0430 \u0441\u0442\u0430\u0442\u044c\u0438 \u043d\u0435 \u0432 \u0431\u0438\u0437\u043d\u0435\u0441-\u043b\u043e\u0433\u0438\u043a\u0435, \u0430 \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c\u00a0<strong>\u0436\u0438\u0437\u043d\u0435\u043d\u043d\u044b\u0439 \u0446\u0438\u043a\u043b<\/strong>: \u043a\u0430\u043a \u043e\u0434\u0438\u043d \u0438 \u0442\u043e\u0442 \u0436\u0435 \u043d\u0430\u0431\u043e\u0440 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043e\u0432 \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u0436\u0438\u0432\u0451\u0442 \u0432 \u043e\u0442\u043b\u0430\u0434\u043e\u0447\u043d\u043e\u0439 \u043a\u043e\u043d\u0441\u043e\u043b\u0438, \u0430 \u043f\u043e\u0442\u043e\u043c \u2014 \u0432 \u044d\u043d\u0442\u0435\u0440\u043f\u0440\u0430\u0439\u0437-\u0440\u0430\u043d\u0442\u0430\u0439\u043c\u0435.<\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/082\/f30\/bdb\/082f30bdbe429d0d3a63bc81c2a73a36.png\" alt=\"redb.tsak\" title=\"redb.tsak\" width=\"1331\" height=\"523\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/082\/f30\/bdb\/082f30bdbe429d0d3a63bc81c2a73a36.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/082\/f30\/bdb\/082f30bdbe429d0d3a63bc81c2a73a36.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>redb.tsak<\/figcaption><\/div>\n<\/figure>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/366\/548\/170\/3665481703c8a26e55cd67e4bf9db18f.png\" alt=\"redb.trsak.web logs\" title=\"redb.trsak.web logs\" width=\"1701\" height=\"1397\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/366\/548\/170\/3665481703c8a26e55cd67e4bf9db18f.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/366\/548\/170\/3665481703c8a26e55cd67e4bf9db18f.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>redb.trsak.web logs<\/figcaption><\/div>\n<\/figure>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/aab\/1c4\/be5\/aab1c4be5d6b9c315f7941b96369fab5.png\" alt=\"redb.tsak.web dashboard\" title=\"redb.tsak.web dashboard\" width=\"1713\" height=\"1400\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/aab\/1c4\/be5\/aab1c4be5d6b9c315f7941b96369fab5.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/aab\/1c4\/be5\/aab1c4be5d6b9c315f7941b96369fab5.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>redb.tsak.web dashboard<\/figcaption><\/div>\n<\/figure>\n<hr\/>\n<h3>\u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430: \u0434\u0432\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430, \u043e\u0434\u043d\u0430 \u0442\u043e\u0447\u043a\u0430 \u0432\u0445\u043e\u0434\u0430<\/h3>\n<p>\u0420\u0430\u0441\u043a\u043b\u0430\u0434\u043a\u0430 \u0442\u0430\u043a\u0430\u044f:<\/p>\n<pre><code>EchoWorkerDemo\/\u251c\u2500 EchoModule\/            &lt;- \u043f\u0440\u043e\u0435\u043a\u0442 1: \u043c\u043e\u0434\u0443\u043b\u044c (class library \u2192 EchoModule.tpkg)\u2502  \u251c\u2500 InitRoute.cs        &lt;- main(IRouteContext): \u0434\u0432\u0430 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430 + \u043a\u043b\u0430\u0441\u0441 Note\u2502  \u251c\u2500 manifest.json       &lt;- { Name, Version, EntryPoints: [\"EchoModule.dll\"] }\u2502  \u251c\u2500 EchoModule.config.json  &lt;- \u0438\u043c\u044f \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 (ContextName) + AutoStart\u2502  \u2514\u2500 EchoModule.csproj   &lt;- \u0441\u0441\u044b\u043b\u043a\u0438 \u043d\u0430 \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440\u044b + \u0442\u0430\u0440\u0433\u0435\u0442 PackTpkg\u2514\u2500 EchoWorker\/            &lt;- \u043f\u0440\u043e\u0435\u043a\u0442 2: \u043e\u0442\u043b\u0430\u0434\u043e\u0447\u043d\u044b\u0439 \u0445\u043e\u0441\u0442 (exe)   \u251c\u2500 Program.cs          &lt;- redb \u043d\u0430 SQLite + \u0432\u044b\u0437\u043e\u0432 InitRoute.main + Start   \u2514\u2500 EchoWorker.csproj<\/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\u0434\u0435\u044f, \u0432\u043e\u043a\u0440\u0443\u0433 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0432\u0441\u0451 \u0432\u0435\u0440\u0442\u0438\u0442\u0441\u044f:\u00a0<strong>\u0442\u043e\u0447\u043a\u0430 \u0432\u0445\u043e\u0434\u0430 \u043e\u0434\u043d\u0430 \u2014\u00a0<\/strong><code><strong>InitRoute.main(IRouteContext)<\/strong><\/code>. \u0415\u0451 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442 \u0438 \u043d\u0430\u0448 \u043e\u0442\u043b\u0430\u0434\u043e\u0447\u043d\u044b\u0439 \u0432\u043e\u0440\u043a\u0435\u0440, \u0438 \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0438\u0439 Tsak-\u0440\u0430\u043d\u0442\u0430\u0439\u043c. \u041a\u043e\u0434 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043e\u0432 \u0436\u0438\u0432\u0451\u0442 \u0440\u043e\u0432\u043d\u043e \u0432 \u043e\u0434\u043d\u043e\u043c \u043c\u0435\u0441\u0442\u0435 \u2014 \u0432\u00a0<code>EchoModule<\/code>. \u041e\u0442\u043b\u0430\u0434\u043e\u0447\u043d\u044b\u0439\u00a0<code>EchoWorker<\/code>\u00a0\u2014 \u044d\u0442\u043e \u043f\u0440\u043e\u0441\u0442\u043e \u00ab\u043e\u0431\u0432\u044f\u0437\u043a\u0430\u00bb, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043f\u043e\u0434\u043d\u0438\u043c\u0430\u0435\u0442 \u0431\u0430\u0437\u0443 \u0438 \u0434\u0451\u0440\u0433\u0430\u0435\u0442 \u0442\u0443 \u0436\u0435\u00a0<code>main<\/code>.<\/p>\n<p>\u041f\u043e\u0447\u0435\u043c\u0443 \u0434\u0432\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430, \u0430 \u043d\u0435 \u043e\u0434\u0438\u043d? \u0422\u0435\u0445\u043d\u0438\u0447\u0435\u0441\u043a\u0438 \u043c\u043e\u0436\u043d\u043e \u0438 \u0432 \u043e\u0434\u0438\u043d (exe \u0442\u043e\u0436\u0435 \u0434\u0430\u0451\u0442 DLL, \u0430 Tsak \u0438\u0449\u0435\u0442\u00a0<code>InitRoute.main<\/code>\u00a0\u0440\u0435\u0444\u043b\u0435\u043a\u0441\u0438\u0435\u0439 \u0432 \u043b\u044e\u0431\u043e\u0439 \u0441\u0431\u043e\u0440\u043a\u0435). \u041d\u043e \u0434\u043b\u044f \u043d\u0430\u0433\u043b\u044f\u0434\u043d\u043e\u0441\u0442\u0438 \u0434\u0432\u0435 \u0440\u043e\u043b\u0438 \u043b\u0443\u0447\u0448\u0435 \u0440\u0430\u0437\u0432\u0435\u0441\u0442\u0438:\u00a0<code><strong>EchoModule<\/strong><\/code><strong>\u00a0\u2014 \u044d\u0442\u043e \u0442\u043e, \u0447\u0442\u043e \u043c\u044b \u043e\u0442\u0433\u0440\u0443\u0436\u0430\u0435\u043c<\/strong>\u00a0(\u043f\u0430\u043a\u0443\u0435\u0442\u0441\u044f \u0432\u00a0<code>.tpkg<\/code>), \u0430\u00a0<code><strong>EchoWorker<\/strong><\/code><strong>\u00a0\u2014 \u0442\u043e, \u0447\u0435\u043c \u043e\u0442\u043b\u0430\u0436\u0438\u0432\u0430\u0435\u043c<\/strong>. \u041c\u043e\u0434\u0443\u043b\u044c \u043d\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u043d\u0438 \u0441\u0442\u0440\u043e\u0447\u043a\u0438 \u0445\u043e\u0441\u0442-\u043a\u043e\u0434\u0430 \u2014 \u0438 \u044d\u0442\u043e \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e: \u0432 \u043f\u0440\u043e\u0434\u0435 \u0431\u0430\u0437\u0443 \u0435\u043c\u0443 \u0434\u0430\u0441\u0442 Tsak.<\/p>\n<hr\/>\n<h3>\u041f\u0440\u043e\u0435\u043a\u0442 1: \u043c\u043e\u0434\u0443\u043b\u044c \u0441 \u0434\u0432\u0443\u043c\u044f \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430\u043c\u0438<\/h3>\n<p>\u0412\u043e\u0442 \u043e\u043d \u0446\u0435\u043b\u0438\u043a\u043e\u043c \u2014\u00a0<code>EchoModule\/InitRoute.cs<\/code>:<\/p>\n<pre><code>using System.Text.Json;using redb.Core;                          \/\/ IRedbService, Query, SaveAsync, SyncSchemeAsyncusing redb.Core.Attributes;               \/\/ RedbSchemeusing redb.Core.Models.Entities;          \/\/ RedbObject&lt;T&gt;using redb.Route.Abstractions;            \/\/ IRouteContext, IExchangeusing redb.Route.Core;                    \/\/ RouteContextusing redb.Route.Http;                    \/\/ HttpComponent, SharedHttpServerManagerusing redb.Route.RedbCore.Extensions;     \/\/ ProcessWithRedb, GetRedbServicenamespace EchoModule;\/\/\/ &lt;summary&gt;\/\/\/ Tsak module entry point.\/\/\/ The worker discovers it by convention \u2014 a public static class named InitRoute\/\/\/ with a public static main(IRouteContext) \u2014 and calls it once when the module\/\/\/ loads. The debug host (EchoWorker\/Program.cs) calls the very same method, so the\/\/\/ route code below lives in exactly one place.\/\/\/\/\/\/ Two minimal endpoints on the shared HTTP server (port 5099), backed by redb\/SQLite:\/\/\/   POST \/api\/notes   body {\"tag\":\"work\",\"text\":\"hello\"}   -&gt; save one note\/\/\/   GET  \/api\/notes?tag=work                               -&gt; list notes with that tag\/\/\/ &lt;\/summary&gt;public static class InitRoute{    private static readonly JsonSerializerOptions Json = new() { PropertyNameCaseInsensitive = true };    public static IRouteContext main(IRouteContext context)    {        \/\/ redb schema for Note. Idempotent \u2014 safe to call every load. The worker        \/\/ (or the debug host) has already brought redb + SQLite up by now.        context.GetRedbService().SyncSchemeAsync&lt;Note&gt;().GetAwaiter().GetResult();        \/\/ One shared HTTP server; both routes below bind to it.        context.AddComponent(new HttpComponent { ServerManager = new SharedHttpServerManager() });        ((RouteContext)context).AddRoutes(r =&gt;        {            \/\/ --- POST \/api\/notes \u2014 save one note ---            r.From(\"http:0.0.0.0:5099\/api\/notes?inOut=true&amp;methods=POST\")                .RouteId(\"notes-post\")                .ConvertBody&lt;string&gt;()                       \/\/ HTTP body -&gt; string (JSON)                .ProcessWithRedb(async (db, ex, ct) =&gt;                {                    var note = JsonSerializer.Deserialize&lt;Note&gt;(ex.In.Body?.ToString() ?? \"{}\", Json) ?? new Note();                    var obj = new RedbObject&lt;Note&gt; { name = $\"note:{note.Tag}\", Props = note };                    await db.SaveAsync(obj);                 \/\/ one insert into redb (SQLite)                    Reply(ex, new { saved = true, id = obj.id });                }).Log(\"Save ${body}\");            \/\/ --- GET \/api\/notes?tag=work \u2014 list by tag ---            r.From(\"http:0.0.0.0:5099\/api\/notes?inOut=true&amp;methods=GET\")                .RouteId(\"notes-get\")                .ProcessWithRedb(async (db, ex, ct) =&gt;                {                    \/\/ ?tag=... arrives as the header redbHttp.QueryParam.tag                    var tag = ex.In.Headers.TryGetValue(\"redbHttp.QueryParam.tag\", out var t)                        ? t?.ToString() ?? \"\"                        : \"\";                    \/\/ Server-side filter: the GET parameter goes straight into Where(...).                    var found = await db.Query&lt;Note&gt;()                        .Where(n =&gt; n.Tag == tag)                        .ToListAsync();                    Reply(ex, found.Select(o =&gt; new { o.Props.Tag, o.Props.Text }));                }).Log(\"Load ${header.redbHttp.QueryParam.tag}\");        });        return context;    }    \/\/ inOut=true -&gt; whatever the body is at the end becomes the HTTP response.    private static void Reply(IExchange ex, object body)    {        ex.In.ContentType = \"application\/json\";        ex.In.Body = JsonSerializer.Serialize(body);    }}\/\/\/ &lt;summary&gt;Persisted note. [RedbScheme] marks the class as a redb schema.&lt;\/summary&gt;[RedbScheme]public sealed class Note{    public string Tag { get; set; } = \"\";    public string Text { get; set; } = \"\";}<\/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>\u0420\u0430\u0437\u0431\u0435\u0440\u0451\u043c \u043f\u043e \u043a\u043e\u0441\u0442\u043e\u0447\u043a\u0430\u043c \u2014 \u0442\u0443\u0442 \u043a\u0430\u0436\u0434\u0430\u044f \u0441\u0442\u0440\u043e\u0447\u043a\u0430 \u0447\u0442\u043e-\u0442\u043e \u0437\u043d\u0430\u0447\u0438\u0442.<\/p>\n<h4>\u0422\u043e\u0447\u043a\u0430 \u0432\u0445\u043e\u0434\u0430\u00a0main<\/h4>\n<pre><code>public static IRouteContext main(IRouteContext 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>\u042d\u0442\u043e \u0438 \u0435\u0441\u0442\u044c \u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442 \u043c\u043e\u0434\u0443\u043b\u044f Tsak. \u041d\u0438\u043a\u0430\u043a\u0438\u0445 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u043e\u0432, \u043d\u0438\u043a\u0430\u043a\u0438\u0445 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u043e\u0432 \u2014\u00a0<strong>\u0441\u043e\u0433\u043b\u0430\u0448\u0435\u043d\u0438\u0435 \u043e\u0431 \u0438\u043c\u0435\u043d\u0430\u0445<\/strong>: \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0439 \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u043a\u043b\u0430\u0441\u0441\u00a0<code>InitRoute<\/code>, \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0439 \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u043c\u0435\u0442\u043e\u0434\u00a0<code>main<\/code>, \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u044e\u0449\u0438\u0439\u00a0<code>IRouteContext<\/code>. Tsak \u043f\u0440\u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0435 \u0441\u0431\u043e\u0440\u043a\u0438 \u0441\u043a\u0430\u043d\u0438\u0440\u0443\u0435\u0442 \u0435\u0451 \u0440\u0435\u0444\u043b\u0435\u043a\u0441\u0438\u0435\u0439, \u043d\u0430\u0445\u043e\u0434\u0438\u0442 \u044d\u0442\u043e\u0442 \u043c\u0435\u0442\u043e\u0434 \u0438 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442 \u0435\u0433\u043e, \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u044f \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442. \u0412\u0441\u0451, \u0447\u0442\u043e \u0432\u044b \u043d\u0430\u0432\u0435\u0441\u0438\u0442\u0435 \u043d\u0430\u00a0<code>context<\/code>\u00a0\u2014 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b, \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u044b, \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u0438 \u2014 \u0441\u0442\u0430\u043d\u0435\u0442 \u0447\u0430\u0441\u0442\u044c\u044e \u0440\u0430\u043d\u0442\u0430\u0439\u043c\u0430.<\/p>\n<p>\u0412\u0430\u0436\u043d\u043e: \u044d\u0442\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0438 \u0432 \u043e\u0442\u043b\u0430\u0434\u043e\u0447\u043d\u043e\u043c \u0432\u043e\u0440\u043a\u0435\u0440\u0435. \u0422\u0430\u043c \u043c\u044b\u00a0<strong>\u0441\u0430\u043c\u0438<\/strong>\u00a0\u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c\u00a0<code>RouteContext<\/code>\u00a0\u0438\u00a0<strong>\u0441\u0430\u043c\u0438<\/strong>\u00a0\u0432\u044b\u0437\u043e\u0432\u0435\u043c\u00a0<code>InitRoute.main(ctx)<\/code>. \u041e\u0434\u0438\u043d \u0438 \u0442\u043e\u0442 \u0436\u0435 \u043c\u0435\u0442\u043e\u0434, \u0434\u0432\u0435 \u0441\u0440\u0435\u0434\u044b \u0437\u0430\u043f\u0443\u0441\u043a\u0430.<\/p>\n<h4>\u0421\u0445\u0435\u043c\u0430 \u0434\u0430\u043d\u043d\u044b\u0445<\/h4>\n<pre><code>context.GetRedbService().SyncSchemeAsync&lt;Note&gt;().GetAwaiter().GetResult();<\/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>Note<\/code>\u00a0\u043f\u043e\u043c\u0435\u0447\u0435\u043d \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u043e\u043c\u00a0<code>[RedbScheme]<\/code>\u00a0\u2014 \u0437\u043d\u0430\u0447\u0438\u0442, \u044d\u0442\u043e persistable-\u043a\u043b\u0430\u0441\u0441 redb.\u00a0<code>SyncSchemeAsync&lt;Note&gt;()<\/code>\u00a0\u0437\u0430\u0432\u043e\u0434\u0438\u0442 \u0434\u043b\u044f \u043d\u0435\u0433\u043e \u0441\u0445\u0435\u043c\u0443 (\u0438\u0434\u0435\u043c\u043f\u043e\u0442\u0435\u043d\u0442\u043d\u043e \u2014 \u0432\u044b\u0437\u044b\u0432\u0430\u0442\u044c \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u0438 \u043a\u0430\u0436\u0434\u043e\u0439 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0435).\u00a0<code>GetRedbService()<\/code>\u00a0\u0434\u043e\u0441\u0442\u0430\u0451\u0442\u00a0<code>IRedbService<\/code>\u00a0\u0438\u0437 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430: \u0432 Tsak \u0431\u0430\u0437\u0443 \u0443\u0436\u0435 \u043f\u043e\u0434\u043d\u044f\u043b\u0438 \u0434\u043e \u0432\u044b\u0437\u043e\u0432\u0430 \u043c\u043e\u0434\u0443\u043b\u044f, \u0432 \u043e\u0442\u043b\u0430\u0434\u043e\u0447\u043d\u043e\u043c \u0432\u043e\u0440\u043a\u0435\u0440\u0435 \u2014 \u043c\u044b \u043f\u043e\u0434\u043d\u0438\u043c\u0435\u043c \u0435\u0451 \u0441\u0430\u043c\u0438 \u0434\u043e\u00a0<code>main<\/code>. \u0412 \u043e\u0431\u043e\u0438\u0445 \u0441\u043b\u0443\u0447\u0430\u044f\u0445 \u043a \u043c\u043e\u043c\u0435\u043d\u0442\u0443 \u0432\u044b\u0437\u043e\u0432\u0430 redb \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d.<\/p>\n<p>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435: \u043c\u043e\u0434\u0443\u043b\u044c\u00a0<strong>\u043d\u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0443\u0435\u0442 \u0431\u0430\u0437\u0443<\/strong>. \u041e\u043d \u043d\u0435 \u0437\u043d\u0430\u0435\u0442 \u0438 \u043d\u0435 \u0434\u043e\u043b\u0436\u0435\u043d \u0437\u043d\u0430\u0442\u044c, SQLite \u0442\u0430\u043c, Postgres \u0438\u043b\u0438 MSSQL. \u041e\u043d \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u0440\u043e\u0441\u0438\u0442\u00a0<code>IRedbService<\/code>\u00a0\u0443 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430. \u041f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440 \u2014 \u0437\u0430\u0431\u043e\u0442\u0430 \u0445\u043e\u0441\u0442\u0430.<\/p>\n<h4>\u041e\u0434\u0438\u043d HTTP-\u0441\u0435\u0440\u0432\u0435\u0440, \u0434\u0432\u0430 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430<\/h4>\n<pre><code>context.AddComponent(new HttpComponent { ServerManager = new SharedHttpServerManager() });<\/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>SharedHttpServerManager<\/code>\u00a0\u2014 \u044d\u0442\u043e \u043e\u0431\u0449\u0438\u0439 HTTP-\u0441\u0435\u0440\u0432\u0435\u0440: \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043e\u0432 \u043c\u043e\u0433\u0443\u0442 \u0432\u0438\u0441\u0435\u0442\u044c \u043d\u0430 \u043e\u0434\u043d\u043e\u043c \u043f\u043e\u0440\u0442\u0443. \u0423 \u043d\u0430\u0441 \u043e\u0431\u0430 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430 \u2014 \u043d\u0430\u00a0<code>5099<\/code>.<\/p>\n<pre><code>r.From(\"http:0.0.0.0:5099\/api\/notes?inOut=true&amp;methods=POST\")r.From(\"http:0.0.0.0:5099\/api\/notes?inOut=true&amp;methods=GET\")<\/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>\u041e\u0431\u0430 \u0441\u043b\u0443\u0448\u0430\u044e\u0442\u00a0<strong>\u043e\u0434\u0438\u043d \u0438 \u0442\u043e\u0442 \u0436\u0435 \u043f\u0443\u0442\u044c<\/strong>\u00a0<code>\/api\/notes<\/code>, \u043d\u043e \u0440\u0430\u0437\u044a\u0435\u0437\u0436\u0430\u044e\u0442\u0441\u044f \u043f\u043e \u043c\u0435\u0442\u043e\u0434\u0443 \u0447\u0435\u0440\u0435\u0437\u00a0<code>?methods=POST<\/code>\u00a0\/\u00a0<code>?methods=GET<\/code>. \u0421\u0435\u0440\u0432\u0435\u0440 \u043c\u0430\u0442\u0447\u0438\u0442 \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0439 \u0437\u0430\u043f\u0440\u043e\u0441 \u043f\u043e \u043f\u0430\u0440\u0435 \u00ab\u043f\u0443\u0442\u044c + \u043c\u0435\u0442\u043e\u0434\u00bb: POST \u0443\u0435\u0434\u0435\u0442 \u0432 \u043f\u0435\u0440\u0432\u044b\u0439 \u043c\u0430\u0440\u0448\u0440\u0443\u0442, GET \u2014 \u0432\u043e \u0432\u0442\u043e\u0440\u043e\u0439, \u0430, \u0441\u043a\u0430\u0436\u0435\u043c,\u00a0<code>PUT \/api\/notes<\/code>\u00a0\u0432\u0435\u0440\u043d\u0451\u0442 \u0447\u0435\u0441\u0442\u043d\u044b\u0439\u00a0<strong>405 Method Not Allowed<\/strong>\u00a0(\u043f\u0443\u0442\u044c \u0435\u0441\u0442\u044c, \u043c\u0435\u0442\u043e\u0434 \u043d\u0435 \u0442\u043e\u0442).\u00a0<code>inOut=true<\/code>\u00a0\u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442 \u00ab\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441-\u043e\u0442\u0432\u0435\u0442\u00bb: \u0442\u043e, \u0447\u0442\u043e \u043e\u043a\u0430\u0436\u0435\u0442\u0441\u044f \u0432 \u0442\u0435\u043b\u0435 \u043e\u0431\u043c\u0435\u043d\u0430 \u043d\u0430 \u0432\u044b\u0445\u043e\u0434\u0435 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430, \u0441\u0442\u0430\u043d\u0435\u0442 HTTP-\u043e\u0442\u0432\u0435\u0442\u043e\u043c.<\/p>\n<h4>POST: \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c<\/h4>\n<pre><code>.ConvertBody&lt;string&gt;().ProcessWithRedb(async (db, ex, ct) =&gt;{    var note = JsonSerializer.Deserialize&lt;Note&gt;(ex.In.Body?.ToString() ?? \"{}\", Json) ?? new Note();    var obj = new RedbObject&lt;Note&gt; { name = $\"note:{note.Tag}\", Props = note };    await db.SaveAsync(obj);    Reply(ex, new { saved = true, id = obj.id });}).Log(\"Save ${body}\");<\/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>ConvertBody&lt;string&gt;()<\/code>\u00a0\u043f\u0440\u0435\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0442\u0435\u043b\u043e HTTP-\u0437\u0430\u043f\u0440\u043e\u0441\u0430 (\u0431\u0430\u0439\u0442\u044b) \u0432 \u0441\u0442\u0440\u043e\u043a\u0443.\u00a0<code>ProcessWithRedb<\/code>\u00a0\u2014 \u044d\u0442\u043e \u0448\u0430\u0433 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438, \u0432 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 redb.Route \u0441\u0430\u043c \u043f\u0440\u043e\u043a\u0438\u0434\u044b\u0432\u0430\u0435\u0442\u00a0<code>IRedbService<\/code>\u00a0(<code>db<\/code>): \u043f\u043e\u0434 \u043a\u0430\u043f\u043e\u0442\u043e\u043c \u043e\u043d \u0431\u0435\u0440\u0451\u0442 per-exchange DI-\u0441\u043a\u043e\u0443\u043f, \u0435\u0441\u043b\u0438 \u0442\u043e\u0442 \u0435\u0441\u0442\u044c, \u0438\u043d\u0430\u0447\u0435 \u2014 \u0441\u0438\u043d\u0433\u043b\u0442\u043e\u043d \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430. \u0414\u0430\u043b\u044c\u0448\u0435 \u2014 \u043e\u0431\u044b\u0447\u043d\u044b\u0439 C#: \u0434\u0435\u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043b\u0438 JSON \u0432\u00a0<code>Note<\/code>, \u0437\u0430\u0432\u0435\u0440\u043d\u0443\u043b\u0438 \u0432\u00a0<code>RedbObject&lt;Note&gt;<\/code>, \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u043b\u0438 \u043e\u0434\u043d\u0438\u043c\u00a0<code>SaveAsync<\/code>, \u0432\u0435\u0440\u043d\u0443\u043b\u0438\u00a0<code>{ saved, id }<\/code>.<\/p>\n<p>\u0425\u0432\u043e\u0441\u0442\u0438\u043a\u00a0<code>.Log(\"Save ${body}\")<\/code>\u00a0\u2014 \u044d\u0442\u043e \u0448\u0430\u0433 \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0441 \u043f\u043e\u0434\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u043e\u0439:\u00a0<code>${body}<\/code>\u00a0\u043f\u043e\u0434\u0441\u0442\u0430\u0432\u0438\u0442 \u0442\u0435\u043a\u0443\u0449\u0435\u0435 \u0442\u0435\u043b\u043e \u043e\u0431\u043c\u0435\u043d\u0430. \u0421\u0438\u043d\u0442\u0430\u043a\u0441\u0438\u0441\u00a0<code>${...}<\/code>\u00a0\u0432 redb.Route \u0443\u043c\u0435\u0435\u0442 \u0434\u043e\u0441\u0442\u0430\u0432\u0430\u0442\u044c\u00a0<code>body<\/code>, \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438 (<code>${header.X}<\/code>) \u0438 \u043f\u0440\u043e\u0447\u0435\u0435 \u2014 \u0443\u0434\u043e\u0431\u043d\u043e \u0434\u043b\u044f \u0442\u0440\u0430\u0441\u0441\u0438\u0440\u043e\u0432\u043a\u0438 \u043f\u0440\u044f\u043c\u043e \u0432 DSL.<\/p>\n<h4>GET: \u0434\u043e\u0441\u0442\u0430\u0442\u044c \u043f\u043e \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0443<\/h4>\n<pre><code>var tag = ex.In.Headers.TryGetValue(\"redbHttp.QueryParam.tag\", out var t)    ? t?.ToString() ?? \"\"    : \"\";var found = await db.Query&lt;Note&gt;()    .Where(n =&gt; n.Tag == tag)    .ToListAsync();Reply(ex, found.Select(o =&gt; new { o.Props.Tag, o.Props.Text }));<\/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>Query-\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b HTTP redb.Route \u0440\u0430\u0441\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u0435\u0442 \u043f\u043e \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0430\u043c \u0441 \u043f\u0440\u0435\u0444\u0438\u043a\u0441\u043e\u043c\u00a0<code>redbHttp.QueryParam.<\/code>\u00a0\u2014 \u0442\u0430\u043a \u0447\u0442\u043e\u00a0<code>?tag=work<\/code>\u00a0\u043f\u0440\u0438\u0435\u0437\u0436\u0430\u0435\u0442 \u043a\u0430\u043a \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a\u00a0<code>redbHttp.QueryParam.tag<\/code>. \u0414\u043e\u0441\u0442\u0430\u0451\u043c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0438 \u043a\u043b\u0430\u0434\u0451\u043c \u0435\u0433\u043e \u043f\u0440\u044f\u043c\u043e \u0432 \u0441\u0435\u0440\u0432\u0435\u0440\u043d\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441:<\/p>\n<pre><code>db.Query&lt;Note&gt;().Where(n =&gt; n.Tag == tag).ToListAsync()<\/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 \u043d\u0435 \u0432\u044b\u0431\u043e\u0440\u043a\u0430 \u00ab\u0432\u0441\u0451 \u0432 \u043f\u0430\u043c\u044f\u0442\u044c, \u043f\u043e\u0442\u043e\u043c \u043e\u0442\u0444\u0438\u043b\u044c\u0442\u0440\u043e\u0432\u0430\u0442\u044c\u00bb \u2014 \u044d\u0442\u043e \u0437\u0430\u043f\u0440\u043e\u0441 \u043d\u0430 \u0441\u0442\u043e\u0440\u043e\u043d\u0435 \u0431\u0430\u0437\u044b: \u043b\u044f\u043c\u0431\u0434\u0430\u00a0<code>n =&gt; n.Tag == tag<\/code>\u00a0\u043a\u043e\u043c\u043f\u0438\u043b\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0432 \u0443\u0441\u043b\u043e\u0432\u0438\u0435 \u043f\u043e \u043f\u043e\u043b\u044e\u00a0<code>Note.Tag<\/code>. \u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u2014 \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u044f\u00a0<code>RedbObject&lt;Note&gt;<\/code>, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043a \u043f\u043e\u043b\u044f\u043c \u043e\u0431\u0440\u0430\u0449\u0430\u0435\u043c\u0441\u044f \u0447\u0435\u0440\u0435\u0437\u00a0<code>.Props<\/code>.<\/p>\n<p>\u0425\u0432\u043e\u0441\u0442\u00a0<code>.Log(\"Load ${header.redbHttp.QueryParam.tag}\")<\/code>\u00a0\u043b\u043e\u0433\u0438\u0440\u0443\u0435\u0442, \u043f\u043e \u043a\u0430\u043a\u043e\u043c\u0443 \u0442\u0435\u0433\u0443 \u043f\u0440\u0438\u0448\u0451\u043b \u0437\u0430\u043f\u0440\u043e\u0441.<\/p>\n<p>\u0412\u043e\u0442 \u0438 \u0432\u0435\u0441\u044c \u043c\u043e\u0434\u0443\u043b\u044c. \u0414\u0432\u0430 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430, \u043a\u043b\u0430\u0441\u0441 \u0434\u0430\u043d\u043d\u044b\u0445, \u043d\u043e\u043b\u044c \u0438\u043d\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b. \u0422\u0435\u043f\u0435\u0440\u044c \u043d\u0430\u0443\u0447\u0438\u043c\u0441\u044f \u0435\u0433\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c \u0438 \u043e\u0442\u043b\u0430\u0436\u0438\u0432\u0430\u0442\u044c.<\/p>\n<hr\/>\n<h3>\u041f\u0440\u043e\u0435\u043a\u0442 2: \u0441\u0432\u043e\u0439 \u0432\u043e\u0440\u043a\u0435\u0440 \u0434\u043b\u044f \u043e\u0442\u043b\u0430\u0434\u043a\u0438<\/h3>\n<p>\u0427\u0442\u043e\u0431\u044b \u0433\u043e\u043d\u044f\u0442\u044c \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u044b \u043f\u043e\u0434 \u043e\u0442\u043b\u0430\u0434\u0447\u0438\u043a\u043e\u043c, \u043d\u0435 \u043f\u043e\u0434\u043d\u0438\u043c\u0430\u044f \u043d\u0438\u043a\u0430\u043a\u043e\u0433\u043e Tsak, \u0441\u0434\u0435\u043b\u0430\u0435\u043c \u043a\u0440\u043e\u0448\u0435\u0447\u043d\u043e\u0435 \u043a\u043e\u043d\u0441\u043e\u043b\u044c\u043d\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435. \u041e\u043d\u043e \u043f\u043e\u0432\u0442\u043e\u0440\u044f\u0435\u0442 \u0440\u043e\u0432\u043d\u043e \u0442\u043e, \u0447\u0442\u043e \u0434\u0435\u043b\u0430\u0435\u0442 Tsak-\u0432\u043e\u0440\u043a\u0435\u0440 \u043f\u0440\u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0435 \u043c\u043e\u0434\u0443\u043b\u044f, \u043d\u043e \u0432 \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u043c \u043e\u0431\u044a\u0451\u043c\u0435. \u0412\u043e\u0442\u00a0<code>EchoWorker\/Program.cs<\/code>\u00a0\u0446\u0435\u043b\u0438\u043a\u043e\u043c:<\/p>\n<pre><code>\/\/ ============================================================================\/\/  EchoWorker \u2014 a debug host for the EchoModule Tsak module.\/\/\/\/  It reproduces, in the smallest possible way, what the Tsak worker does:\/\/    1) stand redb up on SQLite (Free tier \u2014 the worker's default),\/\/    2) create the redb system tables once,\/\/    3) hand a RouteContext to EchoModule.InitRoute.main \u2014 the SAME entry point\/\/       the worker calls, so no route code is duplicated here.\/\/\/\/  Run it, then (PowerShell \u2014 JSON in single quotes; cmd.exe needs \\\" escaping instead):\/\/    POST: curl.exe -X POST http:\/\/localhost:5099\/api\/notes -H \"Content-Type: application\/json\" -d '{\"tag\":\"work\",\"text\":\"hello\"}'\/\/    GET:  curl.exe \"http:\/\/localhost:5099\/api\/notes?tag=work\"\/\/ ============================================================================using Microsoft.Extensions.DependencyInjection;using Microsoft.Extensions.Logging;using redb.Core;                          \/\/ IRedbServiceusing redb.Core.Extensions;               \/\/ AddRedbusing redb.Core.Models.Configuration;     \/\/ PropsSaveStrategyusing redb.SQLite.Pro.Extensions;         \/\/ UseSqlite (tier-agnostic: AddRedb -&gt; Free)using redb.SQLite.Data;                   \/\/ SqliteDataSource.NativeExtensionPathusing redb.Route.Core;                    \/\/ RouteContextnamespace EchoWorker;public static class Program{    public static async Task Main(string[] args)    {        \/\/ Free SQLite needs the native redb extension. The packaged Tsak worker ships        \/\/ it; running from source we point at the one built under redb.SQLite\/native\/build.        SqliteDataSource.NativeExtensionPath ??= ResolveNativeExtension();        \/\/ DI: console logging + redb on SQLite (single-file DB next to the exe).        var services = new ServiceCollection();        services.AddLogging(b =&gt; b            .AddSimpleConsole(o =&gt; { o.SingleLine = true; o.TimestampFormat = \"HH:mm:ss \"; })            .SetMinimumLevel(LogLevel.Information));        services.AddRedb(o =&gt; o            .UseSqlite(\"Data Source=echo_demo.db\")            .Configure(c =&gt; c.PropsSaveStrategy = PropsSaveStrategy.DeleteInsert));        var sp = services.BuildServiceProvider();        \/\/ Create the redb system tables once (the worker does this on boot).        \/\/ ensureCreated: true builds the base tables on a fresh SQLite file.        await sp.GetRequiredService&lt;IRedbService&gt;().InitializeAsync(ensureCreated: true);        \/\/ Build a route context over that provider and call the module entry point.        var ctx = new RouteContext(sp, contextId: \"echo-worker\");        ctx.AddService(typeof(ILoggerFactory), sp.GetRequiredService&lt;ILoggerFactory&gt;());        EchoModule.InitRoute.main(ctx);       \/\/ &lt;- the exact method the Tsak worker calls        await ctx.Start();        Console.WriteLine();        Console.WriteLine(\"EchoWorker running: http:\/\/localhost:5099\/api\/notes\");        Console.WriteLine(\"  POST  {\\\"tag\\\":\\\"work\\\",\\\"text\\\":\\\"hello\\\"}   -&gt; save\");        Console.WriteLine(\"  GET   ?tag=work                          -&gt; list by tag\");        Console.WriteLine(\"Ctrl+C to exit.\");        Console.WriteLine();        var stop = new ManualResetEventSlim();        Console.CancelKeyPress += (_, e) =&gt; { e.Cancel = true; stop.Set(); };        stop.Wait();        await ctx.DisposeAsync();    }    \/\/ Walk up from the app dir to the repo's built Free SQLite native extension.    \/\/ Returns null when running from a packaged worker (it resolves the extension itself).    private static string? ResolveNativeExtension()    {        var suffix = OperatingSystem.IsWindows() ? \".dll\"                   : OperatingSystem.IsMacOS()   ? \".dylib\"                   : \".so\";        for (var dir = new DirectoryInfo(AppContext.BaseDirectory); dir != null; dir = dir.Parent)        {            var candidate = Path.Combine(dir.FullName, \"redb.SQLite\", \"native\", \"build\", \"redb\" + suffix);            if (File.Exists(candidate))                return candidate;        }        return null;    }}<\/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 \u0437\u0434\u0435\u0441\u044c \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442, \u043f\u043e \u0448\u0430\u0433\u0430\u043c.<\/p>\n<p><strong>1. \u041d\u0430\u0442\u0438\u0432\u043d\u043e\u0435 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435 SQLite.<\/strong>\u00a0redb \u043d\u0430 SQLite \u0432 \u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u043e\u043c \u0442\u0438\u0440\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u043d\u0430\u0442\u0438\u0432\u043d\u043e\u0435 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c\u043e\u0435 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435 (<code>redb.dll<\/code>\u00a0\/\u00a0<code>.so<\/code>\u00a0\/\u00a0<code>.dylib<\/code>) \u2014 \u0432 \u043d\u0451\u043c \u0436\u0438\u0432\u0451\u0442 \u0447\u0430\u0441\u0442\u044c \u043c\u0430\u0448\u0438\u043d\u0435\u0440\u0438\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432. \u0412 \u0443\u043f\u0430\u043a\u043e\u0432\u0430\u043d\u043d\u043e\u043c Tsak-\u0432\u043e\u0440\u043a\u0435\u0440\u0435 \u044d\u0442\u043e \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435 \u0435\u0434\u0435\u0442 \u0432 \u043a\u043e\u043c\u043f\u043b\u0435\u043a\u0442\u0435. \u0410 \u043a\u043e\u0433\u0434\u0430 \u043c\u044b \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u0438\u0437 \u0438\u0441\u0445\u043e\u0434\u043d\u0438\u043a\u043e\u0432, \u043d\u0430\u0434\u043e \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u043f\u0443\u0442\u044c \u043a \u0441\u043e\u0431\u0440\u0430\u043d\u043d\u043e\u043c\u0443 \u0431\u0438\u043d\u0430\u0440\u044e \u2014\u00a0<code>ResolveNativeExtension()<\/code>\u00a0\u043f\u0440\u043e\u0441\u0442\u043e \u0438\u0434\u0451\u0442 \u0432\u0432\u0435\u0440\u0445 \u043f\u043e \u0434\u0435\u0440\u0435\u0432\u0443 \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u043e\u0432 \u0438 \u0438\u0449\u0435\u0442\u00a0<code>redb.SQLite\/native\/build\/redb.dll<\/code>. \u041e\u0434\u043d\u0430 \u0447\u0435\u0441\u0442\u043d\u0430\u044f \u0434\u0435\u0442\u0430\u043b\u044c, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u0432 \u0441\u0442\u0430\u0442\u044c\u0435 \u043b\u0443\u0447\u0448\u0435 \u043d\u0435 \u043f\u0440\u044f\u0442\u0430\u0442\u044c: \u0431\u0435\u0437 \u044d\u0442\u043e\u0433\u043e \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u044f \u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u044b\u0439 SQLite-\u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440 \u043d\u0435 \u0437\u0430\u0432\u0435\u0434\u0451\u0442\u0441\u044f.<\/p>\n<p><strong>2. DI \u0438 \u0431\u0430\u0437\u0430.<\/strong>\u00a0\u041e\u0431\u044b\u0447\u043d\u044b\u0439\u00a0<code>ServiceCollection<\/code>: \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0432 \u043a\u043e\u043d\u0441\u043e\u043b\u044c +\u00a0<code>AddRedb(...).UseSqlite(\"Data Source=echo_demo.db\")<\/code>.\u00a0<code>UseSqlite<\/code>\u00a0\u0442\u0438\u0440-\u0430\u0433\u043d\u043e\u0441\u0442\u0438\u0447\u0435\u043d:\u00a0<code>AddRedb<\/code>\u00a0\u0434\u0430\u0451\u0442 Free,\u00a0<code>AddRedbPro<\/code>\u00a0\u2014 Pro; \u0442\u0438\u0440 \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u0431\u0435\u0437 \u043f\u0440\u0430\u0432\u043a\u0438\u00a0<code>using<\/code>-\u043e\u0432. \u042d\u0442\u043e \u0440\u043e\u0432\u043d\u043e \u0442\u043e\u0442 \u0436\u0435 \u0442\u0438\u0440 (Free\/SQLite), \u0447\u0442\u043e Tsak-\u0432\u043e\u0440\u043a\u0435\u0440 \u043f\u043e\u0434\u043d\u0438\u043c\u0430\u0435\u0442 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u2014 \u0442\u043e \u0435\u0441\u0442\u044c \u043e\u0442\u043b\u0430\u0434\u043a\u0430 \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u0435\u0442 \u0441 \u0431\u043e\u0435\u0432\u044b\u043c.<\/p>\n<p><strong>3. \u0421\u0438\u0441\u0442\u0435\u043c\u043d\u044b\u0435 \u0442\u0430\u0431\u043b\u0438\u0446\u044b.<\/strong>\u00a0<code>InitializeAsync(ensureCreated: true)<\/code>\u00a0\u0441\u043e\u0437\u0434\u0430\u0451\u0442 \u0431\u0430\u0437\u043e\u0432\u044b\u0435 \u0442\u0430\u0431\u043b\u0438\u0446\u044b redb \u043d\u0430 \u0441\u0432\u0435\u0436\u0435\u043c \u0444\u0430\u0439\u043b\u0435. \u0412 Tsak \u044d\u0442\u043e \u0434\u0435\u043b\u0430\u0435\u0442 \u0441\u0430\u043c \u0432\u043e\u0440\u043a\u0435\u0440 \u043d\u0430 \u0441\u0442\u0430\u0440\u0442\u0435; \u0432 \u043e\u0442\u043b\u0430\u0434\u043e\u0447\u043d\u043e\u043c \u0445\u043e\u0441\u0442\u0435 \u2014 \u0434\u0435\u043b\u0430\u0435\u043c \u043c\u044b.<\/p>\n<p><strong>4. \u041a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 \u0438 \u0432\u044b\u0437\u043e\u0432 \u043c\u043e\u0434\u0443\u043b\u044f.<\/strong>\u00a0\u0421\u043e\u0437\u0434\u0430\u0451\u043c\u00a0<code>RouteContext<\/code>\u00a0\u043f\u043e\u0432\u0435\u0440\u0445 \u043d\u0430\u0448\u0435\u0433\u043e \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u0430 \u0438 \u0437\u043e\u0432\u0451\u043c\u00a0<code>EchoModule.InitRoute.main(ctx)<\/code>.\u00a0<strong>\u042d\u0442\u043e \u0442\u0430 \u0436\u0435 \u0441\u0430\u043c\u0430\u044f \u0442\u043e\u0447\u043a\u0430 \u0432\u0445\u043e\u0434\u0430, \u0447\u0442\u043e \u0434\u0451\u0440\u043d\u0435\u0442 Tsak.<\/strong>\u00a0\u041d\u0438\u043a\u0430\u043a\u043e\u0433\u043e \u0434\u0443\u0431\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043e\u0432 \u2014 \u0432\u0435\u0441\u044c \u043a\u043e\u0434 \u0432 \u043c\u043e\u0434\u0443\u043b\u0435.<\/p>\n<p><strong>5. \u0421\u0442\u0430\u0440\u0442 \u0438 \u043e\u0436\u0438\u0434\u0430\u043d\u0438\u0435.<\/strong>\u00a0<code>ctx.Start()<\/code>\u00a0\u043f\u043e\u0434\u043d\u0438\u043c\u0430\u0435\u0442 HTTP-\u0441\u0435\u0440\u0432\u0435\u0440 \u043d\u0430 5099, \u0434\u0430\u043b\u044c\u0448\u0435 \u0432\u0438\u0441\u0438\u043c \u0434\u043e Ctrl+C.<\/p>\n<h4>\u0424\u0430\u0439\u043b\u044b \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432<\/h4>\n<p><code>EchoModule\/EchoModule.csproj<\/code>\u00a0\u2014 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 + \u0442\u0430\u0440\u0433\u0435\u0442 \u0443\u043f\u0430\u043a\u043e\u0432\u043a\u0438 \u0432\u00a0<code>.tpkg<\/code>:<\/p>\n<pre><code class=\"xml\">&lt;Project Sdk=\"Microsoft.NET.Sdk\"&gt;  &lt;!-- Project 1 of 2: the Tsak MODULE.       A class library. Its DLL is what gets packed into EchoModule.tpkg and       hot-loaded by the Tsak worker. It contains ONLY route code \u2014 no host,       no DB provider: the worker supplies redb + SQLite at runtime. --&gt;  &lt;PropertyGroup&gt;    &lt;TargetFramework&gt;net9.0&lt;\/TargetFramework&gt;    &lt;ImplicitUsings&gt;enable&lt;\/ImplicitUsings&gt;    &lt;Nullable&gt;enable&lt;\/Nullable&gt;    &lt;RootNamespace&gt;EchoModule&lt;\/RootNamespace&gt;    &lt;CopyLocalLockFileAssemblies&gt;true&lt;\/CopyLocalLockFileAssemblies&gt;  &lt;\/PropertyGroup&gt;  &lt;!-- The connectors the worker already ships in its shared Libs. We compile       against them but the .tpkg carries only EchoModule.dll (see PackTpkg). --&gt;  &lt;ItemGroup&gt;    &lt;ProjectReference Include=\"..\\..\\..\\src\\redb.Route\\redb.Route.csproj\" \/&gt;    &lt;ProjectReference Include=\"..\\..\\..\\src\\redb.Route.Core\\redb.Route.Core.csproj\" \/&gt;    &lt;ProjectReference Include=\"..\\..\\..\\src\\redb.Route.Http\\redb.Route.Http.csproj\" \/&gt;    &lt;ProjectReference Include=\"..\\..\\..\\..\\redb.Core\\redb.Core.csproj\" \/&gt;  &lt;\/ItemGroup&gt;  &lt;!-- After build: zip manifest.json + the module DLL into output\/EchoModule.tpkg. --&gt;  &lt;PropertyGroup&gt;    &lt;TsakModuleName&gt;EchoModule&lt;\/TsakModuleName&gt;  &lt;\/PropertyGroup&gt;  &lt;Target Name=\"PackTpkg\" AfterTargets=\"Build\"&gt;    &lt;PropertyGroup&gt;      &lt;_TpkgStaging&gt;$(IntermediateOutputPath)tpkg&lt;\/_TpkgStaging&gt;      &lt;_TpkgFile&gt;$(MSBuildThisFileDirectory)output\\$(TsakModuleName).tpkg&lt;\/_TpkgFile&gt;    &lt;\/PropertyGroup&gt;    &lt;RemoveDir Directories=\"$(_TpkgStaging)\" \/&gt;    &lt;MakeDir Directories=\"$(_TpkgStaging)\" \/&gt;    &lt;MakeDir Directories=\"$(MSBuildThisFileDirectory)output\" \/&gt;    &lt;Copy SourceFiles=\"$(MSBuildThisFileDirectory)manifest.json\" DestinationFolder=\"$(_TpkgStaging)\" \/&gt;    &lt;Copy SourceFiles=\"$(TargetPath)\" DestinationFolder=\"$(_TpkgStaging)\" \/&gt;    &lt;!-- {ModuleName}.config.json gives the module a named context (ContextName). --&gt;    &lt;Copy SourceFiles=\"$(MSBuildThisFileDirectory)$(TsakModuleName).config.json\"          DestinationFolder=\"$(_TpkgStaging)\"          Condition=\"Exists('$(MSBuildThisFileDirectory)$(TsakModuleName).config.json')\" \/&gt;    &lt;ZipDirectory SourceDirectory=\"$(_TpkgStaging)\" DestinationFile=\"$(_TpkgFile)\" Overwrite=\"true\" \/&gt;    &lt;Message Importance=\"high\" Text=\"Packed $(TsakModuleName) -&gt; $(_TpkgFile)\" \/&gt;  &lt;\/Target&gt;&lt;\/Project&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>EchoWorker\/EchoWorker.csproj<\/code>\u00a0\u2014 \u043a\u043e\u043d\u0441\u043e\u043b\u044c\u043d\u044b\u0439 exe, \u0441\u0441\u044b\u043b\u0430\u0435\u0442\u0441\u044f \u043d\u0430 \u043c\u043e\u0434\u0443\u043b\u044c:<\/p>\n<pre><code class=\"xml\">&lt;Project Sdk=\"Microsoft.NET.Sdk\"&gt;  &lt;!-- Project 2 of 2: the DEBUG HOST.       A tiny console app. It stands redb up on SQLite (the same Free tier the Tsak       worker uses by default), then calls EchoModule.InitRoute.main(ctx) \u2014 the exact       method the worker calls. Run\/F5 this to debug the route without Tsak. --&gt;  &lt;PropertyGroup&gt;    &lt;OutputType&gt;Exe&lt;\/OutputType&gt;    &lt;TargetFramework&gt;net9.0&lt;\/TargetFramework&gt;    &lt;ImplicitUsings&gt;enable&lt;\/ImplicitUsings&gt;    &lt;Nullable&gt;enable&lt;\/Nullable&gt;    &lt;RootNamespace&gt;EchoWorker&lt;\/RootNamespace&gt;  &lt;\/PropertyGroup&gt;  &lt;ItemGroup&gt;    &lt;ProjectReference Include=\"..\\EchoModule\\EchoModule.csproj\" \/&gt;    &lt;!-- UseSqlite is tier-agnostic: AddRedb -&gt; Free (what Tsak uses by default). --&gt;    &lt;ProjectReference Include=\"..\\..\\..\\..\\redb.SQLite.Pro\\redb.SQLite.Pro.csproj\" \/&gt;    &lt;PackageReference Include=\"Microsoft.Extensions.Logging.Console\" Version=\"10.0.3\" \/&gt;  &lt;\/ItemGroup&gt;&lt;\/Project&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>\u0417\u0434\u0435\u0441\u044c \u0432\u0430\u0436\u043d\u0430\u044f \u0434\u0435\u0442\u0430\u043b\u044c \u043f\u0440\u043e \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438. \u041e\u0442\u043b\u0430\u0434\u043e\u0447\u043d\u044b\u0439 \u0445\u043e\u0441\u0442 \u0441\u0441\u044b\u043b\u0430\u0435\u0442\u0441\u044f \u043d\u0430 \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440 SQLite (<a href=\"http:\/\/redb.SQLite.Pro\" rel=\"noopener noreferrer nofollow\"><code>redb.SQLite.Pro<\/code><\/a>) \u2014 \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0431\u0430\u0437\u0443 \u043f\u043e\u0434\u043d\u0438\u043c\u0430\u0435\u0442\u00a0<strong>\u043e\u043d<\/strong>. \u0410 \u0441\u0430\u043c \u043c\u043e\u0434\u0443\u043b\u044c \u043d\u0430 \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440 \u0411\u0414\u00a0<strong>\u043d\u0435 \u0441\u0441\u044b\u043b\u0430\u0435\u0442\u0441\u044f<\/strong>: \u0432 Tsak \u0431\u0430\u0437\u0443 \u0434\u0430\u0451\u0442 \u0432\u043e\u0440\u043a\u0435\u0440. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432\u00a0<code>.tpkg<\/code>\u00a0\u0443\u0435\u0434\u0435\u0442 \u0442\u043e\u043b\u044c\u043a\u043e\u00a0<code>EchoModule.dll<\/code>\u00a0(\u0441\u043c. \u0442\u0430\u0440\u0433\u0435\u0442\u00a0<code>PackTpkg<\/code>\u00a0\u2014 \u043e\u043d \u043a\u043b\u0430\u0434\u0451\u0442 \u0432 \u0430\u0440\u0445\u0438\u0432 \u0440\u043e\u0432\u043d\u043e \u0446\u0435\u043b\u0435\u0432\u0443\u044e DLL, \u043c\u0430\u043d\u0438\u0444\u0435\u0441\u0442 \u0438 \u043a\u043e\u043d\u0444\u0438\u0433, \u043d\u0435 \u0442\u0430\u0449\u0430 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438). \u041a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440\u044b\u00a0<code>redb.Route.*<\/code>\u00a0\u0438\u00a0<code>redb.Core<\/code>\u00a0\u0443\u0436\u0435 \u0435\u0441\u0442\u044c \u0432 \u0432\u043e\u0440\u043a\u0435\u0440\u0435 \u2014 \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043f\u0430\u043a\u0435\u0442 \u043e\u0441\u0442\u0430\u0451\u0442\u0441\u044f \u043b\u0451\u0433\u043a\u0438\u043c.<\/p>\n<h4>\u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u0438 \u0449\u0443\u043f\u0430\u0435\u043c<\/h4>\n<pre><code>dotnet run --project EchoWorker<\/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 \u0432 \u0434\u0440\u0443\u0433\u043e\u043c \u043e\u043a\u043d\u0435 \u0434\u0451\u0440\u043d\u0435\u043c \u0440\u0443\u0447\u043a\u0438. \u0422\u0443\u0442 \u0432\u0430\u0436\u043d\u0430\u044f \u0442\u043e\u043d\u043a\u043e\u0441\u0442\u044c Windows: \u044d\u043a\u0440\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 JSON \u0432\u00a0<code>curl<\/code>\u00a0<strong>\u0440\u0430\u0437\u043d\u043e\u0435 \u0432 cmd \u0438 \u0432 PowerShell<\/strong>. \u0412 PowerShell \u0444\u043e\u0440\u043c\u0430 \u0441\u00a0<code>\\\"<\/code>\u00a0\u043d\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u2014 \u0432\u044b \u043f\u043e\u0439\u043c\u0430\u0435\u0442\u0435\u00a0<code>'\\' is an invalid start of a property name<\/code>. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0434\u0430\u044e \u043e\u0431\u0430 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0430.<\/p>\n<p><strong>cmd.exe<\/strong>\u00a0(JSON \u0432 \u0434\u0432\u043e\u0439\u043d\u044b\u0445 \u043a\u0430\u0432\u044b\u0447\u043a\u0430\u0445, \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0435 \u044d\u043a\u0440\u0430\u043d\u0438\u0440\u0443\u0435\u043c \u0447\u0435\u0440\u0435\u0437\u00a0<code>\\\"<\/code>):<\/p>\n<pre><code>curl -X POST http:\/\/localhost:5099\/api\/notes -H \"Content-Type: application\/json\" -d \"{\\\"tag\\\":\\\"work\\\",\\\"text\\\":\\\"hello\\\"}\"curl \"http:\/\/localhost:5099\/api\/notes?tag=work\"<\/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>PowerShell<\/strong>\u00a0(JSON \u0432 \u043e\u0434\u0438\u043d\u0430\u0440\u043d\u044b\u0445 \u043a\u0430\u0432\u044b\u0447\u043a\u0430\u0445 \u2014 \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0435 \u0434\u0432\u043e\u0439\u043d\u044b\u0435 \u044d\u043a\u0440\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043d\u0435 \u043d\u0430\u0434\u043e; \u0438 \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u00a0<code>curl.exe<\/code>, \u0438\u043d\u0430\u0447\u0435\u00a0<code>curl<\/code>\u00a0\u2014 \u044d\u0442\u043e \u0430\u043b\u0438\u0430\u0441\u00a0<code>Invoke-WebRequest<\/code>):<\/p>\n<pre><code>curl.exe -X POST http:\/\/localhost:5099\/api\/notes -H \"Content-Type: application\/json\" -d '{\"tag\":\"work\",\"text\":\"hello\"}'curl.exe -X POST http:\/\/localhost:5099\/api\/notes -H \"Content-Type: application\/json\" -d '{\"tag\":\"home\",\"text\":\"other\"}'curl.exe \"http:\/\/localhost:5099\/api\/notes?tag=work\"<\/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>PowerShell \u0431\u0435\u0437 curl<\/strong>\u00a0(\u043d\u0430\u0442\u0438\u0432\u043d\u043e):<\/p>\n<pre><code>Invoke-RestMethod -Method Post -Uri http:\/\/localhost:5099\/api\/notes -ContentType 'application\/json' -Body '{\"tag\":\"work\",\"text\":\"hello\"}'Invoke-RestMethod \"http:\/\/localhost:5099\/api\/notes?tag=work\"<\/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>\u041e\u0442\u0432\u0435\u0442\u044b:<\/p>\n<pre><code>{\"saved\":true,\"id\":1000019}{\"saved\":true,\"id\":1000022}[{\"Tag\":\"work\",\"Text\":\"hello\"}]<\/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>GET<\/code>\u00a0\u0441\u00a0<code>?tag=home<\/code>\u00a0\u0432\u0435\u0440\u043d\u0451\u0442 \u0434\u0440\u0443\u0433\u0443\u044e \u0437\u0430\u043c\u0435\u0442\u043a\u0443, \u0430\u00a0<code>PUT \/api\/notes<\/code>\u00a0\u2014 405. \u0412\u0441\u0451 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442, \u0432\u0441\u0451 \u043f\u043e\u0434 \u043e\u0442\u043b\u0430\u0434\u0447\u0438\u043a\u043e\u043c: \u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u0442\u043e\u0447\u043a\u0443 \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u0430 \u0432\u043d\u0443\u0442\u0440\u0438\u00a0<code>ProcessWithRedb<\/code>, \u0448\u043b\u0438\u0442\u0435 curl \u2014 \u0438 \u0432\u044b \u043f\u0440\u044f\u043c\u043e \u0432 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0435, \u0441 \u0436\u0438\u0432\u044b\u043c\u00a0<code>db<\/code>\u00a0\u0438\u00a0<code>ex<\/code>.<\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/8ad\/9e5\/140\/8ad9e5140b88a30c2f53970a0658daaa.png\" alt=\"redb.route custom worker\" title=\"redb.route custom worker\" width=\"978\" height=\"427\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/8ad\/9e5\/140\/8ad9e5140b88a30c2f53970a0658daaa.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/8ad\/9e5\/140\/8ad9e5140b88a30c2f53970a0658daaa.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>redb.route custom worker<\/figcaption><\/div>\n<\/figure>\n<p>\u0412\u043e\u0442 \u044d\u0442\u043e \u0438 \u0435\u0441\u0442\u044c\u00a0<strong>\u043e\u0442\u043b\u0430\u0434\u043e\u0447\u043d\u0430\u044f \u0437\u043e\u043d\u0430<\/strong>: \u043e\u0431\u044b\u0447\u043d\u043e\u0435 \u043a\u043e\u043d\u0441\u043e\u043b\u044c\u043d\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435, F5, \u0442\u043e\u0447\u043a\u0438 \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u0430, \u0436\u0438\u0432\u0430\u044f SQLite-\u0431\u0430\u0437\u0430 \u0440\u044f\u0434\u043e\u043c \u0441 exe. \u041d\u0438\u043a\u0430\u043a\u043e\u0433\u043e \u0440\u0430\u043d\u0442\u0430\u0439\u043c\u0430, \u043d\u0438\u043a\u0430\u043a\u043e\u0433\u043e \u0434\u043e\u043a\u0435\u0440\u0430 \u2014 \u0433\u043e\u043b\u044b\u0439 \u0446\u0438\u043a\u043b \u00ab\u043f\u043e\u043f\u0440\u0430\u0432\u0438\u043b \u2192 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u043b \u2192 \u043f\u043e\u0442\u044b\u043a\u0430\u043b curl\u00bb.<\/p>\n<hr\/>\n<h3>\u0421\u043c\u043e\u0442\u0440\u0438\u0442\u0435: \u0442\u043e\u0442 \u0436\u0435 \u043a\u043e\u0434 \u2014 \u0438 \u044d\u0442\u043e \u0443\u0436\u0435 \u044d\u043d\u0442\u0435\u0440\u043f\u0440\u0430\u0439\u0437<\/h3>\n<p>\u0410 \u0442\u0435\u043f\u0435\u0440\u044c \u0441\u0430\u043c\u043e\u0435 \u043f\u0440\u0438\u044f\u0442\u043d\u043e\u0435.\u00a0<strong>\u041c\u044b \u043d\u0435 \u0442\u0440\u043e\u0433\u0430\u0435\u043c \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u044b \u0432\u043e\u043e\u0431\u0449\u0435.<\/strong>\u00a0\u0411\u0435\u0440\u0451\u043c \u0442\u0443 \u0436\u0435\u00a0<code>EchoModule.dll<\/code>, \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0434\u0432\u0430 \u043c\u0430\u043b\u0435\u043d\u044c\u043a\u0438\u0445 \u0444\u0430\u0439\u043b\u0430-\u043e\u043f\u0438\u0441\u0430\u0442\u0435\u043b\u044f, \u043f\u0430\u043a\u0443\u0435\u043c \u0432\u00a0<code>.tpkg<\/code>\u00a0\u2014 \u0438 \u043e\u0442\u0434\u0430\u0451\u043c \u0432 Tsak. \u041e\u0434\u0438\u043d \u0448\u0430\u0433 \u2014 \u0438 \u043d\u0430\u0448 \u0441\u0435\u0440\u0432\u0438\u0441 \u0438\u0437 \u00ab\u043a\u043e\u043d\u0441\u043e\u043b\u044c\u043a\u0438 \u043f\u043e\u0434 F5\u00bb \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u0434\u0430\u0448\u0431\u043e\u0440\u0434, \u043c\u0435\u0442\u0440\u0438\u043a\u0438, hot-reload \u0438 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043d\u0430 \u043b\u0435\u0442\u0443. \u041a\u043e\u0434\u0430 \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u043d\u043e\u043b\u044c \u043d\u043e\u0432\u043e\u0433\u043e.<\/p>\n<h4>\u0427\u0442\u043e \u0442\u0430\u043a\u043e\u0435\u00a0.tpkg<\/h4>\n<p><code>.tpkg<\/code>\u00a0\u2014 \u044d\u0442\u043e \u043f\u0440\u043e\u0441\u0442\u043e ZIP-\u0430\u0440\u0445\u0438\u0432 \u0441 \u0442\u0440\u0435\u043c\u044f \u0432\u0435\u0449\u0430\u043c\u0438:<\/p>\n<ul>\n<li>\n<p><code>manifest.json<\/code>\u00a0\u2014 \u043f\u0430\u0441\u043f\u043e\u0440\u0442 \u043c\u043e\u0434\u0443\u043b\u044f;<\/p>\n<\/li>\n<li>\n<p><code>EchoModule.dll<\/code>\u00a0\u2014 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u0441\u0431\u043e\u0440\u043a\u0430 \u0441\u00a0<code>InitRoute.main<\/code>;<\/p>\n<\/li>\n<li>\n<p><code>EchoModule.config.json<\/code>\u00a0\u2014 \u043a\u043e\u043d\u0444\u0438\u0433 \u043c\u043e\u0434\u0443\u043b\u044f (\u0432 \u043f\u0435\u0440\u0432\u0443\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u2014 \u0438\u043c\u044f \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430).<\/p>\n<\/li>\n<\/ul>\n<p><code>manifest.json<\/code>:<\/p>\n<pre><code class=\"json\">{  \"Name\": \"EchoModule\",  \"Version\": \"1.0.0\",  \"EntryPoints\": [\"EchoModule.dll\"],  \"Dependencies\": []}<\/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>EchoModule.config.json<\/code>:<\/p>\n<pre><code class=\"json\">{  \"ContextName\": \"echo\",  \"AutoStart\": true}<\/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\u043e\u00a0<code>ContextName<\/code>\u00a0\u2014 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u044d\u0442\u043e \u0433\u0440\u0430\u0431\u043b\u0438, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043b\u0435\u0433\u043a\u043e \u043d\u0430\u0441\u0442\u0443\u043f\u0438\u0442\u044c. \u0415\u0441\u043b\u0438 \u0438\u043c\u044f \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 \u043d\u0435 \u0437\u0430\u0434\u0430\u0442\u044c, Tsak \u043f\u043e\u0434\u043d\u0438\u043c\u0435\u0442 \u043c\u043e\u0434\u0443\u043b\u044c \u0432\u00a0<strong>\u0430\u043d\u043e\u043d\u0438\u043c\u043d\u043e\u043c<\/strong>\u00a0\u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0435 \u0441 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u043c \u0438\u043c\u0435\u043d\u0435\u043c (\u0432\u0438\u0434\u0430\u00a0<code>EchoModule_dyn_&lt;\u0434\u0430\u0442\u0430&gt;_&lt;guid&gt;<\/code>). \u041c\u043e\u0434\u0443\u043b\u044c \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442, \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u044b \u043a\u0440\u0443\u0442\u044f\u0442\u0441\u044f \u2014 \u043d\u043e \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 Endpoints \u0434\u0430\u0448\u0431\u043e\u0440\u0434\u0430 \u0430\u043d\u043e\u043d\u0438\u043c\u043d\u044b\u0435 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u044b \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u043d\u0435 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u044e\u0442\u0441\u044f (\u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u0432 \u043e\u0431\u0449\u0438\u0445 \u0441\u0447\u0451\u0442\u0447\u0438\u043a\u0430\u0445 \u043e\u043d\u0438 \u0443\u0447\u0438\u0442\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u2014 \u0438 \u0446\u0438\u0444\u0440\u044b \u043d\u0435 \u0441\u0445\u043e\u0434\u044f\u0442\u0441\u044f \u0441 \u0442\u0435\u043c, \u0447\u0442\u043e \u0432\u0438\u0434\u043d\u043e \u0432 \u0441\u043f\u0438\u0441\u043a\u0435). \u041c\u043e\u0440\u0430\u043b\u044c \u043f\u0440\u043e\u0441\u0442\u0430\u044f:\u00a0<strong>\u0434\u0430\u0439\u0442\u0435 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0443 \u0438\u043c\u044f<\/strong>\u00a0\u0447\u0435\u0440\u0435\u0437\u00a0<code>EchoModule.config.json<\/code>\u00a0\u2014 \u0438 \u043e\u043d \u0430\u043a\u043a\u0443\u0440\u0430\u0442\u043d\u043e \u043f\u043e\u044f\u0432\u0438\u0442\u0441\u044f \u0432 \u0441\u043f\u0438\u0441\u043a\u0435 \u043a\u0430\u043a\u00a0<code>echo<\/code>. \u041c\u0435\u043b\u043e\u0447\u044c, \u0430 \u0431\u0435\u0440\u0435\u0436\u0451\u0442 \u043d\u0435\u0440\u0432\u044b.<\/p>\n<h4>\u041f\u0430\u043a\u0443\u0435\u043c<\/h4>\n<p>\u0422\u0430\u0440\u0433\u0435\u0442\u00a0<code>PackTpkg<\/code>\u00a0\u0438\u0437\u00a0<code>.csproj<\/code>\u00a0\u0434\u0435\u043b\u0430\u0435\u0442 \u0432\u0441\u0451 \u0441\u0430\u043c \u043d\u0430 \u044d\u0442\u0430\u043f\u0435 \u0441\u0431\u043e\u0440\u043a\u0438:<\/p>\n<pre><code>dotnet build EchoModule -c Debug<\/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\u0430 \u0432\u044b\u0445\u043e\u0434\u0435 \u2014\u00a0<code>EchoModule\/output\/EchoModule.tpkg<\/code>. \u0417\u0430\u0433\u043b\u044f\u043d\u0435\u043c \u0432\u043d\u0443\u0442\u0440\u044c:<\/p>\n<pre><code>Archive:  output\/EchoModule.tpkg  Length      Name---------  ----       49  EchoModule.config.json    13312  EchoModule.dll      108  manifest.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>\u0422\u0440\u0438 \u0444\u0430\u0439\u043b\u0430, ~7 \u041a\u0411. \u0412\u0441\u0451, \u044d\u0442\u043e \u043d\u0430\u0448 \u0434\u0435\u043f\u043b\u043e\u0439-\u0430\u0440\u0442\u0435\u0444\u0430\u043a\u0442.<\/p>\n<hr\/>\n<h3>\u041a\u0430\u043a Tsak \u043d\u0430\u0445\u043e\u0434\u0438\u0442 \u043c\u043e\u0434\u0443\u043b\u0438: \u043f\u0430\u043f\u043a\u0438 \u0438 hot-reload<\/h3>\n<p>\u042d\u0442\u043e \u0442\u043e \u043c\u0435\u0441\u0442\u043e, \u0440\u0430\u0434\u0438 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u0432\u0441\u0451 \u0438 \u0437\u0430\u0442\u0435\u0432\u0430\u043b\u043e\u0441\u044c. Tsak \u043d\u0435 \u00ab\u0441\u043b\u0443\u0448\u0430\u0435\u0442\u00bb \u043f\u0430\u043f\u043a\u0443 \u043f\u043e \u0441\u043e\u0431\u044b\u0442\u0438\u044f\u043c \u2014 \u0432\u043d\u0443\u0442\u0440\u0438 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0444\u043e\u043d\u043e\u0432\u044b\u0439 \u0441\u0435\u0440\u0432\u0438\u0441\u00a0<code>HotReloadService<\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439\u00a0<strong>\u0440\u0430\u0437 \u0432 \u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u0442 \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0438<\/strong>, \u043f\u0435\u0440\u0435\u0447\u0438\u0441\u043b\u0435\u043d\u043d\u044b\u0435 \u0432 \u043a\u043e\u043d\u0444\u0438\u0433\u0435\u00a0<code>Tsak:Modules:AssemblyPaths<\/code>.<\/p>\n<p>\u0421\u0445\u0435\u043c\u0430 \u0442\u0430\u043a\u0430\u044f:<\/p>\n<ul>\n<li>\n<p>\u0412 \u043a\u043e\u043d\u0444\u0438\u0433\u0435 \u0432\u043e\u0440\u043a\u0435\u0440\u0430 \u0435\u0441\u0442\u044c \u0441\u043f\u0438\u0441\u043e\u043a \u043f\u0443\u0442\u0435\u0439\u00a0<code>Tsak:Modules:AssemblyPaths<\/code>. \u041a\u0430\u0442\u0430\u043b\u043e\u0433 \u043c\u043e\u0434\u0443\u043b\u0435\u0439 \u043e\u0431\u044b\u0447\u043d\u043e \u043d\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f\u00a0<code>modules<\/code>.<\/p>\n<\/li>\n<li>\n<p>\u0420\u0430\u0437 \u0432\u00a0<code>Tsak:HotReload:ScanIntervalSeconds<\/code>\u00a0(\u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e\u00a0<strong>10 \u0441\u0435\u043a\u0443\u043d\u0434<\/strong>) \u0441\u0435\u0440\u0432\u0438\u0441 \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u0442 \u043f\u043e \u043a\u0430\u0436\u0434\u043e\u043c\u0443 \u043f\u0443\u0442\u0438 \u0438 \u0431\u0435\u0440\u0451\u0442 \u0432\u0441\u0435 \u0444\u0430\u0439\u043b\u044b\u00a0<code><em>.tpkg<\/em><\/code><em>\u00a0(\u0438 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e \u0433\u043e\u043b\u044b\u0435\u00a0<\/em><code>.dll<\/code>).<\/p>\n<\/li>\n<li>\n<p>\u0420\u0435\u0448\u0435\u043d\u0438\u0435 \u00ab\u043d\u043e\u0432\u044b\u0439 \/ \u0438\u0437\u043c\u0435\u043d\u0438\u043b\u0441\u044f \/ \u0443\u0434\u0430\u043b\u0451\u043d\u00bb \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442\u0441\u044f \u043f\u043e\u00a0<strong>\u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u043c\u043e\u0434\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u0444\u0430\u0439\u043b\u0430<\/strong>:<\/p>\n<ul>\n<li>\n<p>mtime \u043d\u0435 \u0438\u0437\u043c\u0435\u043d\u0438\u043b\u0441\u044f \u2192 \u043f\u0440\u043e\u043f\u0443\u0441\u043a\u0430\u0435\u043c;<\/p>\n<\/li>\n<li>\n<p>\u043d\u043e\u0432\u044b\u0439 \u0444\u0430\u0439\u043b \u2192 \u0440\u0430\u0441\u043f\u0430\u043a\u043e\u0432\u044b\u0432\u0430\u0435\u043c \u0430\u0440\u0445\u0438\u0432, \u0433\u0440\u0443\u0437\u0438\u043c \u0441\u0431\u043e\u0440\u043a\u0443 \u0432\u00a0<strong>\u0438\u0437\u043e\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438<\/strong>\u00a0(\u0441\u0432\u043e\u0439 ALC \u043d\u0430 \u043f\u0430\u043a\u0435\u0442), \u043d\u0430\u0445\u043e\u0434\u0438\u043c\u00a0<code>InitRoute.main<\/code>\u00a0\u0440\u0435\u0444\u043b\u0435\u043a\u0441\u0438\u0435\u0439, \u043f\u043e\u0434\u043d\u0438\u043c\u0430\u0435\u043c \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442;<\/p>\n<\/li>\n<li>\n<p>\u0444\u0430\u0439\u043b \u0438\u0437\u043c\u0435\u043d\u0438\u043b\u0441\u044f \u2192 \u043f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c \u043f\u0430\u043a\u0435\u0442 \u0430\u0442\u043e\u043c\u0430\u0440\u043d\u043e (\u0441\u043d\u0438\u043c\u0430\u0435\u043c \u0441\u0442\u0430\u0440\u0443\u044e \u0432\u0435\u0440\u0441\u0438\u044e, \u0441\u0442\u0430\u0432\u0438\u043c \u043d\u043e\u0432\u0443\u044e, \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u2014 \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0431\u044b\u043b \u043b\u0438 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 \u0437\u0430\u043f\u0443\u0449\u0435\u043d);<\/p>\n<\/li>\n<li>\n<p>\u0444\u0430\u0439\u043b \u0438\u0441\u0447\u0435\u0437 \u0441 \u0434\u0438\u0441\u043a\u0430 \u2192 \u0432\u044b\u0433\u0440\u0443\u0436\u0430\u0435\u043c \u0432\u0441\u0435 \u043c\u043e\u0434\u0443\u043b\u0438 \u044d\u0442\u043e\u0433\u043e \u043f\u0430\u043a\u0435\u0442\u0430.<\/p>\n<\/li>\n<\/ul>\n<\/li>\n<li>\n<p>\u041e\u0431\u0449\u0438\u0435 \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440\u044b (<code>redb.Route.*<\/code>,\u00a0<code>redb.Core<\/code>) \u0440\u0435\u0437\u043e\u043b\u0432\u044f\u0442\u0441\u044f \u0438\u0437 \u043e\u0431\u0449\u0438\u0445 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a \u0432\u043e\u0440\u043a\u0435\u0440\u0430 (<code>Libs\/shared<\/code>), \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432 \u0441\u0430\u043c\u043e\u043c\u00a0<code>.tpkg<\/code>\u00a0\u0438\u0445 \u043d\u0435\u0442.<\/p>\n<\/li>\n<\/ul>\n<p>\u041f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u0441\u043b\u0435\u0434\u0441\u0442\u0432\u0438\u044f:<\/p>\n<ul>\n<li>\n<p>\u041a\u0438\u043d\u0443\u043b\u0438\/\u043e\u0431\u043d\u043e\u0432\u0438\u043b\u0438\u00a0<code>EchoModule.tpkg<\/code>\u00a0\u0432\u00a0<code>modules\/<\/code>\u00a0\u2014 \u043f\u043e\u0434\u0445\u0432\u0430\u0442\u0438\u0442\u0441\u044f\u00a0<strong>\u0432 \u0442\u0435\u0447\u0435\u043d\u0438\u0435 ~10 \u0441\u0435\u043a\u0443\u043d\u0434<\/strong>, \u0431\u0435\u0437 \u0440\u0435\u0441\u0442\u0430\u0440\u0442\u0430.<\/p>\n<\/li>\n<li>\n<p>\u0422\u0440\u0438\u0433\u0433\u0435\u0440 \u2014 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 mtime \u0444\u0430\u0439\u043b\u0430. \u041e\u0431\u044b\u0447\u043d\u043e\u0435 \u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 mtime \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u0442, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u00ab\u043f\u0435\u0440\u0435\u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043b\u00bb = \u00ab\u043f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u0438\u043b\u00bb.<\/p>\n<\/li>\n<li>\n<p>\u041a\u0430\u0436\u0434\u044b\u0439\u00a0<code>.tpkg<\/code>\u00a0\u0436\u0438\u0432\u0451\u0442 \u0432 \u0441\u0432\u043e\u0451\u043c ALC \u2014 \u0432\u0435\u0440\u0441\u0438\u0438 \u043c\u043e\u0434\u0443\u043b\u0435\u0439 \u0438\u0437\u043e\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u044b \u0434\u0440\u0443\u0433 \u043e\u0442 \u0434\u0440\u0443\u0433\u0430.<\/p>\n<\/li>\n<\/ul>\n<p>\u0418\u043d\u044b\u043c\u0438 \u0441\u043b\u043e\u0432\u0430\u043c\u0438, \u0434\u0435\u043f\u043b\u043e\u0439 \u043d\u043e\u0432\u043e\u0433\u043e \u043c\u043e\u0434\u0443\u043b\u044f \u0438\u043b\u0438 \u0435\u0433\u043e \u0432\u0435\u0440\u0441\u0438\u0438 \u2014 \u044d\u0442\u043e\u00a0<strong>\u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043e\u0434\u043d\u043e\u0433\u043e \u0444\u0430\u0439\u043b\u0430 \u0432 \u043f\u0430\u043f\u043a\u0443<\/strong>. \u0414\u0430\u043b\u044c\u0448\u0435 Tsak \u0441\u0430\u043c.<\/p>\n<hr\/>\n<h3>\u0414\u0430\u0448\u0431\u043e\u0440\u0434: \u0441\u043c\u043e\u0442\u0440\u0438\u043c \u0438 \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c<\/h3>\n<p>\u041f\u043e\u0434\u043d\u0438\u043c\u0430\u0435\u043c Tsak (\u043a\u0430\u043a \u0438\u043c\u0435\u043d\u043d\u043e \u2014 \u0434\u0432\u0443\u043c\u044f \u0441\u043f\u043e\u0441\u043e\u0431\u0430\u043c\u0438 \u043d\u0438\u0436\u0435), \u043a\u043b\u0430\u0434\u0451\u043c\u00a0<code>EchoModule.tpkg<\/code>\u00a0\u0432 \u043f\u0430\u043f\u043a\u0443 \u043c\u043e\u0434\u0443\u043b\u0435\u0439, \u0436\u0434\u0451\u043c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0441\u0435\u043a\u0443\u043d\u0434 \u2014 \u0438 \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u0432\u0435\u0431-\u0434\u0430\u0448\u0431\u043e\u0440\u0434. \u041d\u0430\u0448 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u00a0<code>echo<\/code>\u00a0\u0442\u0435\u043f\u0435\u0440\u044c \u0442\u0430\u043c, \u0441 \u0434\u0432\u0443\u043c\u044f \u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u0430\u043c\u0438.<\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/fc2\/caa\/7ba\/fc2caa7ba275c44da187315c068cc7a6.png\" alt=\"Tsak Dashboard \u2014 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 Endpoints\" title=\"Tsak Dashboard \u2014 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 Endpoints\" width=\"1698\" height=\"872\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/fc2\/caa\/7ba\/fc2caa7ba275c44da187315c068cc7a6.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/fc2\/caa\/7ba\/fc2caa7ba275c44da187315c068cc7a6.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption><strong>Tsak Dashboard \u2014 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 Endpoints<\/strong><\/figcaption><\/div>\n<\/figure>\n<p>\u0418 \u0432\u043e\u0442 \u0442\u0443\u0442 \u2014 \u0432\u0442\u043e\u0440\u0430\u044f \u0431\u043e\u043b\u044c\u0448\u0430\u044f \u0440\u0430\u0437\u043d\u0438\u0446\u0430 \u0441 \u043e\u0442\u043b\u0430\u0434\u043e\u0447\u043d\u043e\u0439 \u043a\u043e\u043d\u0441\u043e\u043b\u044c\u044e. \u0412 \u043a\u043e\u043d\u0441\u043e\u043b\u0438 \u043c\u044b \u0432\u0438\u0434\u0435\u043b\u0438 \u0442\u043e\u043b\u044c\u043a\u043e \u0442\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0435 \u043b\u043e\u0433\u0438. \u0412 Tsak \u2014 \u0442\u043e \u0436\u0435 \u0441\u0430\u043c\u043e\u0435, \u043d\u043e:<\/p>\n<ul>\n<li>\n<p>\u0432\u0438\u0434\u043d\u043e \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u00a0<code>echo<\/code>\u00a0\u0438 \u0435\u0433\u043e \u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u044b, \u0442\u0438\u043f (HTTP), \u0440\u043e\u043b\u044c (Consumer), \u0441\u0447\u0451\u0442\u0447\u0438\u043a\u0438 in\/out, \u043e\u0448\u0438\u0431\u043a\u0438, throughput, uptime, \u0437\u0434\u043e\u0440\u043e\u0432\u044c\u0435;<\/p>\n<\/li>\n<li>\n<p>\u0432\u0438\u0434\u043d\u043e \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u044b\u0439 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u00a0<code>_SYSTEM<\/code>\u00a0\u2014 \u044d\u0442\u043e \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u044e\u0449\u0438\u0439 API \u0441\u0430\u043c\u043e\u0433\u043e \u0432\u043e\u0440\u043a\u0435\u0440\u0430;<\/p>\n<\/li>\n<li>\n<p>\u043f\u043e \u043a\u0430\u0436\u0434\u043e\u043c\u0443 \u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u0443 \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u043e\u0432\u0430\u043b\u0438\u0442\u044c\u0441\u044f \u0432 \u0434\u0435\u0442\u0430\u043b\u0438 (\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f, \u0431\u0430\u0439\u0442\u044b, \u0441\u0440\u0435\u0434\u043d\u0435\u0435 \u0432\u0440\u0435\u043c\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438, \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0435 \u043e\u0448\u0438\u0431\u043a\u0438).<\/p>\n<\/li>\n<\/ul>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/c7a\/32c\/cf2\/c7a32ccf2de95665da8bb7e4e1ef39a3.png\" alt=\"Tsak \u2014 \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0430 \u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u0430 notes-get \u0441 \u043c\u0435\u0442\u0440\u0438\u043a\u0430\u043c\u0438 \u0438 \u0434\u0435\u0442\u0430\u043b\u044f\u043c\u0438\" title=\"Tsak \u2014 \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0430 \u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u0430 notes-get \u0441 \u043c\u0435\u0442\u0440\u0438\u043a\u0430\u043c\u0438 \u0438 \u0434\u0435\u0442\u0430\u043b\u044f\u043c\u0438\" width=\"1705\" height=\"981\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/c7a\/32c\/cf2\/c7a32ccf2de95665da8bb7e4e1ef39a3.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/c7a\/32c\/cf2\/c7a32ccf2de95665da8bb7e4e1ef39a3.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption><strong>Tsak \u2014 \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0430 \u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u0430 notes-get \u0441 \u043c\u0435\u0442\u0440\u0438\u043a\u0430\u043c\u0438 \u0438 \u0434\u0435\u0442\u0430\u043b\u044f\u043c\u0438<\/strong><\/figcaption><\/div>\n<\/figure>\n<p>\u041d\u043e \u0433\u043b\u0430\u0432\u043d\u043e\u0435 \u2014 \u044d\u0442\u0438\u043c \u043c\u043e\u0436\u043d\u043e\u00a0<strong>\u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u043f\u0440\u044f\u043c\u043e \u0438\u0437 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430<\/strong>, \u043d\u0435 \u0442\u0440\u043e\u0433\u0430\u044f \u043a\u043e\u043d\u0444\u0438\u0433\u0438 \u0438 \u043d\u0435 \u043f\u0435\u0440\u0435\u0434\u0435\u043f\u043b\u043e\u0438\u0432\u0430\u044f. \u041d\u0430 \u0434\u0435\u0442\u0430\u043b\u044c\u043d\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 \u043d\u043e\u0434\u044b \u043f\u043e \u043d\u0430\u0448\u0435\u043c\u0443 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0443\u00a0<code>echo<\/code>\u00a0\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e:<\/p>\n<ul>\n<li>\n<p><strong>\u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 \u0446\u0435\u043b\u0438\u043a\u043e\u043c<\/strong>\u00a0\u2014 Start \/ Stop \/ Restart, \u0430 \u0442\u0430\u043a\u0436\u0435 Reset route states (\u0441\u0431\u0440\u043e\u0441 \u0441\u043e\u0445\u0440\u0430\u043d\u0451\u043d\u043d\u044b\u0445 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0439 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043e\u0432);<\/p>\n<\/li>\n<li>\n<p><strong>\u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0435 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u044b<\/strong>\u00a0\u2014 \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c\/\u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u0439\u00a0<code>notes-post<\/code>\u00a0\u0438\u043b\u0438\u00a0<code>notes-get<\/code>, \u043d\u0435 \u0442\u0440\u043e\u0433\u0430\u044f \u0441\u043e\u0441\u0435\u0434\u043d\u0438\u0439;<\/p>\n<\/li>\n<li>\n<p>\u0435\u0441\u043b\u0438 \u0432 \u043c\u043e\u0434\u0443\u043b\u0435 \u0435\u0441\u0442\u044c \u0440\u0430\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u044f (Quartz) \u2014 \u043d\u0430 \u0432\u043a\u043b\u0430\u0434\u043a\u0435 \u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0449\u0438\u043a\u0430 \u043c\u043e\u0436\u043d\u043e \u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0434\u0436\u043e\u0431\u044b \u043d\u0430 \u043f\u0430\u0443\u0437\u0443 \u0438 \u0432\u043e\u0437\u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0442\u044c.<\/p>\n<\/li>\n<\/ul>\n<p>\u0418 \u0432\u043e\u0442 \u0432\u0430\u0436\u043d\u044b\u0439 \u043c\u043e\u043c\u0435\u043d\u0442:\u00a0<strong>\u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0437\u0430\u043f\u043e\u043c\u0438\u043d\u0430\u0435\u0442\u0441\u044f<\/strong>. \u0415\u0441\u043b\u0438 \u0432\u044b \u0440\u0443\u043a\u0430\u043c\u0438 \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043b\u0438 \u043c\u0430\u0440\u0448\u0440\u0443\u0442, \u0442\u043e hot-reload \u043f\u0440\u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0438 \u043f\u0430\u043a\u0435\u0442\u0430 \u0441\u0430\u043c \u0435\u0433\u043e \u043e\u0431\u0440\u0430\u0442\u043d\u043e\u00a0<strong>\u043d\u0435 \u043f\u043e\u0434\u043d\u0438\u043c\u0435\u0442<\/strong>\u00a0\u2014 Tsak \u0443\u0432\u0430\u0436\u0430\u0435\u0442 \u0440\u0443\u0447\u043d\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u0430. \u0422\u043e \u0435\u0441\u0442\u044c \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044b\u0439 \u043d\u0430 \u043f\u0440\u043e\u0434\u0435 \u043c\u0430\u0440\u0448\u0440\u0443\u0442 \u043d\u0435 \u00ab\u043e\u0436\u0438\u0432\u0451\u0442\u00bb \u0432\u043d\u0435\u0437\u0430\u043f\u043d\u043e \u043f\u043e\u0441\u043b\u0435 \u0442\u043e\u0433\u043e, \u043a\u0430\u043a \u0432\u044b \u043f\u043e\u0434\u043a\u0430\u0442\u0438\u0442\u0435 \u043d\u043e\u0432\u0443\u044e \u0432\u0435\u0440\u0441\u0438\u044e\u00a0<code>.tpkg<\/code>. \u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u2014 \u00ab\u0433\u043e\u0440\u044f\u0447\u0435\u0435\u00bb (\u0434\u0435\u0439\u0441\u0442\u0432\u0443\u0435\u0442 \u0441\u0440\u0430\u0437\u0443) \u0438 \u00ab\u043b\u0438\u043f\u043a\u043e\u0435\u00bb (\u043f\u0435\u0440\u0435\u0436\u0438\u0432\u0430\u0435\u0442 \u043f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0443 \u043c\u043e\u0434\u0443\u043b\u044f).<\/p>\n<p>\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0432 \u0434\u0430\u0448\u0431\u043e\u0440\u0434\u0435 \u043f\u043e\u0434 \u0440\u043e\u043b\u044c\u044e \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u0430 \u2014 \u0442\u043e \u0435\u0441\u0442\u044c \u044d\u0442\u043e \u0438\u043c\u0435\u043d\u043d\u043e \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u0441\u043a\u0430\u044f \u043f\u0430\u043d\u0435\u043b\u044c, \u0430 \u043d\u0435 \u043f\u0440\u043e\u0441\u0442\u043e \u0432\u0438\u0442\u0440\u0438\u043d\u0430 \u043c\u0435\u0442\u0440\u0438\u043a.<\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/072\/615\/2ed\/0726152ed23e9096e100dcc2f3db9df1.png\" alt=\"Tsak \u2014 \u0434\u0435\u0442\u0430\u043b\u044c\u043d\u0430\u044f \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u043d\u043e\u0434\u044b\/\u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 echo \u0441 \u043a\u043d\u043e\u043f\u043a\u0430\u043c\u0438 Start\/Stop\/Restart \u0434\u043b\u044f \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043e\u0432 notes-post \/ notes-get\" title=\"Tsak \u2014 \u0434\u0435\u0442\u0430\u043b\u044c\u043d\u0430\u044f \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u043d\u043e\u0434\u044b\/\u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 echo \u0441 \u043a\u043d\u043e\u043f\u043a\u0430\u043c\u0438 Start\/Stop\/Restart \u0434\u043b\u044f \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043e\u0432 notes-post \/ notes-get\" width=\"1705\" height=\"642\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/072\/615\/2ed\/0726152ed23e9096e100dcc2f3db9df1.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/072\/615\/2ed\/0726152ed23e9096e100dcc2f3db9df1.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption><strong>Tsak \u2014 \u0434\u0435\u0442\u0430\u043b\u044c\u043d\u0430\u044f \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u043d\u043e\u0434\u044b\/\u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 echo \u0441 \u043a\u043d\u043e\u043f\u043a\u0430\u043c\u0438 Start\/Stop\/Restart \u0434\u043b\u044f \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043e\u0432 notes-post \/ notes-get<\/strong><\/figcaption><\/div>\n<\/figure>\n<p>\u0421\u0440\u0430\u0432\u043d\u0438\u0442\u0435 \u043e\u0449\u0443\u0449\u0435\u043d\u0438\u044f: \u0432 \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u043e\u043c \u0432\u043e\u0440\u043a\u0435\u0440\u0435 \u0432\u044b\u00a0<strong>\u043e\u0442\u043b\u0430\u0436\u0438\u0432\u0430\u0435\u0442\u0435<\/strong>\u00a0(F5, \u0431\u0440\u0435\u0439\u043a\u043f\u043e\u0438\u043d\u0442\u044b, \u0433\u043e\u043b\u0430\u044f \u043a\u043e\u043d\u0441\u043e\u043b\u044c), \u0430 \u0432 Tsak \u2014\u00a0<strong>\u044d\u043a\u0441\u043f\u043b\u0443\u0430\u0442\u0438\u0440\u0443\u0435\u0442\u0435<\/strong>\u00a0(\u043d\u0430\u0431\u043b\u044e\u0434\u0430\u0435\u0442\u0435, \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442\u0435, \u0434\u0435\u043f\u043b\u043e\u0438\u0442\u0435). \u041e\u0434\u0438\u043d \u0438 \u0442\u043e\u0442 \u0436\u0435 \u043a\u043e\u0434 \u2014 \u0434\u0432\u0435 \u0441\u0440\u0435\u0434\u044b, \u0434\u0432\u0435 \u0431\u043e\u043b\u044c\u0448\u0438\u0435 \u0440\u0430\u0437\u043d\u0438\u0446\u044b.<\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/9b6\/59d\/ad0\/9b659dad0c1c0aadb054bf67ec9e33f7.png\" alt=\"redb.tsak.web routes\" title=\"redb.tsak.web routes\" width=\"1707\" height=\"762\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/9b6\/59d\/ad0\/9b659dad0c1c0aadb054bf67ec9e33f7.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/9b6\/59d\/ad0\/9b659dad0c1c0aadb054bf67ec9e33f7.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>redb.tsak.web routes<\/figcaption><\/div>\n<\/figure>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/802\/104\/84e\/80210484e07586be7d64b0e04b9d056c.png\" alt=\"redb.routes console\" title=\"redb.routes console\" width=\"978\" height=\"427\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/802\/104\/84e\/80210484e07586be7d64b0e04b9d056c.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/802\/104\/84e\/80210484e07586be7d64b0e04b9d056c.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>redb.routes console<\/figcaption><\/div>\n<\/figure>\n<hr\/>\n<h3>\u041f\u043e\u0440\u0442\u044b: \u0447\u0442\u043e \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0438 \u043a\u0430\u043a \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c<\/h3>\n<p>\u0420\u0430\u0437 \u0443\u0436 \u043c\u044b \u0433\u043e\u043d\u044f\u0435\u043c \u044d\u0442\u043e \u0432 \u0440\u0430\u0437\u043d\u044b\u0445 \u0441\u0440\u0435\u0434\u0430\u0445, \u0440\u0430\u0437\u043b\u043e\u0436\u0438\u043c \u043f\u043e\u0440\u0442\u044b \u043f\u043e \u043f\u043e\u043b\u043e\u0447\u043a\u0430\u043c \u2014 \u0438\u043d\u0430\u0447\u0435 \u043b\u0435\u0433\u043a\u043e \u0437\u0430\u043f\u0443\u0442\u0430\u0442\u044c\u0441\u044f, \u0433\u0434\u0435 \u0447\u0442\u043e \u0436\u0438\u0432\u0451\u0442.<\/p>\n<ul>\n<li>\n<p><strong>\u041c\u043e\u0434\u0443\u043b\u044c (\u043d\u0430\u0448\u0438 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u044b) \u2014\u00a0<\/strong><code><strong>5099<\/strong><\/code><strong>.<\/strong>\u00a0\u042d\u0442\u043e \u043f\u043e\u0440\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u044b \u0441\u0430\u043c\u0438 \u043f\u0440\u043e\u043f\u0438\u0441\u0430\u043b\u0438 \u0432\u00a0<code>From(\"http:0.0.0.0:5099\/...\")<\/code>. \u0421\u0432\u043e\u0439 HTTP-\u0441\u0435\u0440\u0432\u0435\u0440 \u043c\u043e\u0434\u0443\u043b\u044f. \u0425\u043e\u0442\u0438\u0442\u0435 \u0434\u0440\u0443\u0433\u043e\u0439 \u2014 \u043c\u0435\u043d\u044f\u0435\u0442\u0435 \u0432 \u043a\u043e\u0434\u0435 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430.<\/p>\n<\/li>\n<li>\n<p><strong>\u0412\u043e\u0440\u043a\u0435\u0440 (\u0443\u043f\u0440\u0430\u0432\u043b\u044f\u044e\u0449\u0438\u0439 API Tsak) \u2014\u00a0<\/strong><code><strong>9090<\/strong><\/code><strong>.<\/strong>\u00a0\u042d\u0442\u043e \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0441\u0435\u0440\u0432\u0435\u0440: health, \u043c\u0435\u0442\u0440\u0438\u043a\u0438, \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430\u043c\u0438, \u043a\u043b\u0430\u0441\u0442\u0435\u0440\u043d\u044b\u0439 API. \u0415\u0433\u043e \u0434\u0451\u0440\u0433\u0430\u0435\u0442 \u0434\u0430\u0448\u0431\u043e\u0440\u0434. \u041f\u043e\u0440\u0442 \u0431\u0435\u0440\u0451\u0442\u0441\u044f \u0438\u0437 \u043a\u043e\u043d\u0444\u0438\u0433\u0430 \u0432\u043e\u0440\u043a\u0435\u0440\u0430.<\/p>\n<\/li>\n<li>\n<p><strong>\u0412\u0435\u0431-\u0434\u0430\u0448\u0431\u043e\u0440\u0434 \u2014 \u0442\u0443\u0442 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435.<\/strong>\u00a0\u0415\u0441\u043b\u0438 \u043d\u0438\u00a0<code>ASPNETCORE_URLS<\/code>, \u043d\u0438\u00a0<code>Kestrel<\/code>\u00a0\u0432 \u043a\u043e\u043d\u0444\u0438\u0433\u0435 \u0432\u0435\u0431\u0430 \u043d\u0435 \u0437\u0430\u0434\u0430\u043d\u044b, <a href=\"http:\/\/ASP.NET\" rel=\"noopener noreferrer nofollow\">ASP.NET<\/a> Core \u0431\u0435\u0440\u0451\u0442 \u0441\u0432\u043e\u0439\u00a0<strong>\u0434\u0435\u0444\u043e\u043b\u0442 \u2014\u00a0<\/strong><a href=\"http:\/\/localhost:5000\" rel=\"noopener noreferrer nofollow\"><code><strong>http:\/\/localhost:5000<\/strong><\/code><\/a>. Docker-\u043e\u0431\u0440\u0430\u0437\u044b \u0438 \u0441\u0442\u0430\u0440\u0442\u043e\u0432\u044b\u0435 \u0441\u043a\u0440\u0438\u043f\u0442\u044b \u0434\u0438\u0441\u0442\u0440\u0438\u0431\u0443\u0442\u0438\u0432\u0430 \u0437\u0430\u0434\u0430\u044e\u0442\u00a0<code>ASPNETCORE_URLS=<\/code><a href=\"http:\/\/localhost:8080\" rel=\"noopener noreferrer nofollow\"><code>http:\/\/localhost:8080<\/code><\/a>\u00a0(\u0438\u043b\u0438\u00a0<a href=\"http:\/\/+:8080\" rel=\"noopener noreferrer nofollow\"><code>http:\/\/+:8080<\/code><\/a>\u00a0\u0432 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0435) \u2014 \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432 \u0434\u043e\u043a\u0435\u0440\u0435 \u0438 \u043f\u043e \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u0438 \u0434\u0430\u0448\u0431\u043e\u0440\u0434 \u043d\u0430\u00a0<strong>8080<\/strong>. \u0415\u0441\u043b\u0438 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0432\u0435\u0431 \u00ab\u0433\u043e\u043b\u044b\u043c\u00bb \u0431\u0435\u0437 \u044d\u0442\u043e\u0439 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0439 \u2014 \u043d\u0435 \u0443\u0434\u0438\u0432\u043b\u044f\u0439\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u043c\u043e\u0440\u0434\u0430 \u043e\u043a\u0430\u0436\u0435\u0442\u0441\u044f \u043d\u0430\u00a0<strong>5000<\/strong>.<\/p>\n<\/li>\n<\/ul>\n<p>\u041a\u0430\u043a \u0437\u0430\u0434\u0430\u0442\u044c \u044f\u0432\u043d\u043e:<\/p>\n<pre><code class=\"powershell\"># \u043f\u0435\u0440\u0435\u0434 \u0437\u0430\u043f\u0443\u0441\u043a\u043e\u043c \u0432\u0435\u0431\u0430$env:ASPNETCORE_URLS = \"http:\/\/localhost:8080\"<\/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>\u0438\u043b\u0438 \u0432\u00a0<code>appsettings.json<\/code>\u00a0\u0432\u0435\u0431\u0430:<\/p>\n<pre><code class=\"json\">\"Kestrel\": { \"Endpoints\": { \"Http\": { \"Url\": \"http:\/\/localhost:8080\" } } }<\/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>\u041a\u043e\u0440\u043e\u0442\u043a\u0430\u044f \u0448\u043f\u0430\u0440\u0433\u0430\u043b\u043a\u0430:\u00a0<strong>9090<\/strong>\u00a0\u2014 API \u0432\u043e\u0440\u043a\u0435\u0440\u0430,\u00a0<strong>5099<\/strong>\u00a0\u2014 \u043d\u0430\u0448\u0438 \u0440\u0443\u0447\u043a\u0438,\u00a0<strong>8080<\/strong>\u00a0\u2014 \u0434\u0430\u0448\u0431\u043e\u0440\u0434 (\u0435\u0441\u043b\u0438 \u0437\u0430\u0434\u0430\u043d\u00a0<code>ASPNETCORE_URLS<\/code>; \u0438\u043d\u0430\u0447\u0435 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a-\u0434\u0435\u0444\u043e\u043b\u0442\u00a0<strong>5000<\/strong>).<\/p>\n<hr\/>\n<h3>\u042d\u043d\u0442\u0435\u0440\u043f\u0440\u0430\u0439\u0437-\u0434\u0435\u043f\u043b\u043e\u0439 \u21161: Docker (\u0441\u0442\u0435\u043a \u043e\u0434\u043d\u0438\u043c \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u043e\u043c)<\/h3>\n<p>\u0421\u0430\u043c\u044b\u0439 \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 \u043f\u043e\u0434\u043d\u044f\u0442\u044c \u0432\u0441\u0451 \u0440\u0430\u0437\u043e\u043c \u2014 \u043e\u0431\u0440\u0430\u0437\u00a0<code>redb-tsak-stack<\/code>: \u0432\u043e\u0440\u043a\u0435\u0440 + \u0434\u0430\u0448\u0431\u043e\u0440\u0434 \u0432 \u043e\u0434\u043d\u043e\u043c \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0435. \u041e\u0431\u0440\u0430\u0437\u044b \u043b\u0435\u0436\u0430\u0442 \u043d\u0430\u00a0<a href=\"https:\/\/github.com\/orgs\/redbase-app\/packages?repo_name=redb-tsak\" rel=\"noopener noreferrer nofollow\">\u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 \u043f\u0430\u043a\u0435\u0442\u043e\u0432 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0438<\/a>\u00a0\u2014 \u0442\u0430\u043c \u0438\u0445 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e: \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u00a0<code>redb-tsak-worker<\/code>,\u00a0<code>redb-tsak-web<\/code>\u00a0\u0438 \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0451\u043d\u043d\u044b\u0439\u00a0<code>redb-tsak-stack<\/code>\u00a0(\u043f\u043b\u044e\u0441 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u044b \u043f\u043e\u0434 \u0440\u0430\u0437\u043d\u044b\u0435 TFM). \u041d\u0430\u043c \u0434\u043b\u044f \u0441\u0442\u0430\u0440\u0442\u0430 \u0445\u0432\u0430\u0442\u0438\u0442\u00a0<code>stack<\/code>. \u0412\u043e\u0442\u00a0<code>docker-compose.yml<\/code>\u00a0\u0446\u0435\u043b\u0438\u043a\u043e\u043c:<\/p>\n<pre><code class=\"yaml\"># Minimal redb.Tsak stack \u2014 Worker (API) + Blazor dashboard in one container.# Drop a module as a .tpkg into .\/modules and the worker hot-loads it.## Start:#   docker compose up -d## Dashboard:   http:\/\/localhost:8080   (login admin \/ admin)# Tsak API:    http:\/\/localhost:9090\/api\/health\/live# EchoModule endpoints:  http:\/\/localhost:5099\/api\/notes#   POST (PowerShell): curl.exe -X POST http:\/\/localhost:5099\/api\/notes -H \"Content-Type: application\/json\" -d '{\"tag\":\"work\",\"text\":\"hello\"}'#   POST (cmd.exe):    curl -X POST http:\/\/localhost:5099\/api\/notes -H \"Content-Type: application\/json\" -d \"{\\\"tag\\\":\\\"work\\\",\\\"text\\\":\\\"hello\\\"}\"#   GET:  curl.exe \"http:\/\/localhost:5099\/api\/notes?tag=work\"services:  tsak:    image: ghcr.io\/redbase-app\/redb-tsak-stack:latest    container_name: tsak    restart: unless-stopped    ports:      - \"8080:8080\"   # Blazor dashboard      - \"9090:9090\"   # Tsak management REST API (health, cluster, contexts...)      - \"5099:5099\"   # the EchoModule's own HTTP server (\/api\/notes)    environment:      # redb store on the bundled SQLite \u2014 no external dependencies      Tsak__Storage__Type: Redb      # HMAC secret for API-key auth (any &gt;=16 chars; change for real use)      Tsak__Auth__Secret: \"demo-secret-change-me-please-0123456789\"    volumes:      # your .tpkg modules \u2014 hot-loaded by the worker      - .\/modules:\/app\/worker\/modules<\/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 \u0432\u0430\u0436\u043d\u043e:<\/p>\n<ul>\n<li>\n<p><strong>\u041e\u0431\u0440\u0430\u0437<\/strong>\u00a0<code>ghcr.io\/redbase-app\/redb-tsak-stack:latest<\/code>\u00a0\u2014 \u0432\u043e\u0440\u043a\u0435\u0440 \u0438 \u0434\u0430\u0448\u0431\u043e\u0440\u0434 \u0432 \u043e\u0434\u043d\u043e\u043c \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0435 (\u043f\u043e\u0434 \u0441\u0443\u043f\u0435\u0440\u0432\u0438\u0437\u043e\u0440\u043e\u043c). \u0425\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u2014 \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0439 SQLite, \u0431\u0435\u0437 \u0432\u043d\u0435\u0448\u043d\u0438\u0445 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439.<\/p>\n<\/li>\n<li>\n<p><strong>\u041f\u043e\u0440\u0442\u044b<\/strong>\u00a0\u2014 \u0442\u0435 \u0441\u0430\u043c\u044b\u0435 \u0442\u0440\u0438:\u00a0<code>8080<\/code>\u00a0(\u0434\u0430\u0448\u0431\u043e\u0440\u0434),\u00a0<code>9090<\/code>\u00a0(API \u0432\u043e\u0440\u043a\u0435\u0440\u0430),\u00a0<code>5099<\/code>\u00a0(\u043d\u0430\u0448\u0438 \u0440\u0443\u0447\u043a\u0438).<\/p>\n<\/li>\n<li>\n<p><strong>Volume<\/strong>\u00a0<code>.\/modules:\/app\/worker\/modules<\/code>\u00a0\u2014 \u0432\u043e\u0442 \u0441\u044e\u0434\u0430 \u0438 \u043a\u043b\u0430\u0434\u0451\u043c\u00a0<code>.tpkg<\/code>. \u0412\u043d\u0443\u0442\u0440\u0438 \u0441\u0442\u0435\u043a-\u043e\u0431\u0440\u0430\u0437\u0430 \u043a\u0430\u0442\u0430\u043b\u043e\u0433 \u043c\u043e\u0434\u0443\u043b\u0435\u0439 \u2014\u00a0<code>\/app\/worker\/modules<\/code>, \u0438 \u044d\u0442\u043e \u0440\u043e\u0432\u043d\u043e \u0442\u043e\u0442 \u043f\u0443\u0442\u044c, \u0447\u0442\u043e \u043f\u043e\u0434\u0445\u0432\u0430\u0442\u044b\u0432\u0430\u0435\u0442 hot-reload.<\/p>\n<\/li>\n<li>\n<p><code><strong>Tsak__Auth__Secret<\/strong><\/code>\u00a0\u2014 \u0441\u0435\u043a\u0440\u0435\u0442 \u0434\u043b\u044f HMAC-\u043f\u043e\u0434\u043f\u0438\u0441\u0438 API-\u043a\u043b\u044e\u0447\u0435\u0439; \u0434\u043b\u044f \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0433\u043e\u043d\u0430 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043b\u044e\u0431\u043e\u0433\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u226516 \u0441\u0438\u043c\u0432\u043e\u043b\u043e\u0432.<\/p>\n<\/li>\n<\/ul>\n<p>\u0420\u0430\u0441\u043a\u043b\u0430\u0434\u043a\u0430 \u043d\u0430 \u0445\u043e\u0441\u0442\u0435:<\/p>\n<pre><code>tsak\/\u251c\u2500 docker-compose.yml\u2514\u2500 modules\/   \u2514\u2500 EchoModule.tpkg<\/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\u043d\u0438\u043c\u0430\u0435\u043c:<\/p>\n<pre><code>docker compose up -d<\/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\u0448\u0431\u043e\u0440\u0434 \u2014\u00a0<a href=\"http:\/\/localhost:8080\" rel=\"noopener noreferrer nofollow\"><code>http:\/\/localhost:8080<\/code><\/a>\u00a0(admin \/ admin). \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0440\u0443\u0447\u043a\u0438 (PowerShell):<\/p>\n<pre><code>curl.exe -X POST http:\/\/localhost:5099\/api\/notes -H \"Content-Type: application\/json\" -d '{\"tag\":\"work\",\"text\":\"hello\"}'curl.exe \"http:\/\/localhost:5099\/api\/notes?tag=work\"<\/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>\u0412\u0441\u0451. \u0422\u043e\u0442 \u0436\u0435 \u043c\u043e\u0434\u0443\u043b\u044c, \u0447\u0442\u043e \u043c\u044b \u043e\u0442\u043b\u0430\u0436\u0438\u0432\u0430\u043b\u0438 \u0432 \u043a\u043e\u043d\u0441\u043e\u043b\u0438, \u0442\u0435\u043f\u0435\u0440\u044c \u043a\u0440\u0443\u0442\u0438\u0442\u0441\u044f \u0432 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0435 \u0441 \u0434\u0430\u0448\u0431\u043e\u0440\u0434\u043e\u043c. \u041e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u043c\u043e\u0434\u0443\u043b\u044c \u2014 \u043f\u0435\u0440\u0435\u0441\u043e\u0431\u0440\u0430\u043b\u0438\u00a0<code>.tpkg<\/code>, \u043f\u0435\u0440\u0435\u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043b\u0438 \u0432\u00a0<code>modules\/<\/code>, \u0447\u0435\u0440\u0435\u0437 ~10 \u0441\u0435\u043a\u0443\u043d\u0434 \u043f\u043e\u0434\u0445\u0432\u0430\u0442\u0438\u0442\u0441\u044f.<\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/c3b\/fe7\/3fc\/c3bfe73fc3b1a79bb73498af98676460.png\" alt=\"docker compose up \u2014 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u043f\u043e\u0434\u043d\u044f\u0442, \u0434\u0430\u0448\u0431\u043e\u0440\u0434 \u043d\u0430 8080 \u0441 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u043e\u043c echo\" title=\"docker compose up \u2014 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u043f\u043e\u0434\u043d\u044f\u0442, \u0434\u0430\u0448\u0431\u043e\u0440\u0434 \u043d\u0430 8080 \u0441 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u043e\u043c echo\" width=\"1317\" height=\"1122\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/c3b\/fe7\/3fc\/c3bfe73fc3b1a79bb73498af98676460.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/c3b\/fe7\/3fc\/c3bfe73fc3b1a79bb73498af98676460.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption><strong>docker compose up \u2014 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u043f\u043e\u0434\u043d\u044f\u0442, \u0434\u0430\u0448\u0431\u043e\u0440\u0434 \u043d\u0430 8080 \u0441 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u043e\u043c echo<\/strong><\/figcaption><\/div>\n<\/figure>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/4b5\/d36\/f08\/4b5d36f08a3e3e9bd164bec92bb362da.png\" alt=\"docker compose up \u2014 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u043f\u043e\u0434\u043d\u044f\u0442, \u0434\u0430\u0448\u0431\u043e\u0440\u0434 \u043d\u0430 8080 \u0441 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u043e\u043c echo\" title=\"docker compose up \u2014 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u043f\u043e\u0434\u043d\u044f\u0442, \u0434\u0430\u0448\u0431\u043e\u0440\u0434 \u043d\u0430 8080 \u0441 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u043e\u043c echo\" width=\"1475\" height=\"898\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/4b5\/d36\/f08\/4b5d36f08a3e3e9bd164bec92bb362da.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/4b5\/d36\/f08\/4b5d36f08a3e3e9bd164bec92bb362da.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption><strong>docker compose up \u2014 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u043f\u043e\u0434\u043d\u044f\u0442, \u0434\u0430\u0448\u0431\u043e\u0440\u0434 \u043d\u0430 8080 \u0441 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u043e\u043c echo<\/strong><\/figcaption><\/div>\n<\/figure>\n<hr\/>\n<h3>\u042d\u043d\u0442\u0435\u0440\u043f\u0440\u0430\u0439\u0437-\u0434\u0435\u043f\u043b\u043e\u0439 \u21162: \u0431\u0435\u0437 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430 (standalone-\u0430\u0440\u0445\u0438\u0432)<\/h3>\n<p>\u041d\u0435 \u0445\u043e\u0442\u0438\u0442\u0435 \u0434\u043e\u043a\u0435\u0440 \u2014 \u043d\u0435 \u043d\u0430\u0434\u043e. \u041d\u0430\u00a0<a href=\"https:\/\/github.com\/redbase-app\/redb-tsak\/releases\" rel=\"noopener noreferrer nofollow\">\u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 \u0440\u0435\u043b\u0438\u0437\u043e\u0432<\/a>\u00a0\u043b\u0435\u0436\u0430\u0442 \u0441\u0430\u043c\u043e\u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u044b\u0435 \u0430\u0440\u0445\u0438\u0432\u044b \u043f\u043e\u0434 \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0443 \u2014 \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0442\u0435\u0433\u00a0<a href=\"https:\/\/github.com\/redbase-app\/redb-tsak\/releases\/tag\/v3.2.0\" rel=\"noopener noreferrer nofollow\">v3.2.0<\/a>\u00a0\u0441 \u0430\u0440\u0445\u0438\u0432\u0430\u043c\u0438 \u043f\u043e\u0434\u00a0<code>win-x64<\/code>,\u00a0<code>linux-x64<\/code>\u00a0\u0438 \u0442.\u0434. (\u043a\u0430\u0436\u0434\u044b\u0439 \u2014 \u043f\u043e\u0434\u043f\u0438\u0441\u0430\u043d cosign, \u0440\u044f\u0434\u043e\u043c \u043b\u0435\u0436\u0430\u0442\u00a0<code>.bundle<\/code>\u00a0\u0438 SBOM \u0434\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u0446\u0435\u043b\u043e\u0441\u0442\u043d\u043e\u0441\u0442\u0438).<\/p>\n<p>\u0412\u043d\u0443\u0442\u0440\u0438 \u0440\u0430\u0441\u043f\u0430\u043a\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u0430\u0440\u0445\u0438\u0432\u0430 (<code>redb-tsak-&lt;\u0432\u0435\u0440\u0441\u0438\u044f&gt;-&lt;\u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0430&gt;<\/code>) \u2014 \u0442\u0430\u043a\u0430\u044f \u0440\u0430\u0441\u043a\u043b\u0430\u0434\u043a\u0430:<\/p>\n<pre><code>redb-tsak-3.2.0-win-x64\/\u251c\u2500 worker\/     &lt;- \u0440\u0430\u043d\u0442\u0430\u0439\u043c: redb.Tsak.Worker.exe (+ Libs\/shared \u0441 \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440\u0430\u043c\u0438, + modules\/)\u251c\u2500 web\/        &lt;- \u0434\u0430\u0448\u0431\u043e\u0440\u0434: redb.Tsak.Web.exe\u251c\u2500 cli\/        &lt;- \u043a\u043e\u043d\u0441\u043e\u043b\u044c\u043d\u0430\u044f \u0443\u0442\u0438\u043b\u0438\u0442\u0430 tsak\u251c\u2500 scripts\/    &lt;- start-worker \/ start-web \/ start-stack (.bat \/ .ps1 \/ .sh)\u251c\u2500 README.txt\u251c\u2500 LICENSE, NOTICE<\/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\u043f\u0443\u0441\u043a \u2014 \u0438\u0437\u00a0<code>scripts\/<\/code>:<\/p>\n<ul>\n<li>\n<p><code>start-stack<\/code>\u00a0\u2014 \u043f\u043e\u0434\u043d\u044f\u0442\u044c \u0432\u043e\u0440\u043a\u0435\u0440 \u0438 \u0434\u0430\u0448\u0431\u043e\u0440\u0434 \u0440\u0430\u0437\u043e\u043c;<\/p>\n<\/li>\n<li>\n<p><code>start-worker<\/code>\u00a0\u2014 \u0442\u043e\u043b\u044c\u043a\u043e \u0440\u0430\u043d\u0442\u0430\u0439\u043c;<\/p>\n<\/li>\n<li>\n<p><code>start-web<\/code>\u00a0\u2014 \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u0430\u0448\u0431\u043e\u0440\u0434.<\/p>\n<\/li>\n<\/ul>\n<p>\u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043d\u0430 Windows:<\/p>\n<pre><code class=\"powershell\">.\\scripts\\start-stack.ps1<\/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\u0440\u0442\u044b \u0442\u0435 \u0436\u0435: \u0432\u043e\u0440\u043a\u0435\u0440 \u2014\u00a0<code>9090<\/code>, \u0434\u0430\u0448\u0431\u043e\u0440\u0434 \u2014\u00a0<code>8080<\/code>\u00a0(\u0441\u043a\u0440\u0438\u043f\u0442\u044b \u0437\u0430\u0434\u0430\u044e\u0442\u00a0<code>ASPNETCORE_URLS<\/code>; \u0435\u0441\u043b\u0438 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c\u00a0<code>web<\/code>\u00a0\u0441\u043e\u0432\u0441\u0435\u043c \u0433\u043e\u043b\u044b\u043c \u043c\u0438\u043c\u043e \u0441\u043a\u0440\u0438\u043f\u0442\u0430 \u2014 \u0432\u0441\u043f\u043e\u043c\u043d\u0438\u0442\u0435 \u043f\u0440\u043e \u0434\u0435\u0444\u043e\u043b\u0442\u00a0<code>5000<\/code>\u00a0\u0438\u0437 \u0440\u0430\u0437\u0434\u0435\u043b\u0430 \u043e \u043f\u043e\u0440\u0442\u0430\u0445).<\/p>\n<p><strong>\u041a\u0443\u0434\u0430 \u043a\u043b\u0430\u0441\u0442\u044c \u043c\u043e\u0434\u0443\u043b\u044c.<\/strong>\u00a0\u0412 standalone-\u0440\u0430\u0441\u043a\u043b\u0430\u0434\u043a\u0435 \u043c\u043e\u0434\u0443\u043b\u0438 \u043b\u0435\u0436\u0430\u0442 \u0440\u044f\u0434\u043e\u043c \u0441 \u0432\u043e\u0440\u043a\u0435\u0440\u043e\u043c \u2014 \u0432 \u0435\u0433\u043e \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0435\u00a0<code>modules\/<\/code>\u00a0(\u043f\u0443\u0442\u044c \u0431\u0435\u0440\u0451\u0442\u0441\u044f \u0438\u0437\u00a0<code>Tsak:Modules:AssemblyPaths<\/code>). \u041a\u043b\u0430\u0434\u0451\u043c \u043d\u0430\u0448\u00a0<code>EchoModule.tpkg<\/code>\u00a0\u0442\u0443\u0434\u0430:<\/p>\n<pre><code>worker\/\u2514\u2500 modules\/   \u2514\u2500 EchoModule.tpkg<\/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\u00a0<code>Libs\/shared<\/code>\u00a0\u0440\u0443\u043a\u0430\u043c\u0438 \u043d\u0435 \u0442\u0440\u043e\u0433\u0430\u0435\u043c \u2014 \u0442\u0430\u043c \u043e\u0431\u0449\u0438\u0435 \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440\u044b \u043e\u0431\u0440\u0430\u0437\u0430\/\u0434\u0438\u0441\u0442\u0440\u0438\u0431\u0443\u0442\u0438\u0432\u0430, \u043f\u043e\u0432\u0435\u0440\u0445 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0433\u0440\u0443\u0437\u0438\u0442\u0441\u044f \u043d\u0430\u0448 \u043b\u0451\u0433\u043a\u0438\u0439 \u043f\u0430\u043a\u0435\u0442.<\/p>\n<p>\u041b\u0438\u0446\u0435\u043d\u0437\u0438\u044f: \u0431\u0435\u0437 \u043a\u043b\u044e\u0447\u0430 \u0440\u0430\u043d\u0442\u0430\u0439\u043c \u0441\u0442\u0430\u0440\u0442\u0443\u0435\u0442 \u0432 OSS-\u0440\u0435\u0436\u0438\u043c\u0435. \u041f\u0440\u043e-\u0444\u0438\u0447\u0438 (\u0432 \u0442.\u0447. \u043a\u043b\u0430\u0441\u0442\u0435\u0440) \u0432\u043a\u043b\u044e\u0447\u0430\u044e\u0442\u0441\u044f \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0439\u00a0<code>Tsak__Redb__License__0<\/code>\u00a0\u0441 \u0432\u0430\u0448\u0438\u043c JWT \u043b\u0438\u0431\u043e \u043f\u0440\u0430\u0432\u043a\u043e\u0439\u00a0<code>worker\/appsettings.json<\/code>\u00a0\u043f\u0435\u0440\u0435\u0434 \u0437\u0430\u043f\u0443\u0441\u043a\u043e\u043c.<\/p>\n<p>\u0418\u0442\u043e\u0433\u043e \u0434\u0432\u0430 \u043f\u0443\u0442\u0438 \u0434\u0435\u043f\u043b\u043e\u044f \u2014 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u0438\u043b\u0438 \u0430\u0440\u0445\u0438\u0432 \u2014 \u043d\u043e \u043c\u043e\u0434\u0435\u043b\u044c \u043e\u0434\u043d\u0430:\u00a0<strong>\u0430\u0440\u0442\u0435\u0444\u0430\u043a\u0442\u00a0<\/strong><code><strong>.tpkg<\/strong><\/code><strong>\u00a0\u043a\u043b\u0430\u0434\u0451\u0442\u0441\u044f \u0432 \u043f\u0430\u043f\u043a\u0443 \u043c\u043e\u0434\u0443\u043b\u0435\u0439, \u0434\u0430\u043b\u044c\u0448\u0435 hot-reload.<\/strong><\/p>\n<hr\/>\n<h3>\u0414\u0432\u0435 \u0437\u043e\u043d\u044b: \u0440\u0430\u0431\u043e\u0447\u0430\u044f \u0438 \u044d\u043a\u0441\u043f\u043b\u0443\u0430\u0442\u0430\u0446\u0438\u043e\u043d\u043d\u0430\u044f<\/h3>\n<p>\u0421\u043e\u0431\u0435\u0440\u0451\u043c \u043a\u0430\u0440\u0442\u0438\u043d\u0443 \u0446\u0435\u043b\u0438\u043a\u043e\u043c.<\/p>\n<ul>\n<li>\n<p><strong>\u041e\u0442\u043b\u0430\u0434\u043e\u0447\u043d\u0430\u044f \u0437\u043e\u043d\u0430<\/strong>\u00a0\u2014 \u043f\u0440\u043e\u0435\u043a\u0442\u00a0<code>EchoWorker<\/code>. \u041a\u043e\u043d\u0441\u043e\u043b\u044c\u043d\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435, F5, \u0442\u043e\u0447\u043a\u0438 \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u0430, \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u0430\u044f SQLite \u0440\u044f\u0434\u043e\u043c \u0441 exe, \u0433\u043e\u043b\u044b\u0435 \u043b\u043e\u0433\u0438 \u0432 \u043a\u043e\u043d\u0441\u043e\u043b\u044c. \u0417\u0434\u0435\u0441\u044c \u0432\u044b\u00a0<strong>\u043f\u0438\u0448\u0435\u0442\u0435 \u0438 \u043e\u0442\u043b\u0430\u0436\u0438\u0432\u0430\u0435\u0442\u0435<\/strong>\u00a0\u043c\u0430\u0440\u0448\u0440\u0443\u0442\u044b. \u0418\u0442\u0435\u0440\u0430\u0446\u0438\u044f \u2014 \u00ab\u043f\u043e\u043f\u0440\u0430\u0432\u0438\u043b \u2192 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u043b \u2192 \u043f\u043e\u0442\u044b\u043a\u0430\u043b curl\u00bb.<\/p>\n<\/li>\n<li>\n<p><strong>\u042d\u043a\u0441\u043f\u043b\u0443\u0430\u0442\u0430\u0446\u0438\u043e\u043d\u043d\u0430\u044f \u0437\u043e\u043d\u0430<\/strong>\u00a0\u2014 Tsak (\u0434\u043e\u043a\u0435\u0440 \u0438\u043b\u0438 \u0430\u0440\u0445\u0438\u0432). \u0422\u043e\u0442 \u0436\u0435 \u043c\u043e\u0434\u0443\u043b\u044c, \u043d\u043e \u0441 \u0434\u0430\u0448\u0431\u043e\u0440\u0434\u043e\u043c, \u043c\u0435\u0442\u0440\u0438\u043a\u0430\u043c\u0438, hot-reload \u0438 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u043c \u043d\u0430 \u043b\u0435\u0442\u0443. \u0417\u0434\u0435\u0441\u044c \u0432\u044b\u00a0<strong>\u0434\u0435\u043f\u043b\u043e\u0438\u0442\u0435 \u0438 \u0440\u0443\u043b\u0438\u0442\u0435<\/strong>. \u0418\u0442\u0435\u0440\u0430\u0446\u0438\u044f \u2014 \u00ab\u043f\u0435\u0440\u0435\u0441\u043e\u0431\u0440\u0430\u043b\u00a0<code>.tpkg<\/code>\u00a0\u2192 \u0441\u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043b \u0432\u00a0<code>modules\/<\/code>\u00a0\u2192 \u043f\u043e\u0434\u0445\u0432\u0430\u0442\u0438\u043b\u043e\u0441\u044c\u00bb.<\/p>\n<\/li>\n<\/ul>\n<p>\u0418 \u043a\u043b\u044e\u0447\u0435\u0432\u043e\u0435:\u00a0<strong>\u043c\u0435\u0436\u0434\u0443 \u044d\u0442\u0438\u043c\u0438 \u0437\u043e\u043d\u0430\u043c\u0438 \u043d\u0435 \u043c\u0435\u043d\u044f\u0435\u0442\u0441\u044f \u043d\u0438 \u0441\u0442\u0440\u043e\u0447\u043a\u0438 \u0432 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430\u0445<\/strong>.\u00a0<code>InitRoute.main<\/code>\u00a0\u043e\u0434\u0438\u043d \u0438 \u0442\u043e\u0442 \u0436\u0435. \u0420\u0430\u0437\u043d\u0438\u0446\u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u0432 \u043e\u0431\u0432\u044f\u0437\u043a\u0435: \u0432 \u043e\u0442\u043b\u0430\u0434\u043a\u0435 \u0435\u0451 \u043f\u0438\u0448\u0435\u0442\u0435 \u0432\u044b (\u0434\u0435\u0441\u044f\u0442\u043e\u043a \u0441\u0442\u0440\u043e\u043a\u00a0<code>Program.cs<\/code>), \u0432 \u043f\u0440\u043e\u0434\u0435 \u2014 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 Tsak.<\/p>\n<hr\/>\n<h3>\u0427\u0442\u043e \u0434\u0430\u043b\u044c\u0448\u0435: \u043a\u043b\u0430\u0441\u0442\u0435\u0440 (\u0442\u0438\u0437\u0435\u0440)<\/h3>\n<p>\u0412\u0441\u0451, \u0447\u0442\u043e \u0432\u044b\u0448\u0435 \u2014 \u043f\u0440\u043e \u043e\u0434\u0438\u043d \u0443\u0437\u0435\u043b. \u041d\u043e Tsak \u0443\u043c\u0435\u0435\u0442 \u0438 \u0432 \u043a\u043b\u0430\u0441\u0442\u0435\u0440: \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0432\u043e\u0440\u043a\u0435\u0440\u043e\u0432, \u043e\u0434\u0438\u043d \u043e\u0431\u0449\u0438\u0439 \u0434\u0430\u0448\u0431\u043e\u0440\u0434, \u0440\u0430\u0441\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u043c\u043e\u0434\u0443\u043b\u0435\u0439 \u0438 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043e\u0432 \u043f\u043e \u0443\u0437\u043b\u0430\u043c, \u0432\u044b\u0431\u043e\u0440 \u043b\u0438\u0434\u0435\u0440\u0430, \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u043f\u0435\u0440\u0435\u0445\u0432\u0430\u0442 \u043f\u0440\u0438 \u043f\u0430\u0434\u0435\u043d\u0438\u0438 \u0443\u0437\u043b\u0430 \u2014 \u0438 \u0432\u0441\u044f \u0442\u043e\u043f\u043e\u043b\u043e\u0433\u0438\u044f \u043a\u043b\u0430\u0441\u0442\u0435\u0440\u0430 \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f\u00a0<strong>\u0432 \u0441\u0430\u043c\u043e\u0439 \u0431\u0430\u0437\u0435 redb<\/strong>, \u0431\u0435\u0437 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0439 \u0438\u043d\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b \u043c\u0435\u043c\u0431\u0435\u0440\u0448\u0438\u043f\u0430 (\u0431\u0435\u0437 ZooKeeper\/etcd\/Consul). \u041e\u0442\u0434\u0435\u043b\u044c\u043d\u043e \u0436\u0438\u0432\u0451\u0442 \u0438 \u00abactive-passive\u00bb \u0434\u043b\u044f \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043e\u0432: \u043e\u0434\u0438\u043d \u0438 \u0442\u043e\u0442 \u0436\u0435 \u043c\u0430\u0440\u0448\u0440\u0443\u0442-\u043a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440 \u043a\u0440\u0443\u0442\u0438\u0442\u0441\u044f \u0440\u043e\u0432\u043d\u043e \u043d\u0430 \u043e\u0434\u043d\u043e\u043c \u0443\u0437\u043b\u0435, \u0430 \u0435\u0441\u043b\u0438 \u0442\u043e\u0442 \u043f\u0430\u0434\u0430\u0435\u0442 \u2014 \u0435\u0433\u043e \u043f\u043e\u0434\u0445\u0432\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u0434\u0440\u0443\u0433\u043e\u0439.<\/p>\n<p>\u041d\u043e \u044d\u0442\u043e \u2014 \u0442\u0435\u043c\u0430 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439 \u0441\u0442\u0430\u0442\u044c\u0438. \u0417\u0434\u0435\u0441\u044c \u0443 \u043d\u0430\u0441 \u0431\u044b\u043b\u0430 \u0432\u0432\u043e\u0434\u043d\u0430\u044f: \u043a\u0430\u043a \u0437\u0430 \u0432\u0435\u0447\u0435\u0440 \u0441\u043e\u0431\u0440\u0430\u0442\u044c \u0434\u0432\u0430 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430, \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0441\u0435\u0431\u0435 \u043e\u0442\u043b\u0430\u0434\u043e\u0447\u043d\u044b\u0439 \u0432\u043e\u0440\u043a\u0435\u0440, \u0430 \u043f\u043e\u0442\u043e\u043c \u0442\u0435\u043c \u0436\u0435 \u043a\u043e\u0434\u043e\u043c \u0437\u0430\u0435\u0445\u0430\u0442\u044c \u0432 \u044d\u043d\u0442\u0435\u0440\u043f\u0440\u0430\u0439\u0437-\u0440\u0430\u043d\u0442\u0430\u0439\u043c. \u041f\u0440\u043e \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u043e\u0440, \u043b\u0438\u0434\u0435\u0440\u0430 \u0438 failover \u043f\u043e\u0433\u043e\u0432\u043e\u0440\u0438\u043c \u043f\u0440\u0435\u0434\u043c\u0435\u0442\u043d\u043e \u0434\u0430\u043b\u044c\u0448\u0435.<\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/43b\/b51\/cd5\/43bb51cd5b877752c38d6495e3e6493c.png\" alt=\"dashboard\" title=\"dashboard\" width=\"1701\" height=\"1343\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/43b\/b51\/cd5\/43bb51cd5b877752c38d6495e3e6493c.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/43b\/b51\/cd5\/43bb51cd5b877752c38d6495e3e6493c.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>dashboard<\/figcaption><\/div>\n<\/figure>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/9dd\/864\/e2f\/9dd864e2f53507ef7ab1497cdc314276.png\" alt=\"monitoring\" title=\"monitoring\" width=\"1692\" height=\"1341\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/9dd\/864\/e2f\/9dd864e2f53507ef7ab1497cdc314276.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/9dd\/864\/e2f\/9dd864e2f53507ef7ab1497cdc314276.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>monitoring<\/figcaption><\/div>\n<\/figure>\n<hr\/>\n<h3>\u0418\u0442\u043e\u0433<\/h3>\n<p>\u041c\u044b \u043f\u0440\u043e\u0448\u043b\u0438 \u0432\u0435\u0441\u044c \u043f\u0443\u0442\u044c \u043d\u0430 \u043e\u0434\u043d\u043e\u043c \u043a\u0440\u043e\u0448\u0435\u0447\u043d\u043e\u043c \u043f\u0440\u0438\u043c\u0435\u0440\u0435:<\/p>\n<ol>\n<li>\n<p>\u043d\u0430\u043f\u0438\u0441\u0430\u043b\u0438\u00a0<strong>\u0434\u0432\u0430 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430<\/strong>\u00a0\u043d\u0430 redb.Route (POST \u2014 \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0432 redb\/SQLite, GET \u2014 \u0434\u043e\u0441\u0442\u0430\u0442\u044c \u0441\u0435\u0440\u0432\u0435\u0440\u043d\u044b\u043c\u00a0<code>Where(...).ToListAsync()<\/code>);<\/p>\n<\/li>\n<li>\n<p>\u0441\u0434\u0435\u043b\u0430\u043b\u0438\u00a0<strong>\u0441\u0432\u043e\u0439 \u043e\u0442\u043b\u0430\u0434\u043e\u0447\u043d\u044b\u0439 \u0432\u043e\u0440\u043a\u0435\u0440<\/strong>\u00a0\u2014 \u043a\u043e\u043d\u0441\u043e\u043b\u044c\u043a\u0443 \u043f\u043e\u0434 F5, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0437\u043e\u0432\u0451\u0442 \u0442\u0443 \u0436\u0435\u00a0<code>InitRoute.main<\/code>;<\/p>\n<\/li>\n<li>\n<p>\u0443\u043f\u0430\u043a\u043e\u0432\u0430\u043b\u0438 \u043c\u043e\u0434\u0443\u043b\u044c \u0432\u00a0<code><strong>.tpkg<\/strong><\/code>\u00a0(DLL +\u00a0<code>manifest.json<\/code>\u00a0+\u00a0<code>config.json<\/code>\u00a0\u0441 \u0438\u043c\u0435\u043d\u0435\u043c \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430);<\/p>\n<\/li>\n<li>\n<p>\u0443\u0432\u0438\u0434\u0435\u043b\u0438, \u043a\u0430\u043a Tsak\u00a0<strong>\u0441\u0430\u043c \u043f\u043e\u0434\u0445\u0432\u0430\u0442\u044b\u0432\u0430\u0435\u0442<\/strong>\u00a0\u043f\u0430\u043a\u0435\u0442 \u0438\u0437 \u043f\u0430\u043f\u043a\u0438\u00a0<code>modules\/<\/code>\u00a0\u043f\u043e hot-reload;<\/p>\n<\/li>\n<li>\n<p>\u043f\u043e\u0440\u0443\u043b\u0438\u043b\u0438 \u0438\u0437\u00a0<strong>\u0434\u0430\u0448\u0431\u043e\u0440\u0434\u0430<\/strong>\u00a0(\u0441\u0442\u0430\u0440\u0442\/\u0441\u0442\u043e\u043f\/\u0440\u0435\u0441\u0442\u0430\u0440\u0442 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u043e\u0432, \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0437\u0430\u043f\u043e\u043c\u0438\u043d\u0430\u0435\u0442\u0441\u044f);<\/p>\n<\/li>\n<li>\n<p>\u0440\u0430\u0437\u043e\u0431\u0440\u0430\u043b\u0438\u0441\u044c \u0441\u00a0<strong>\u043f\u043e\u0440\u0442\u0430\u043c\u0438<\/strong>\u00a0(9090 \u2014 API \u0432\u043e\u0440\u043a\u0435\u0440\u0430, 5099 \u2014 \u043d\u0430\u0448\u0438 \u0440\u0443\u0447\u043a\u0438, 8080\/5000 \u2014 \u0434\u0430\u0448\u0431\u043e\u0440\u0434);<\/p>\n<\/li>\n<li>\n<p>\u0440\u0430\u0437\u0432\u0435\u0440\u043d\u0443\u043b\u0438 \u0434\u0432\u0443\u043c\u044f \u0441\u043f\u043e\u0441\u043e\u0431\u0430\u043c\u0438:\u00a0<strong>\u0434\u043e\u043a\u0435\u0440\u043e\u043c<\/strong>\u00a0(\u0432\u0435\u0441\u044c\u00a0<code>docker-compose.yml<\/code>) \u0438\u00a0<strong>\u0431\u0435\u0437 \u0434\u043e\u043a\u0435\u0440\u0430<\/strong>\u00a0(\u0430\u0440\u0445\u0438\u0432 \u0441 \u0440\u0435\u043b\u0438\u0437\u043e\u0432).<\/p>\n<\/li>\n<\/ol>\n<p>\u041e\u0434\u0438\u043d \u0438 \u0442\u043e\u0442 \u0436\u0435 \u043a\u043e\u0434 \u2014 \u0438 \u0443\u0434\u043e\u0431\u043d\u0430\u044f \u043e\u0442\u043b\u0430\u0434\u043a\u0430, \u0438 \u044d\u043d\u0442\u0435\u0440\u043f\u0440\u0430\u0439\u0437-\u0440\u0430\u043d\u0442\u0430\u0439\u043c. \u0414\u0430\u043b\u044c\u0448\u0435 \u2014 \u043a\u043b\u0430\u0441\u0442\u0435\u0440.<\/p>\n<p>\u0412\u0435\u0441\u044c \u043f\u0440\u043e\u0435\u043a\u0442:\u00a0<code>redb.Route\/demos\/EchoWorkerDemo<\/code>\u00a0\u0432 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438. \u041a\u043e\u043f\u0438\u0440\u0443\u0439\u0442\u0435, \u043f\u043e\u0432\u0442\u043e\u0440\u044f\u0439\u0442\u0435, \u043b\u043e\u043c\u0430\u0439\u0442\u0435.<\/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\/1055050\/\">https:\/\/habr.com\/ru\/articles\/1055050\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>redb.tsak worker\u0421\u0435\u0440\u0438\u044f:\u00a0redb ecosystem \/ redb.Route redb.Tsak\u0415\u0441\u0442\u044c \u0443 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0433\u043e \u043a\u043e\u0434\u0430 \u043e\u0434\u043d\u0430 \u043d\u0435\u043f\u0440\u0438\u044f\u0442\u043d\u0430\u044f \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e\u0441\u0442\u044c. \u041d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u043f\u0430\u0440\u0443 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043e\u0432 \u2014 \u00ab\u043f\u0440\u0438\u043d\u044f\u043b HTTP, \u043f\u043e\u043b\u043e\u0436\u0438\u043b \u0432 \u0431\u0430\u0437\u0443, \u043e\u0442\u0434\u0430\u043b \u043e\u0431\u0440\u0430\u0442\u043d\u043e\u00bb \u2014 \u0434\u0435\u043b\u043e \u043d\u0430 \u043f\u043e\u043b\u0447\u0430\u0441\u0430. \u0410 \u0432\u043e\u0442 \u0434\u043e\u0432\u0435\u0441\u0442\u0438 \u044d\u0442\u043e \u0434\u043e \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f, \u043a\u043e\u0433\u0434\u0430 \u043e\u043d\u043e \u043a\u0440\u0443\u0442\u0438\u0442\u0441\u044f \u0432 \u043f\u0440\u043e\u0434\u0435, \u0441\u0430\u043c\u043e \u043f\u043e\u0434\u043d\u0438\u043c\u0430\u0435\u0442\u0441\u044f, \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u043c\u0435\u0442\u0440\u0438\u043a\u0438, \u0443\u043c\u0435\u0435\u0442 \u043e\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0442\u044c\/\u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0435 \u043a\u0443\u0441\u043a\u0438 \u0440\u0443\u043a\u0430\u043c\u0438 \u0438 \u0440\u0430\u0437\u0432\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0431\u0435\u0437 \u043f\u0435\u0440\u0435\u0441\u0431\u043e\u0440\u043a\u0438 \u2014 \u044d\u0442\u043e \u043e\u0431\u044b\u0447\u043d\u043e \u0441\u043e\u0432\u0441\u0435\u043c \u0434\u0440\u0443\u0433\u0430\u044f \u0438\u0441\u0442\u043e\u0440\u0438\u044f \u0438 \u0441\u043e\u0432\u0441\u0435\u043c \u0434\u0440\u0443\u0433\u043e\u0439 \u0441\u0442\u0435\u043a.\u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u043f\u043e\u043a\u0430\u0436\u0443, \u0447\u0442\u043e \u0432 \u0441\u0432\u044f\u0437\u043a\u0435\u00a0redb.Route + redb.Tsak\u00a0\u044d\u0442\u043e \u0431\u0443\u043a\u0432\u0430\u043b\u044c\u043d\u043e \u043e\u0434\u0438\u043d \u0438 \u0442\u043e\u0442 \u0436\u0435 \u043a\u043e\u0434. \u041c\u044b:\u043d\u0430\u043f\u0438\u0448\u0435\u043c\u00a0\u0434\u0432\u0430 \u043f\u0440\u043e\u0441\u0442\u0435\u0439\u0448\u0438\u0445 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430\u00a0(POST \u2014 \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c, GET \u2014 \u0434\u043e\u0441\u0442\u0430\u0442\u044c \u043f\u043e \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0443) \u043d\u0430 redb.Route;\u0441\u0434\u0435\u043b\u0430\u0435\u043c\u00a0\u0441\u0432\u043e\u0439 \u0432\u043e\u0440\u043a\u0435\u0440 \u0434\u043b\u044f \u043e\u0442\u043b\u0430\u0434\u043a\u0438\u00a0\u2014 \u043e\u0431\u044b\u0447\u043d\u043e\u0435 \u043a\u043e\u043d\u0441\u043e\u043b\u044c\u043d\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043c\u043e\u0436\u043d\u043e \u0433\u043e\u043d\u044f\u0442\u044c \u043f\u043e\u0434 F5 \u0441 \u0442\u043e\u0447\u043a\u0430\u043c\u0438 \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u0430;\u0430 \u043f\u043e\u0442\u043e\u043c,\u00a0\u043d\u0435 \u043c\u0435\u043d\u044f\u044f \u043d\u0438 \u0441\u0442\u0440\u043e\u0447\u043a\u0438 \u0432 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430\u0445, \u0443\u043f\u0430\u043a\u0443\u0435\u043c \u0432\u0441\u0451 \u0432 \u043c\u043e\u0434\u0443\u043b\u044c \u0438 \u0437\u0430\u043a\u0438\u043d\u0435\u043c \u0432 Tsak \u2014 \u0438 \u0442\u043e\u0442 \u0436\u0435 \u043a\u0440\u043e\u0448\u0435\u0447\u043d\u044b\u0439 \u043f\u0440\u043e\u0435\u043a\u0442 \u043f\u043e\u043b\u0443\u0447\u0438\u0442 \u0434\u0430\u0448\u0431\u043e\u0440\u0434, hot-reload, \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430\u043c\u0438 \u0438 \u044d\u043d\u0442\u0435\u0440\u043f\u0440\u0430\u0439\u0437-\u0434\u0435\u043f\u043b\u043e\u0439 (\u0434\u043e\u043a\u0435\u0440\u043e\u043c \u0438 \u0431\u0435\u0437 \u0434\u043e\u043a\u0435\u0440\u0430).\u041f\u0440\u043e\u0435\u043a\u0442 \u0446\u0435\u043b\u0438\u043a\u043e\u043c \u043b\u0435\u0436\u0438\u0442 \u0432 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438 (redb.Route\/demos\/EchoWorkerDemo), \u0438 \u044f \u0432\u0441\u0442\u0430\u0432\u043b\u044e \u0435\u0433\u043e \u0441\u044e\u0434\u0430 \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u2014 \u043c\u043e\u0436\u043d\u043e \u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u044f\u0442\u044c.\u0426\u0438\u043a\u043b \u043f\u0440\u043e redb \u0438 redb.Route.\u00a0\u042d\u0442\u043e \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0435\u043d\u0438\u0435 \u0441\u0435\u0440\u0438\u0438, \u0441\u0432\u0435\u0436\u0438\u0435 \u0441\u0442\u0430\u0442\u044c\u0438 \u2014 \u0441\u0432\u0435\u0440\u0445\u0443:redb.Route \u2014 \u0443\u0445\u043e\u0434\u0438\u043c \u043e\u0442 MassTransit, \u0438\u0434\u0451\u043c \u043a Apache Camel: Kafka, Scatter\u2011Gather \u0438 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438Apache Camel \u043f\u043e\u0434 .NET: HTTP-\u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440 \u0431\u0435\u0437 ASP.NET MVC + Content-Based Routerredb.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\u043eredb \u2014 \u0442\u0438\u043f\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0435 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435 \u0434\u043b\u044f .NET \u043f\u043e\u0432\u0435\u0440\u0445 Postgres\/MSSQL: \u0431\u0435\u0437 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0439, \u0431\u0435\u0437 Include, \u0441 \u043f\u043e\u043b\u043d\u044b\u043c LINQ\u041f\u043e\u043b\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a \u2014 \u0432\u00a0\u043f\u0440\u043e\u0444\u0438\u043b\u0435. \u0418\u0441\u0445\u043e\u0434\u043d\u0438\u043a\u0438:\u00a0github.com\/redbase-app. \u041f\u0440\u043e \u0441\u0430\u043c\u0443 \u0411\u0414 redb:\u00a0redb.ru.\u0417\u0434\u0435\u0441\u044c \u2014 \u0432\u0432\u043e\u0434\u043d\u0430\u044f \u043f\u0440\u043e \u0432\u043e\u0440\u043a\u0435\u0440\u044b \u0438 \u0434\u0435\u043f\u043b\u043e\u0439; \u043f\u0440\u043e \u043a\u043b\u0430\u0441\u0442\u0435\u0440, \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u043e\u0440 \u0438 failover \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e.\u0427\u0442\u043e \u0432\u043e\u043e\u0431\u0449\u0435 \u0434\u0435\u043b\u0430\u0435\u043c\u041c\u0438\u043d\u0438-\u0441\u0435\u0440\u0432\u0438\u0441 \u0437\u0430\u043c\u0435\u0442\u043e\u043a. \u0414\u0432\u0435 \u0440\u0443\u0447\u043a\u0438 \u043d\u0430 \u043e\u0434\u043d\u043e\u043c HTTP-\u043f\u043e\u0440\u0442\u0443:POST \/api\/notes\u00a0\u0441 \u0442\u0435\u043b\u043e\u043c\u00a0{&#171;tag&#187;:&#187;work&#187;,&#187;text&#187;:&#187;hello&#187;}\u00a0\u2014 \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0437\u0430\u043c\u0435\u0442\u043a\u0443 \u0432 \u0431\u0430\u0437\u0443 redb (\u043d\u0430 SQLite);GET \/api\/notes?tag=work\u00a0\u2014 \u0432\u0435\u0440\u043d\u0443\u0442\u044c \u0437\u0430\u043c\u0435\u0442\u043a\u0438 \u0441 \u044d\u0442\u0438\u043c \u0442\u0435\u0433\u043e\u043c; \u0432\u044b\u0431\u043e\u0440\u043a\u0430 \u2014 \u0441\u0435\u0440\u0432\u0435\u0440\u043d\u044b\u043c \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u043c\u00a0Where(&#8230;).ToListAsync(), \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 \u0431\u0435\u0440\u0451\u0442\u0441\u044f \u043f\u0440\u044f\u043c\u043e \u0438\u0437 query-\u0441\u0442\u0440\u043e\u043a\u0438.\u041d\u0438\u0447\u0435\u0433\u043e \u043b\u0438\u0448\u043d\u0435\u0433\u043e. \u0417\u0430\u0434\u0430\u0447\u0430 \u0441\u0442\u0430\u0442\u044c\u0438 \u043d\u0435 \u0432 \u0431\u0438\u0437\u043d\u0435\u0441-\u043b\u043e\u0433\u0438\u043a\u0435, \u0430 \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c\u00a0\u0436\u0438\u0437\u043d\u0435\u043d\u043d\u044b\u0439 \u0446\u0438\u043a\u043b: \u043a\u0430\u043a \u043e\u0434\u0438\u043d \u0438 \u0442\u043e\u0442 \u0436\u0435 \u043d\u0430\u0431\u043e\u0440 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043e\u0432 \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u0436\u0438\u0432\u0451\u0442 \u0432 \u043e\u0442\u043b\u0430\u0434\u043e\u0447\u043d\u043e\u0439 \u043a\u043e\u043d\u0441\u043e\u043b\u0438, \u0430 \u043f\u043e\u0442\u043e\u043c \u2014 \u0432 \u044d\u043d\u0442\u0435\u0440\u043f\u0440\u0430\u0439\u0437-\u0440\u0430\u043d\u0442\u0430\u0439\u043c\u0435.redb.tsakredb.trsak.web logsredb.tsak.web dashboard\u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430: \u0434\u0432\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430, \u043e\u0434\u043d\u0430 \u0442\u043e\u0447\u043a\u0430 \u0432\u0445\u043e\u0434\u0430\u0420\u0430\u0441\u043a\u043b\u0430\u0434\u043a\u0430 \u0442\u0430\u043a\u0430\u044f:EchoWorkerDemo\/\u251c\u2500 EchoModule\/            &lt;- \u043f\u0440\u043e\u0435\u043a\u0442 1: \u043c\u043e\u0434\u0443\u043b\u044c (class library \u2192 EchoModule.tpkg)\u2502  \u251c\u2500 InitRoute.cs        &lt;- main(IRouteContext): \u0434\u0432\u0430 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430 + \u043a\u043b\u0430\u0441\u0441 Note\u2502  \u251c\u2500 manifest.json       &lt;- { Name, Version, EntryPoints: [&#171;EchoModule.dll&#187;] }\u2502  \u251c\u2500 EchoModule.config.json  &lt;- \u0438\u043c\u044f \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 (ContextName) + AutoStart\u2502  \u2514\u2500 EchoModule.csproj   &lt;- \u0441\u0441\u044b\u043b\u043a\u0438 \u043d\u0430 \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440\u044b + \u0442\u0430\u0440\u0433\u0435\u0442 PackTpkg\u2514\u2500 EchoWorker\/            &lt;- \u043f\u0440\u043e\u0435\u043a\u0442 2: \u043e\u0442\u043b\u0430\u0434\u043e\u0447\u043d\u044b\u0439 \u0445\u043e\u0441\u0442 (exe)   \u251c\u2500 Program.cs          &lt;- redb \u043d\u0430 SQLite + \u0432\u044b\u0437\u043e\u0432 InitRoute.main + Start   \u2514\u2500 EchoWorker.csproj\u0418\u0434\u0435\u044f, \u0432\u043e\u043a\u0440\u0443\u0433 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0432\u0441\u0451 \u0432\u0435\u0440\u0442\u0438\u0442\u0441\u044f:\u00a0\u0442\u043e\u0447\u043a\u0430 \u0432\u0445\u043e\u0434\u0430 \u043e\u0434\u043d\u0430 \u2014\u00a0InitRoute.main(IRouteContext). \u0415\u0451 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442 \u0438 \u043d\u0430\u0448 \u043e\u0442\u043b\u0430\u0434\u043e\u0447\u043d\u044b\u0439 \u0432\u043e\u0440\u043a\u0435\u0440, \u0438 \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0438\u0439 Tsak-\u0440\u0430\u043d\u0442\u0430\u0439\u043c. \u041a\u043e\u0434 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043e\u0432 \u0436\u0438\u0432\u0451\u0442 \u0440\u043e\u0432\u043d\u043e \u0432 \u043e\u0434\u043d\u043e\u043c \u043c\u0435\u0441\u0442\u0435 \u2014 \u0432\u00a0EchoModule. \u041e\u0442\u043b\u0430\u0434\u043e\u0447\u043d\u044b\u0439\u00a0EchoWorker\u00a0\u2014 \u044d\u0442\u043e \u043f\u0440\u043e\u0441\u0442\u043e \u00ab\u043e\u0431\u0432\u044f\u0437\u043a\u0430\u00bb, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043f\u043e\u0434\u043d\u0438\u043c\u0430\u0435\u0442 \u0431\u0430\u0437\u0443 \u0438 \u0434\u0451\u0440\u0433\u0430\u0435\u0442 \u0442\u0443 \u0436\u0435\u00a0main.\u041f\u043e\u0447\u0435\u043c\u0443 \u0434\u0432\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430, \u0430 \u043d\u0435 \u043e\u0434\u0438\u043d? \u0422\u0435\u0445\u043d\u0438\u0447\u0435\u0441\u043a\u0438 \u043c\u043e\u0436\u043d\u043e \u0438 \u0432 \u043e\u0434\u0438\u043d (exe \u0442\u043e\u0436\u0435 \u0434\u0430\u0451\u0442 DLL, \u0430 Tsak \u0438\u0449\u0435\u0442\u00a0InitRoute.main\u00a0\u0440\u0435\u0444\u043b\u0435\u043a\u0441\u0438\u0435\u0439 \u0432 \u043b\u044e\u0431\u043e\u0439 \u0441\u0431\u043e\u0440\u043a\u0435). \u041d\u043e \u0434\u043b\u044f \u043d\u0430\u0433\u043b\u044f\u0434\u043d\u043e\u0441\u0442\u0438 \u0434\u0432\u0435 \u0440\u043e\u043b\u0438 \u043b\u0443\u0447\u0448\u0435 \u0440\u0430\u0437\u0432\u0435\u0441\u0442\u0438:\u00a0EchoModule\u00a0\u2014 \u044d\u0442\u043e \u0442\u043e, \u0447\u0442\u043e \u043c\u044b \u043e\u0442\u0433\u0440\u0443\u0436\u0430\u0435\u043c\u00a0(\u043f\u0430\u043a\u0443\u0435\u0442\u0441\u044f \u0432\u00a0.tpkg), \u0430\u00a0EchoWorker\u00a0\u2014 \u0442\u043e, \u0447\u0435\u043c \u043e\u0442\u043b\u0430\u0436\u0438\u0432\u0430\u0435\u043c. \u041c\u043e\u0434\u0443\u043b\u044c \u043d\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u043d\u0438 \u0441\u0442\u0440\u043e\u0447\u043a\u0438 \u0445\u043e\u0441\u0442-\u043a\u043e\u0434\u0430 \u2014 \u0438 \u044d\u0442\u043e \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e: \u0432 \u043f\u0440\u043e\u0434\u0435 \u0431\u0430\u0437\u0443 \u0435\u043c\u0443 \u0434\u0430\u0441\u0442 Tsak.\u041f\u0440\u043e\u0435\u043a\u0442 1: \u043c\u043e\u0434\u0443\u043b\u044c \u0441 \u0434\u0432\u0443\u043c\u044f \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430\u043c\u0438\u0412\u043e\u0442 \u043e\u043d \u0446\u0435\u043b\u0438\u043a\u043e\u043c \u2014\u00a0EchoModule\/InitRoute.cs:using System.Text.Json;using redb.Core;                          \/\/ IRedbService, Query, SaveAsync, SyncSchemeAsyncusing redb.Core.Attributes;               \/\/ RedbSchemeusing redb.Core.Models.Entities;          \/\/ RedbObject&lt;T&gt;using redb.Route.Abstractions;            \/\/ IRouteContext, IExchangeusing redb.Route.Core;                    \/\/ RouteContextusing redb.Route.Http;                    \/\/ HttpComponent, SharedHttpServerManagerusing redb.Route.RedbCore.Extensions;     \/\/ ProcessWithRedb, GetRedbServicenamespace EchoModule;\/\/\/ &lt;summary&gt;\/\/\/ Tsak module entry point.\/\/\/ The worker discovers it by convention \u2014 a public static class named InitRoute\/\/\/ with a public static main(IRouteContext) \u2014 and calls it once when the module\/\/\/ loads. The debug host (EchoWorker\/Program.cs) calls the very same method, so the\/\/\/ route code below lives in exactly one place.\/\/\/\/\/\/ Two minimal endpoints on the shared HTTP server (port 5099), backed by redb\/SQLite:\/\/\/   POST \/api\/notes   body {&#171;tag&#187;:&#187;work&#187;,&#187;text&#187;:&#187;hello&#187;}   -&gt; save one note\/\/\/   GET  \/api\/notes?tag=work                               -&gt; list notes with that tag\/\/\/ &lt;\/summary&gt;public static class InitRoute{    private static readonly JsonSerializerOptions Json = new() { PropertyNameCaseInsensitive = true };    public static IRouteContext main(IRouteContext context)    {        \/\/ redb schema for Note. Idempotent \u2014 safe to call every load. The worker        \/\/ (or the debug host) has already brought redb + SQLite up by now.        context.GetRedbService().SyncSchemeAsync&lt;Note&gt;().GetAwaiter().GetResult();        \/\/ One shared HTTP server; both routes below bind to it.        context.AddComponent(new HttpComponent { ServerManager = new SharedHttpServerManager() });        ((RouteContext)context).AddRoutes(r =&gt;        {            \/\/ &#8212; POST \/api\/notes \u2014 save one note &#8212;            r.From(&#171;http:0.0.0.0:5099\/api\/notes?inOut=true&amp;methods=POST&#187;)                .RouteId(&#171;notes-post&#187;)                .ConvertBody&lt;string&gt;()                       \/\/ HTTP body -&gt; string (JSON)                .ProcessWithRedb(async (db, ex, ct) =&gt;                {                    var note = JsonSerializer.Deserialize&lt;Note&gt;(ex.In.Body?.ToString() ?? &#171;{}&#187;, Json) ?? new Note();                    var obj = new RedbObject&lt;Note&gt; { name = $&#187;note:{note.Tag}&#187;, Props = note };                    await db.SaveAsync(obj);                 \/\/ one insert into redb (SQLite)                    Reply(ex, new { saved = true, id = obj.id });                }).Log(&#171;Save ${body}&#187;);            \/\/ &#8212; GET \/api\/notes?tag=work \u2014 list by tag &#8212;            r.From(&#171;http:0.0.0.0:5099\/api\/notes?inOut=true&amp;methods=GET&#187;)                .RouteId(&#171;notes-get&#187;)                .ProcessWithRedb(async (db, ex, ct) =&gt;                {                    \/\/ ?tag=&#8230; arrives as the header redbHttp.QueryParam.tag                    var tag = ex.In.Headers.TryGetValue(&#171;redbHttp.QueryParam.tag&#187;, out var t)                        ? t?.ToString() ?? &#171;&#187;                        : &#171;&#187;;                    \/\/ Server-side filter: the GET parameter goes straight into Where(&#8230;).                    var found = await db.Query&lt;Note&gt;()                        .Where(n =&gt; n.Tag == tag)                        .ToListAsync();                    Reply(ex, found.Select(o =&gt; new { o.Props.Tag, o.Props.Text }));                }).Log(&#171;Load ${header.redbHttp.QueryParam.tag}&#187;);        });        return context;    }    \/\/ inOut=true -&gt; whatever the body is at the end becomes the HTTP response.    private static void Reply(IExchange ex, object body)    {        ex.In.ContentType = &#171;application\/json&#187;;        ex.In.Body = JsonSerializer.Serialize(body);    }}\/\/\/ &lt;summary&gt;Persisted note. [RedbScheme] marks the class as a redb schema.&lt;\/summary&gt;[RedbScheme]public sealed class Note{    public string Tag { get; set; } = &#171;&#187;;    public string Text { get; set; } = &#171;&#187;;}\u0420\u0430\u0437\u0431\u0435\u0440\u0451\u043c \u043f\u043e \u043a\u043e\u0441\u0442\u043e\u0447\u043a\u0430\u043c \u2014 \u0442\u0443\u0442 \u043a\u0430\u0436\u0434\u0430\u044f \u0441\u0442\u0440\u043e\u0447\u043a\u0430 \u0447\u0442\u043e-\u0442\u043e \u0437\u043d\u0430\u0447\u0438\u0442.\u0422\u043e\u0447\u043a\u0430 \u0432\u0445\u043e\u0434\u0430\u00a0mainpublic static IRouteContext main(IRouteContext context)\u042d\u0442\u043e \u0438 \u0435\u0441\u0442\u044c \u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442 \u043c\u043e\u0434\u0443\u043b\u044f Tsak. \u041d\u0438\u043a\u0430\u043a\u0438\u0445 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u043e\u0432, \u043d\u0438\u043a\u0430\u043a\u0438\u0445 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u043e\u0432 \u2014\u00a0\u0441\u043e\u0433\u043b\u0430\u0448\u0435\u043d\u0438\u0435 \u043e\u0431 \u0438\u043c\u0435\u043d\u0430\u0445: \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0439 \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u043a\u043b\u0430\u0441\u0441\u00a0InitRoute, \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0439 \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u043c\u0435\u0442\u043e\u0434\u00a0main, \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u044e\u0449\u0438\u0439\u00a0IRouteContext. Tsak \u043f\u0440\u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0435 \u0441\u0431\u043e\u0440\u043a\u0438 \u0441\u043a\u0430\u043d\u0438\u0440\u0443\u0435\u0442 \u0435\u0451 \u0440\u0435\u0444\u043b\u0435\u043a\u0441\u0438\u0435\u0439, \u043d\u0430\u0445\u043e\u0434\u0438\u0442 \u044d\u0442\u043e\u0442 \u043c\u0435\u0442\u043e\u0434 \u0438 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442 \u0435\u0433\u043e, \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u044f \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442. \u0412\u0441\u0451, \u0447\u0442\u043e \u0432\u044b \u043d\u0430\u0432\u0435\u0441\u0438\u0442\u0435 \u043d\u0430\u00a0context\u00a0\u2014 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b, \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u044b, \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u0438 \u2014 \u0441\u0442\u0430\u043d\u0435\u0442 \u0447\u0430\u0441\u0442\u044c\u044e \u0440\u0430\u043d\u0442\u0430\u0439\u043c\u0430.\u0412\u0430\u0436\u043d\u043e: \u044d\u0442\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0438 \u0432 \u043e\u0442\u043b\u0430\u0434\u043e\u0447\u043d\u043e\u043c \u0432\u043e\u0440\u043a\u0435\u0440\u0435. \u0422\u0430\u043c \u043c\u044b\u00a0\u0441\u0430\u043c\u0438\u00a0\u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c\u00a0RouteContext\u00a0\u0438\u00a0\u0441\u0430\u043c\u0438\u00a0\u0432\u044b\u0437\u043e\u0432\u0435\u043c\u00a0InitRoute.main(ctx). \u041e\u0434\u0438\u043d \u0438 \u0442\u043e\u0442 \u0436\u0435 \u043c\u0435\u0442\u043e\u0434, \u0434\u0432\u0435 \u0441\u0440\u0435\u0434\u044b \u0437\u0430\u043f\u0443\u0441\u043a\u0430.\u0421\u0445\u0435\u043c\u0430 \u0434\u0430\u043d\u043d\u044b\u0445context.GetRedbService().SyncSchemeAsync&lt;Note&gt;().GetAwaiter().GetResult();Note\u00a0\u043f\u043e\u043c\u0435\u0447\u0435\u043d \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u043e\u043c\u00a0[RedbScheme]\u00a0\u2014 \u0437\u043d\u0430\u0447\u0438\u0442, \u044d\u0442\u043e persistable-\u043a\u043b\u0430\u0441\u0441 redb.\u00a0SyncSchemeAsync&lt;Note&gt;()\u00a0\u0437\u0430\u0432\u043e\u0434\u0438\u0442 \u0434\u043b\u044f \u043d\u0435\u0433\u043e \u0441\u0445\u0435\u043c\u0443 (\u0438\u0434\u0435\u043c\u043f\u043e\u0442\u0435\u043d\u0442\u043d\u043e \u2014 \u0432\u044b\u0437\u044b\u0432\u0430\u0442\u044c \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u0438 \u043a\u0430\u0436\u0434\u043e\u0439 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0435).\u00a0GetRedbService()\u00a0\u0434\u043e\u0441\u0442\u0430\u0451\u0442\u00a0IRedbService\u00a0\u0438\u0437 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430: \u0432 Tsak \u0431\u0430\u0437\u0443 \u0443\u0436\u0435 \u043f\u043e\u0434\u043d\u044f\u043b\u0438 \u0434\u043e \u0432\u044b\u0437\u043e\u0432\u0430 \u043c\u043e\u0434\u0443\u043b\u044f, \u0432 \u043e\u0442\u043b\u0430\u0434\u043e\u0447\u043d\u043e\u043c \u0432\u043e\u0440\u043a\u0435\u0440\u0435 \u2014 \u043c\u044b \u043f\u043e\u0434\u043d\u0438\u043c\u0435\u043c \u0435\u0451 \u0441\u0430\u043c\u0438 \u0434\u043e\u00a0main. \u0412 \u043e\u0431\u043e\u0438\u0445 \u0441\u043b\u0443\u0447\u0430\u044f\u0445 \u043a \u043c\u043e\u043c\u0435\u043d\u0442\u0443 \u0432\u044b\u0437\u043e\u0432\u0430 redb \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d.\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435: \u043c\u043e\u0434\u0443\u043b\u044c\u00a0\u043d\u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0443\u0435\u0442 \u0431\u0430\u0437\u0443. \u041e\u043d \u043d\u0435 \u0437\u043d\u0430\u0435\u0442 \u0438 \u043d\u0435 \u0434\u043e\u043b\u0436\u0435\u043d \u0437\u043d\u0430\u0442\u044c, SQLite \u0442\u0430\u043c, Postgres \u0438\u043b\u0438 MSSQL. \u041e\u043d \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u0440\u043e\u0441\u0438\u0442\u00a0IRedbService\u00a0\u0443 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430. \u041f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440 \u2014 \u0437\u0430\u0431\u043e\u0442\u0430 \u0445\u043e\u0441\u0442\u0430.\u041e\u0434\u0438\u043d HTTP-\u0441\u0435\u0440\u0432\u0435\u0440, \u0434\u0432\u0430 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430context.AddComponent(new HttpComponent { ServerManager = new SharedHttpServerManager() });SharedHttpServerManager\u00a0\u2014 \u044d\u0442\u043e \u043e\u0431\u0449\u0438\u0439 HTTP-\u0441\u0435\u0440\u0432\u0435\u0440: \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043e\u0432 \u043c\u043e\u0433\u0443\u0442 \u0432\u0438\u0441\u0435\u0442\u044c \u043d\u0430 \u043e\u0434\u043d\u043e\u043c \u043f\u043e\u0440\u0442\u0443. \u0423 \u043d\u0430\u0441 \u043e\u0431\u0430 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430 \u2014 \u043d\u0430\u00a05099.r.From(&#171;http:0.0.0.0:5099\/api\/notes?inOut=true&amp;methods=POST&#187;)r.From(&#171;http:0.0.0.0:5099\/api\/notes?inOut=true&amp;methods=GET&#187;)\u041e\u0431\u0430 \u0441\u043b\u0443\u0448\u0430\u044e\u0442\u00a0\u043e\u0434\u0438\u043d \u0438 \u0442\u043e\u0442 \u0436\u0435 \u043f\u0443\u0442\u044c\u00a0\/api\/notes, \u043d\u043e \u0440\u0430\u0437\u044a\u0435\u0437\u0436\u0430\u044e\u0442\u0441\u044f \u043f\u043e \u043c\u0435\u0442\u043e\u0434\u0443 \u0447\u0435\u0440\u0435\u0437\u00a0?methods=POST\u00a0\/\u00a0?methods=GET. \u0421\u0435\u0440\u0432\u0435\u0440 \u043c\u0430\u0442\u0447\u0438\u0442 \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0439 \u0437\u0430\u043f\u0440\u043e\u0441 \u043f\u043e \u043f\u0430\u0440\u0435 \u00ab\u043f\u0443\u0442\u044c + \u043c\u0435\u0442\u043e\u0434\u00bb: POST \u0443\u0435\u0434\u0435\u0442 \u0432 \u043f\u0435\u0440\u0432\u044b\u0439 \u043c\u0430\u0440\u0448\u0440\u0443\u0442, GET \u2014 \u0432\u043e \u0432\u0442\u043e\u0440\u043e\u0439, \u0430, \u0441\u043a\u0430\u0436\u0435\u043c,\u00a0PUT \/api\/notes\u00a0\u0432\u0435\u0440\u043d\u0451\u0442 \u0447\u0435\u0441\u0442\u043d\u044b\u0439\u00a0405 Method Not Allowed\u00a0(\u043f\u0443\u0442\u044c \u0435\u0441\u0442\u044c, \u043c\u0435\u0442\u043e\u0434 \u043d\u0435 \u0442\u043e\u0442).\u00a0inOut=true\u00a0\u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442 \u00ab\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441-\u043e\u0442\u0432\u0435\u0442\u00bb: \u0442\u043e, \u0447\u0442\u043e \u043e\u043a\u0430\u0436\u0435\u0442\u0441\u044f \u0432 \u0442\u0435\u043b\u0435 \u043e\u0431\u043c\u0435\u043d\u0430 \u043d\u0430 \u0432\u044b\u0445\u043e\u0434\u0435 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430, \u0441\u0442\u0430\u043d\u0435\u0442 HTTP-\u043e\u0442\u0432\u0435\u0442\u043e\u043c.POST: \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c.ConvertBody&lt;string&gt;().ProcessWithRedb(async (db, ex, ct) =&gt;{    var note = JsonSerializer.Deserialize&lt;Note&gt;(ex.In.Body?.ToString() ?? &#171;{}&#187;, Json) ?? new Note();    var obj = new RedbObject&lt;Note&gt; { name = $&#187;note:{note.Tag}&#187;, Props = note };    await db.SaveAsync(obj);&#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-485995","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/485995","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=485995"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/485995\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=485995"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=485995"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=485995"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}