{"id":319251,"date":"2021-03-09T15:00:37","date_gmt":"2021-03-09T15:00:37","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=319251"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=319251","title":{"rendered":"GraphQL \u043d\u0430 Rust"},"content":{"rendered":"\n<div class=\"post__text post__text_v2\" id=\"post-content-body\">\n<p>\u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u043f\u043e\u043a\u0430\u0436\u0443 \u043a\u0430\u043a \u0441\u043e\u0437\u0434\u0430\u0442\u044c GraphQL \u0441\u0435\u0440\u0432\u0435\u0440, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f Rust \u0438 \u0435\u0433\u043e \u044d\u043a\u043e\u0441\u0438\u0441\u0442\u0435\u043c\u0443; \u0431\u0443\u0434\u0443\u0442 \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043d\u044b \u043f\u0440\u0438\u043c\u0435\u0440\u044b \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043d\u0430\u0438\u0431\u043e\u043b\u0435\u0435 \u0447\u0430\u0441\u0442\u043e \u0432\u0441\u0442\u0440\u0435\u0447\u0430\u044e\u0449\u0438\u0445\u0441\u044f \u0437\u0430\u0434\u0430\u0447 \u043f\u0440\u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0435 GraphQL API. \u0412 \u0438\u0442\u043e\u0433\u0435 API \u0442\u0440\u0451\u0445 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u0431\u0443\u0434\u0443\u0442 \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0435\u043d\u044b \u0432 \u0435\u0434\u0438\u043d\u0443\u044e \u0442\u043e\u0447\u043a\u0443 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e Apollo Server \u0438 <a href=\"https:\/\/www.apollographql.com\/docs\/federation\/\" rel=\"noopener noreferrer nofollow\">Apollo Federation<\/a>. \u042d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u043a\u043b\u0438\u0435\u043d\u0442\u0430\u043c \u0437\u0430\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e \u0438\u0437 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u043e\u0432 \u0431\u0435\u0437 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u0437\u043d\u0430\u0442\u044c \u043a\u0430\u043a\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u0440\u0438\u0445\u043e\u0434\u044f\u0442 \u0438\u0437 \u043a\u0430\u043a\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0438\u0441\u0430.<\/p>\n<h2>\u0412\u0432\u0435\u0434\u0435\u043d\u0438\u0435<\/h2>\n<h3>\u041e\u0431\u0437\u043e\u0440<\/h3>\n<p>\u0421 \u0442\u043e\u0447\u043a\u0438 \u0437\u0440\u0435\u043d\u0438\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c\u044b\u0439 \u043f\u0440\u043e\u0435\u043a\u0442 \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u043f\u043e\u0445\u043e\u0436 \u043d\u0430 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0439 \u0432 \u043c\u043e\u0435\u0439 <a href=\"https:\/\/romankudryashov.com\/blog\/2020\/02\/how-to-graphql\/\" rel=\"noopener noreferrer nofollow\">\u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0439 \u0441\u0442\u0430\u0442\u044c\u0435<\/a>, \u043d\u043e \u0432 \u044d\u0442\u043e\u0442 \u0440\u0430\u0437 \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u0441\u0442\u044d\u043a\u0430 Rust. \u0410\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u043d\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/71c\/569\/7d0\/71c5697d0e423c08f7b991d10fc6251d.png\" width=\"6532\" height=\"4500\"><figcaption><\/figcaption><\/figure>\n<p>\u041a\u0430\u0436\u0434\u044b\u0439 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u044b \u043e\u0441\u0432\u0435\u0449\u0430\u0435\u0442 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0432\u043e\u043f\u0440\u043e\u0441\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u0433\u0443\u0442 \u0432\u043e\u0437\u043d\u0438\u043a\u043d\u0443\u0442\u044c \u043f\u0440\u0438 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 GraphQL API. \u0414\u043e\u043c\u0435\u043d\u043d\u0430\u044f \u043c\u043e\u0434\u0435\u043b\u044c \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u0434\u0430\u043d\u043d\u044b\u0435 \u043e \u043f\u043b\u0430\u043d\u0435\u0442\u0430\u0445 \u0421\u043e\u043b\u043d\u0435\u0447\u043d\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u0438 \u0438\u0445 \u0441\u043f\u0443\u0442\u043d\u0438\u043a\u0430\u0445. \u041f\u0440\u043e\u0435\u043a\u0442 \u0438\u043c\u0435\u0435\u0442 \u043c\u043d\u043e\u0433\u043e\u043c\u043e\u0434\u0443\u043b\u044c\u043d\u0443\u044e \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 (\u0438\u043b\u0438 \u043c\u043e\u043d\u043e\u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439) \u0438 \u0441\u043e\u0441\u0442\u043e\u0438\u0442 \u0438\u0437 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u043c\u043e\u0434\u0443\u043b\u0435\u0439:<\/p>\n<ul>\n<li>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/tree\/master\/planets-service\" rel=\"noopener noreferrer nofollow\">planets-service<\/a> (Rust)<\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/tree\/master\/satellites-service\" rel=\"noopener noreferrer nofollow\">satellites-service<\/a> (Rust)<\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/tree\/master\/auth-service\" rel=\"noopener noreferrer nofollow\">auth-service<\/a> (Rust)<\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/tree\/master\/apollo-server\" rel=\"noopener noreferrer nofollow\">apollo-server<\/a> (JS)<\/p>\n<\/li>\n<\/ul>\n<p>\u0421\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0442 \u0434\u0432\u0435 \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0445 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 \u0434\u043b\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 GraphQL \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u043d\u0430 Rust: <a href=\"https:\/\/github.com\/graphql-rust\/juniper\" rel=\"noopener noreferrer nofollow\">Juniper<\/a> \u0438 <a href=\"https:\/\/github.com\/async-graphql\/async-graphql\" rel=\"noopener noreferrer nofollow\">Async-graphql<\/a>, \u043d\u043e \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u044f\u044f \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 Apollo Federation, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043e\u043d\u0430 \u0431\u044b\u043b\u0430 \u0432\u044b\u0431\u0440\u0430\u043d\u0430 \u0434\u043b\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 (\u0435\u0441\u0442\u044c \u0442\u0430\u043a\u0436\u0435 \u043e\u0442\u043a\u0440\u044b\u0442\u044b\u0439 <a href=\"https:\/\/github.com\/graphql-rust\/juniper\/issues\/376\" rel=\"noopener noreferrer nofollow\">\u0437\u0430\u043f\u0440\u043e\u0441<\/a> \u043d\u0430 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0438 Federation \u0432 Juniper). \u041e\u0431\u0435 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u044e\u0442 <a href=\"https:\/\/blog.logrocket.com\/code-first-vs-schema-first-development-graphql\/\" rel=\"noopener noreferrer nofollow\">code-first<\/a> \u043f\u043e\u0434\u0445\u043e\u0434.<\/p>\n<p>\u041f\u043e\u043c\u0438\u043c\u043e \u044d\u0442\u043e\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u044b PostgreSQL \u2014 \u0434\u043b\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0441\u043b\u043e\u044f \u0434\u0430\u043d\u043d\u044b\u0445, <a href=\"https:\/\/jwt.io\/\" rel=\"noopener noreferrer nofollow\">JWT<\/a> \u2014 \u0434\u043b\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u0438 Kafka \u2014 \u0434\u043b\u044f \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e\u0433\u043e \u043e\u0431\u043c\u0435\u043d\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f\u043c\u0438.<\/p>\n<h3>\u0421\u0442\u044d\u043a \u0442\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u0439<\/h3>\n<p>\u0412 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439 \u0442\u0430\u0431\u043b\u0438\u0446\u0435 \u043f\u043e\u043a\u0430\u0437\u0430\u043d \u0441\u0442\u044d\u043a \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0445 \u0442\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u0439, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u0432 \u043f\u0440\u043e\u0435\u043a\u0442\u0435:<\/p>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th>\n<p>\u0422\u0438\u043f<\/p>\n<\/th>\n<th>\n<p>\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435<\/p>\n<\/th>\n<th>\n<p>\u0421\u0430\u0439\u0442<\/p>\n<\/th>\n<th>\n<p>GitHub<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td>\n<p>\u042f\u0437\u044b\u043a \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f<\/p>\n<\/td>\n<td>\n<p>Rust<\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/www.rust-lang.org\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/github.com\/rust-lang\/rust\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>GraphQL \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430<\/p>\n<\/td>\n<td>\n<p>Async-graphql<\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/async-graphql.github.io\/async-graphql\/en\/index.html\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/github.com\/async-graphql\/async-graphql\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>\u0415\u0434\u0438\u043d\u0430\u044f GraphQL \u0442\u043e\u0447\u043a\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0430<\/p>\n<\/td>\n<td>\n<p>Apollo Server<\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/www.apollographql.com\/docs\/apollo-server\/\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/github.com\/apollographql\/apollo-server\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>Web \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a<\/p>\n<\/td>\n<td>\n<p>actix-web<\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/actix.rs\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/github.com\/actix\/actix-web\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>\u0411\u0430\u0437\u0430 \u0434\u0430\u043d\u044b\u0445<\/p>\n<\/td>\n<td>\n<p>PostgreSQL<\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/www.postgresql.org\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/github.com\/postgres\/postgres\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>\u0411\u0440\u043e\u043a\u0435\u0440 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439<\/p>\n<\/td>\n<td>\n<p>Apache Kafka<\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/kafka.apache.org\/\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/github.com\/apache\/kafka\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>\u041e\u0440\u043a\u0435\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u043e\u0432<\/p>\n<\/td>\n<td>\n<p>Docker Compose<\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/docs.docker.com\/compose\/\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/github.com\/docker\/compose\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>\u0422\u0430\u043a\u0436\u0435 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0435 Rust \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438:<\/p>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th>\n<p>\u0422\u0438\u043f<\/p>\n<\/th>\n<th>\n<p>\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435<\/p>\n<\/th>\n<th>\n<p>\u0421\u0430\u0439\u0442<\/p>\n<\/th>\n<th>\n<p>GitHub<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td>\n<p>ORM<\/p>\n<\/td>\n<td>\n<p>Diesel<\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/diesel.rs\/\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/github.com\/diesel-rs\/diesel\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>Kafka \u043a\u043b\u0438\u0435\u043d\u0442<\/p>\n<\/td>\n<td>\n<p>rust-rdkafka<\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/crates.io\/crates\/rdkafka\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/github.com\/fede1024\/rust-rdkafka\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>\u0425\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043f\u0430\u0440\u043e\u043b\u0435\u0439<\/p>\n<\/td>\n<td>\n<p>argonautica<\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/crates.io\/crates\/argonautica\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/github.com\/bcmyers\/argonautica\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>JWT \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430<\/p>\n<\/td>\n<td>\n<p>jsonwebtoken<\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/crates.io\/crates\/jsonwebtoken\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/github.com\/Keats\/jsonwebtoken\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>\u0411\u0438\u0431\u0438\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f<\/p>\n<\/td>\n<td>\n<p>Testcontainers-rs<\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/crates.io\/crates\/testcontainers\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/github.com\/testcontainers\/testcontainers-rs\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<h3>\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0435 \u041f\u041e<\/h3>\n<p>\u0427\u0442\u043e\u0431\u044b \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u043f\u0440\u043e\u0435\u043a\u0442 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e, \u0432\u0430\u043c \u043d\u0443\u0436\u0435\u043d \u0442\u043e\u043b\u044c\u043a\u043e Docker Compose. \u0412 \u043f\u0440\u043e\u0442\u0438\u0432\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0432\u0430\u043c \u043c\u043e\u0436\u0435\u0442 \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u044c\u0441\u044f \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435:<\/p>\n<ul>\n<li>\n<p><a href=\"https:\/\/www.rust-lang.org\/tools\/install\" rel=\"noopener noreferrer nofollow\">Rust<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/diesel.rs\/guides\/getting-started\/\" rel=\"noopener noreferrer nofollow\">Diesel CLI<\/a> (\u0434\u043b\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u0435 <code>cargo install diesel_cli --no-default-features --features postgres<\/code>)<\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/releases.llvm.org\/download.html\" rel=\"noopener noreferrer nofollow\">LLVM<\/a> (\u044d\u0442\u043e \u043d\u0443\u0436\u043d\u043e \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u043a\u0440\u044d\u0439\u0442\u0430 <code>argonautica<\/code>)<\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/cmake.org\/install\/\" rel=\"noopener noreferrer nofollow\">CMake<\/a> (\u044d\u0442\u043e \u043d\u0443\u0436\u043d\u043e \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u043a\u0440\u044d\u0439\u0442\u0430 <code>rust-rdkafka<\/code>)<\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/www.postgresql.org\/download\/\" rel=\"noopener noreferrer nofollow\">PostgreSQL<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/kafka.apache.org\/quickstart\" rel=\"noopener noreferrer nofollow\">Apache Kafka<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/www.npmjs.com\/get-npm\" rel=\"noopener noreferrer nofollow\">npm<\/a><\/p>\n<\/li>\n<\/ul>\n<h2>\u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f<\/h2>\n<p>\u0412 <code>Cargo.toml<\/code> \u0432 \u043a\u043e\u0440\u043d\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0443\u043a\u0430\u0437\u0430\u043d\u044b \u0442\u0440\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438 \u043e\u0434\u043d\u0430 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430:<\/p>\n<p><em>Root <\/em><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/blob\/master\/Cargo.toml\" rel=\"noopener noreferrer nofollow\"><em>Cargo.toml<\/em><\/a><\/p>\n<pre><code class=\"rust\">[workspace] members = [     \"auth-service\",     \"planets-service\",     \"satellites-service\",     \"common-utils\", ] <\/code><\/pre>\n<p>\u041d\u0430\u0447\u043d\u0451\u043c \u0441 <code>planets-service<\/code>.<\/p>\n<h3>\u0417\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438<\/h3>\n<p><code>Cargo.toml<\/code> \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a:<\/p>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/blob\/master\/planets-service\/Cargo.toml\" rel=\"noopener noreferrer nofollow\"><em>Cargo.toml<\/em><\/a><\/p>\n<pre><code>[package] name = \"planets-service\" version = \"0.1.0\" edition = \"2018\"  [dependencies] common-utils = { path = \"..\/common-utils\" } async-graphql = \"2.4.3\" async-graphql-actix-web = \"2.4.3\" actix-web = \"3.3.2\" actix-rt = \"1.1.1\" actix-web-actors = \"3.0.0\" futures = \"0.3.8\" async-trait = \"0.1.42\" bigdecimal = { version = \"0.1.2\", features = [\"serde\"] } serde = { version = \"1.0.118\", features = [\"derive\"] } serde_json = \"1.0.60\" diesel = { version = \"1.4.5\", features = [\"postgres\", \"r2d2\", \"numeric\"] } diesel_migrations = \"1.4.0\" dotenv = \"0.15.0\" strum = \"0.20.0\" strum_macros = \"0.20.1\" rdkafka = { version = \"0.24.0\", features = [\"cmake-build\"] } async-stream = \"0.3.0\" lazy_static = \"1.4.0\"  [dev-dependencies] jsonpath_lib = \"0.2.6\" testcontainers = \"0.9.1\" <\/code><\/pre>\n<p><code>async-graphql<\/code> \u2014 \u044d\u0442\u043e GraphQL \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430, <code>actix-web<\/code> \u2014 web \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a, \u0430 <code>async-graphql-actix-web<\/code> \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0432\u0430\u0435\u0442 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044e \u043c\u0435\u0436\u0434\u0443 \u043d\u0438\u043c\u0438.<\/p>\n<h3>\u041a\u043b\u044e\u0447\u0435\u0432\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438<\/h3>\n<p>\u041d\u0430\u0447\u043d\u0451\u043c \u0441 <code>main.rs<\/code>:<\/p>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/blob\/master\/planets-service\/src\/main.rs\" rel=\"noopener noreferrer nofollow\"><em>main.rs<\/em><\/a><\/p>\n<pre><code class=\"rust\">#[actix_rt::main] async fn main() -&gt; std::io::Result&lt;()&gt; {     dotenv().ok();     let pool = create_connection_pool();     run_migrations(&amp;pool);      let schema = create_schema_with_context(pool);      HttpServer::new(move || App::new()         .configure(configure_service)         .data(schema.clone())     )         .bind(\"0.0.0.0:8001\")?         .run()         .await } <\/code><\/pre>\n<p>\u0417\u0434\u0435\u0441\u044c \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u0435 \u0438 HTTP \u0441\u0435\u0440\u0432\u0435\u0440 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u0439, \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0451\u043d\u043d\u044b\u0445 \u0432 <code>lib.rs<\/code>:<\/p>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/blob\/master\/planets-service\/src\/lib.rs\" rel=\"noopener noreferrer nofollow\"><em>lib.rs<\/em><\/a><\/p>\n<pre><code class=\"rust\">pub fn configure_service(cfg: &amp;mut web::ServiceConfig) {     cfg         .service(web::resource(\"\/\")             .route(web::post().to(index))             .route(web::get().guard(guard::Header(\"upgrade\", \"websocket\")).to(index_ws))             .route(web::get().to(index_playground))         ); }  async fn index(schema: web::Data, http_req: HttpRequest, req: Request) -&gt; Response {     let mut query = req.into_inner();      let maybe_role = common_utils::get_role(http_req);     if let Some(role) = maybe_role {         query = query.data(role);     }      schema.execute(query).await.into() }  async fn index_ws(schema: web::Data, req: HttpRequest, payload: web::Payload) -&gt; Result {     WSSubscription::start(Schema::clone(&amp;*schema), &amp;req, payload) }  async fn index_playground() -&gt; HttpResponse {     HttpResponse::Ok()         .content_type(\"text\/html; charset=utf-8\")         .body(playground_source(GraphQLPlaygroundConfig::new(\"\/\").subscription_endpoint(\"\/\"))) }  pub fn create_schema_with_context(pool: PgPool) -&gt; Schema {     let arc_pool = Arc::new(pool);     let cloned_pool = Arc::clone(&amp;arc_pool);     let details_batch_loader = Loader::new(DetailsBatchLoader {         pool: cloned_pool     }).with_max_batch_size(10);      let kafka_consumer_counter = Mutex::new(0);      Schema::build(Query, Mutation, Subscription)         .data(arc_pool)         .data(details_batch_loader)         .data(kafka::create_producer())         .data(kafka_consumer_counter)         .finish() } <\/code><\/pre>\n<p>\u042d\u0442\u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0434\u0435\u043b\u0430\u044e\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435:<\/p>\n<ul>\n<li>\n<p><code>index<\/code> \u2014 \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 GraphQL <a href=\"https:\/\/graphql.org\/learn\/queries\/\" rel=\"noopener noreferrer nofollow\">\u0437\u0430\u043f\u0440\u043e\u0441\u044b (query) \u0438 \u043c\u0443\u0442\u0430\u0446\u0438\u0438<\/a><\/p>\n<\/li>\n<li>\n<p><code>index_ws<\/code> \u2014 \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 GraphQL <a href=\"https:\/\/www.apollographql.com\/docs\/react\/data\/subscriptions\/\" rel=\"noopener noreferrer nofollow\">\u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438<\/a><\/p>\n<\/li>\n<li>\n<p><code>index_playground<\/code> \u2014 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 Playground GraphQL IDE<\/p>\n<\/li>\n<li>\n<p><code>create_schema_with_context<\/code> \u2014 \u0441\u043e\u0437\u0434\u0430\u0451\u0442 GraphQL \u0441\u0445\u0435\u043c\u0443 \u0441 \u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u044b\u043c \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u043e\u043c \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u043c \u0432 \u0440\u0430\u043d\u0442\u0430\u0439\u043c\u0435, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043f\u0443\u043b \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0439 \u0441 \u0411\u0414<\/p>\n<\/li>\n<\/ul>\n<h3>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 GraphQL \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u0438 \u0442\u0438\u043f\u0430<\/h3>\n<p>\u0420\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043a\u0430\u043a \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u0437\u0430\u043f\u0440\u043e\u0441:<\/p>\n<p><em>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u0430<\/em><\/p>\n<pre><code class=\"rust\">#[Object] impl Query {     async fn get_planets(&amp;self, ctx: &amp;Context&lt;'_&gt;) -&gt; Vec {         repository::get_all(&amp;get_conn_from_ctx(ctx)).expect(\"Can't get planets\")             .iter()             .map(|p| { Planet::from(p) })             .collect()     }      async fn get_planet(&amp;self, ctx: &amp;Context&lt;'_&gt;, id: ID) -&gt; Option {         find_planet_by_id_internal(ctx, id)     }      #[graphql(entity)]     async fn find_planet_by_id(&amp;self, ctx: &amp;Context&lt;'_&gt;, id: ID) -&gt; Option {         find_planet_by_id_internal(ctx, id)     } }  fn find_planet_by_id_internal(ctx: &amp;Context&lt;'_&gt;, id: ID) -&gt; Option {     let id = id.to_string().parse::().expect(\"Can't get id from String\");     repository::get(id, &amp;get_conn_from_ctx(ctx)).ok()         .map(|p| { Planet::from(&amp;p) }) } <\/code><\/pre>\n<p>\u042d\u0442\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u043f\u043e\u043b\u0443\u0447\u0430\u044e\u0442 \u0434\u0430\u043d\u043d\u044b\u0435 \u0438\u0437 \u0411\u0414 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u0441\u043b\u043e\u0439 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u044f. \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0435 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0438 \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u0432 GraphQL DTO (\u044d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0441\u043e\u0431\u043b\u044e\u0441\u0442\u0438 \u043f\u0440\u0438\u043d\u0446\u0438\u043f \u0435\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0439 \u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0441\u0442\u0438 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0439 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b). \u0417\u0430\u043f\u0440\u043e\u0441\u044b <code>get_planets<\/code> \u0438 <code>get_planet<\/code> \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u044b \u0438\u0437 \u043b\u044e\u0431\u043e\u0439 GraphQL IDE \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 \u0442\u0430\u043a:<\/p>\n<p><em>\u041f\u0440\u0438\u043c\u0435\u0440 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0437\u0430\u043f\u0440\u043e\u0441\u0430<\/em><\/p>\n<pre><code>{   getPlanets {     name     type   } } <\/code><\/pre>\n<p>\u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 <code>Planet<\/code> \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0430 \u0442\u0430\u043a:<\/p>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/blob\/master\/planets-service\/src\/graphql.rs\" rel=\"noopener noreferrer nofollow\"><em>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 GraphQL \u0442\u0438\u043f\u0430<\/em><\/a><\/p>\n<pre><code class=\"rust\">#[derive(Serialize, Deserialize)] struct Planet {     id: ID,     name: String,     planet_type: PlanetType, }  #[Object] impl Planet {     async fn id(&amp;self) -&gt; &amp;ID {         &amp;self.id     }      async fn name(&amp;self) -&gt; &amp;String {         &amp;self.name     }      \/\/\/ From an astronomical point of view     #[graphql(name = \"type\")]     async fn planet_type(&amp;self) -&gt; &amp;PlanetType {         &amp;self.planet_type     }      #[graphql(deprecation = \"Now it is not in doubt. Do not use this field\")]     async fn is_rotating_around_sun(&amp;self) -&gt; bool {         true     }      async fn details(&amp;self, ctx: &amp;Context&lt;'_&gt;) -&gt; Details {         let loader = ctx.data::&gt;().expect(\"Can't get loader\");         let planet_id = self.id.to_string().parse::().expect(\"Can't convert id\");         loader.load(planet_id).await     } } <\/code><\/pre>\n<p>\u0412 <code>impl<\/code> \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442\u0441\u044f \u0440\u0435\u0437\u043e\u043b\u0432\u0435\u0440 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043f\u043e\u043b\u044f. \u0422\u0430\u043a\u0436\u0435 \u0434\u043b\u044f \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043f\u043e\u043b\u0435\u0439 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u044b \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 (\u0432 \u0432\u0438\u0434\u0435 Rust \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f) \u0438 deprecation reason. \u042d\u0442\u043e \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u043e \u0432 GraphQL IDE.<\/p>\n<h3>\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 N+1<\/h3>\n<p>\u0412 \u0441\u043b\u0443\u0447\u0430\u0435 <em>\u043d\u0430\u0438\u0432\u043d\u043e\u0439<\/em> \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 <code>Planet.details<\/code> \u0432\u044b\u0448\u0435 \u0432\u043e\u0437\u043d\u0438\u043a\u043b\u0430 \u0431\u044b \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 N+1, \u0442\u043e \u0435\u0441\u0442\u044c, \u043f\u0440\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0438 \u0442\u0430\u043a\u043e\u0433\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u0430:<\/p>\n<p><em>\u041f\u0440\u0438\u043c\u0435\u0440 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0433\u043e \u0440\u0435\u0441\u0443\u0440\u0441\u043e\u0451\u043c\u043a\u043e\u0433\u043e GraphQL \u0437\u0430\u043f\u0440\u043e\u0441\u0430<\/em><\/p>\n<pre><code>{   getPlanets {     name     details {       meanRadius     }   } } <\/code><\/pre>\n<p>\u0434\u043b\u044f \u043f\u043e\u043b\u044f <code>details<\/code> \u043a\u0430\u0436\u0434\u043e\u0439 \u0438\u0437 \u043f\u043b\u0430\u043d\u0435\u0442 \u0431\u044b\u043b \u0431\u044b \u0441\u0434\u0435\u043b\u0430\u043d \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 SQL \u0437\u0430\u043f\u0440\u043e\u0441, \u0442. \u043a. <code>Details<\/code> \u2014 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0430\u044f \u043e\u0442 <code>Planet<\/code> \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u044c \u0438 \u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f \u0432 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0439 \u0442\u0430\u0431\u043b\u0438\u0446\u0435.<\/p>\n<p>\u041d\u043e \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <a href=\"https:\/\/github.com\/graphql\/dataloader\" rel=\"noopener noreferrer nofollow\">DataLoader<\/a>, \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u0432 Async-graphql, \u0440\u0435\u0437\u043e\u043b\u0432\u0435\u0440 <code>details<\/code> \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0451\u043d \u0442\u0430\u043a:<\/p>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/blob\/master\/planets-service\/src\/graphql.rs\" rel=\"noopener noreferrer nofollow\"><em>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u0440\u0435\u0437\u043e\u043b\u0432\u0435\u0440\u0430<\/em><\/a><\/p>\n<pre><code class=\"rust\">async fn details(&amp;self, ctx: &amp;Context&lt;'_&gt;) -&gt; Result {     let data_loader = ctx.data::&gt;().expect(\"Can't get data loader\");     let planet_id = self.id.to_string().parse::().expect(\"Can't convert id\");     let details = data_loader.load_one(planet_id).await?;     details.ok_or_else(|| \"Not found\".into()) } <\/code><\/pre>\n<p><code>data_loader<\/code> \u2014 \u044d\u0442\u043e \u043e\u0431\u044a\u0435\u043a\u0442 \u0432 \u043a\u043e\u043d\u0442\u0435\u043a\u0442\u0441\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0451\u043d\u043d\u044b\u0439 \u0442\u0430\u043a:<\/p>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/blob\/master\/planets-service\/src\/lib.rs\" rel=\"noopener noreferrer nofollow\"><em>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 DataLoader&#8217;\u0430<\/em><\/a><\/p>\n<pre><code class=\"rust\">let details_data_loader = DataLoader::new(DetailsLoader {     pool: cloned_pool }).max_batch_size(10); <\/code><\/pre>\n<p><code>DetailsLoader<\/code> \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/blob\/master\/planets-service\/src\/graphql.rs\" rel=\"noopener noreferrer nofollow\"><em>DetailsLoader definition<\/em><\/a><\/p>\n<pre><code class=\"rust\">pub struct DetailsLoader {     pub pool: Arc }  #[async_trait::async_trait] impl Loader for DetailsLoader {     type Value = Details;     type Error = Error;      async fn load(&amp;self, keys: &amp;[i32]) -&gt; Result, Self::Error&gt; {         let conn = self.pool.get().expect(\"Can't get DB connection\");         let details = repository::get_details(keys, &amp;conn).expect(\"Can't get planets' details\");          Ok(details.iter()             .map(|details_entity| (details_entity.planet_id, Details::from(details_entity)))             .collect::&gt;())     } } <\/code><\/pre>\n<p>\u0422\u0430\u043a\u043e\u0439 \u043f\u043e\u0434\u0445\u043e\u0434 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043f\u0440\u0435\u0434\u043e\u0442\u0432\u0440\u0430\u0442\u0438\u0442\u044c \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0443 N+1, \u0442. \u043a. \u043a\u0430\u0436\u0434\u044b\u0439 \u0432\u044b\u0437\u043e\u0432 <code>DetailsLoader.load<\/code> \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u0438\u043d SQL \u0437\u0430\u043f\u0440\u043e\u0441, \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u044e\u0449\u0438\u0439 \u043f\u0430\u0447\u043a\u0443 <code>DetailsEntity<\/code>.<\/p>\n<h3>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430<\/h3>\n<p>GraphQL \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0438 \u0435\u0433\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u044b \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/blob\/master\/planets-service\/src\/graphql.rs\" rel=\"noopener noreferrer nofollow\"><em>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 GraphQL \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430<\/em><\/a><\/p>\n<pre><code class=\"rust\">#[derive(Interface, Clone)] #[graphql(     field(name = \"mean_radius\", type = \"&amp;CustomBigDecimal\"),     field(name = \"mass\", type = \"&amp;CustomBigInt\"), )] pub enum Details {     InhabitedPlanetDetails(InhabitedPlanetDetails),     UninhabitedPlanetDetails(UninhabitedPlanetDetails), }  #[derive(SimpleObject, Clone)] pub struct InhabitedPlanetDetails {     mean_radius: CustomBigDecimal,     mass: CustomBigInt,     \/\/\/ In billions     population: CustomBigDecimal, }  #[derive(SimpleObject, Clone)] pub struct UninhabitedPlanetDetails {     mean_radius: CustomBigDecimal,     mass: CustomBigInt, } <\/code><\/pre>\n<p>\u0417\u0434\u0435\u0441\u044c \u0432\u044b \u0442\u0430\u043a\u0436\u0435 \u043c\u043e\u0436\u0435\u0442\u0435 \u0432\u0438\u0434\u0435\u0442\u044c, \u0447\u0442\u043e \u0435\u0441\u043b\u0438 \u0432 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0435 \u043d\u0435\u0442 \u043d\u0438 \u043e\u0434\u043d\u043e\u0433\u043e \u043f\u043e\u043b\u044f \u0441\u043e &#171;\u0441\u043b\u043e\u0436\u043d\u044b\u043c&#187; \u0440\u0435\u0437\u043e\u043b\u0432\u0435\u0440\u043e\u043c, \u0442\u043e \u043e\u043d\u0430 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u0430 \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u0430 <code>SimpleObject<\/code>.<\/p>\n<h3>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u043e\u0433\u043e \u0441\u043a\u0430\u043b\u044f\u0440\u043d\u043e\u0433\u043e \u0442\u0438\u043f\u0430<\/h3>\n<p>\u041a\u0430\u0441\u0442\u043e\u043c\u043d\u044b\u0435 \u0441\u043a\u0430\u043b\u044f\u0440\u044b \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u044e\u0442 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u043a\u0430\u043a \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0442\u044c \u0438 \u043a\u0430\u043a \u043f\u0430\u0440\u0441\u0438\u0442\u044c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0451\u043d\u043d\u043e\u0433\u043e \u0442\u0438\u043f\u0430. \u041f\u0440\u043e\u0435\u043a\u0442 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0434\u0432\u0430 \u043f\u0440\u0438\u043c\u0435\u0440\u0430 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u044b\u0445 \u0441\u043a\u0430\u043b\u044f\u0440\u043e\u0432; \u043e\u0431\u0430 \u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u043e\u0431\u0451\u0440\u0442\u043a\u0430\u043c\u0438 \u0434\u043b\u044f \u0447\u0438\u0441\u043b\u043e\u0432\u044b\u0445 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440 (\u0442. \u043a. \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u0432\u043d\u0435\u0448\u043d\u0438\u0439 \u0442\u0440\u0435\u0439\u0442 \u043d\u0430 \u0432\u043d\u0435\u0448\u043d\u0435\u0439 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0435 \u0438\u0437-\u0437\u0430 <a href=\"https:\/\/doc.rust-lang.org\/book\/ch10-02-traits.html#implementing-a-trait-on-a-type\" rel=\"noopener noreferrer nofollow\"><em>orphan rule<\/em><\/a>). \u042d\u0442\u0438 \u043e\u0431\u0451\u0440\u0442\u043a\u0438 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u044b \u0442\u0430\u043a:<\/p>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/blob\/master\/planets-service\/src\/graphql.rs\" rel=\"noopener noreferrer nofollow\"><em>\u041a\u0430\u0441\u0442\u043e\u043c\u043d\u044b\u0439 \u0441\u043a\u0430\u043b\u044f\u0440: \u043e\u0431\u0451\u0440\u0442\u043a\u0430 \u0434\u043b\u044f BigInt<\/em><\/a><\/p>\n<pre><code class=\"rust\">#[derive(Clone)] pub struct CustomBigInt(BigDecimal);  #[Scalar(name = \"BigInt\")] impl ScalarType for CustomBigInt {     fn parse(value: Value) -&gt; InputValueResult {         match value {             Value::String(s) =&gt; {                 let parsed_value = BigDecimal::from_str(&amp;s)?;                 Ok(CustomBigInt(parsed_value))             }             _ =&gt; Err(InputValueError::expected_type(value)),         }     }      fn to_value(&amp;self) -&gt; Value {         Value::String(format!(\"{:e}\", &amp;self))     } }  impl LowerExp for CustomBigInt {     fn fmt(&amp;self, f: &amp;mut Formatter&lt;'_&gt;) -&gt; fmt::Result {         let val = &amp;self.0.to_f64().expect(\"Can't convert BigDecimal\");         LowerExp::fmt(val, f)     } } <\/code><\/pre>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/blob\/master\/planets-service\/src\/graphql.rs\" rel=\"noopener noreferrer nofollow\"><em>\u041a\u0430\u0441\u0442\u043e\u043c\u043d\u044b\u0439 \u0441\u043a\u0430\u043b\u044f\u0440: \u043e\u0431\u0451\u0440\u0442\u043a\u0430 \u0434\u043b\u044f BigDecimal<\/em><\/a><\/p>\n<pre><code class=\"rust\">#[derive(Clone)] pub struct CustomBigDecimal(BigDecimal);  #[Scalar(name = \"BigDecimal\")] impl ScalarType for CustomBigDecimal {     fn parse(value: Value) -&gt; InputValueResult {         match value {             Value::String(s) =&gt; {                 let parsed_value = BigDecimal::from_str(&amp;s)?;                 Ok(CustomBigDecimal(parsed_value))             }             _ =&gt; Err(InputValueError::expected_type(value)),         }     }      fn to_value(&amp;self) -&gt; Value {         Value::String(self.0.to_string())     } } <\/code><\/pre>\n<p>\u0412 \u043f\u0435\u0440\u0432\u043e\u043c \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u0442\u0430\u043a\u0436\u0435 \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u043e, \u043a\u0430\u043a \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0433\u0438\u0433\u0430\u043d\u0442\u0441\u043a\u043e\u0435 \u0447\u0438\u0441\u043b\u043e \u0432 \u0432\u0438\u0434\u0435 \u044d\u043a\u0441\u043f\u043e\u043d\u0435\u043d\u0446\u0438\u0430\u043b\u044c\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438.<\/p>\n<h3>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u043c\u0443\u0442\u0430\u0446\u0438\u0438<\/h3>\n<p>\u041c\u0443\u0442\u0430\u0446\u0438\u044f \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0430 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/blob\/master\/planets-service\/src\/graphql.rs\" rel=\"noopener noreferrer nofollow\"><em>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u043c\u0443\u0442\u0430\u0446\u0438\u0438<\/em><\/a><\/p>\n<pre><code class=\"rust\">pub struct Mutation;  #[Object] impl Mutation {     #[graphql(guard(RoleGuard(role = \"Role::Admin\")))]     async fn create_planet(&amp;self, ctx: &amp;Context&lt;'_&gt;, planet: PlanetInput) -&gt; Result {         let new_planet = NewPlanetEntity {             name: planet.name,             planet_type: planet.planet_type.to_string(),         };          let details = planet.details;         let new_planet_details = NewDetailsEntity {             mean_radius: details.mean_radius.0,             mass: BigDecimal::from_str(&amp;details.mass.0.to_string()).expect(\"Can't get BigDecimal from string\"),             population: details.population.map(|wrapper| { wrapper.0 }),             planet_id: 0,         };          let created_planet_entity = repository::create(new_planet, new_planet_details, &amp;get_conn_from_ctx(ctx))?;          let producer = ctx.data::().expect(\"Can't get Kafka producer\");         let message = serde_json::to_string(&amp;Planet::from(&amp;created_planet_entity)).expect(\"Can't serialize a planet\");         kafka::send_message(producer, message).await;          Ok(Planet::from(&amp;created_planet_entity))     } } <\/code><\/pre>\n<p>\u0427\u0442\u043e\u0431\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043e\u0431\u044a\u0435\u043a\u0442 \u043a\u0430\u043a \u0432\u0445\u043e\u0434\u043d\u043e\u0439 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 \u043c\u0443\u0442\u0430\u0446\u0438\u0438, \u043d\u0430\u0434\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/blob\/master\/planets-service\/src\/graphql.rs\" rel=\"noopener noreferrer nofollow\"><em>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 input type<\/em><\/a><\/p>\n<pre><code class=\"rust\">#[derive(InputObject)] struct PlanetInput {     name: String,     #[graphql(name = \"type\")]     planet_type: PlanetType,     details: DetailsInput, } <\/code><\/pre>\n<p>\u041c\u0443\u0442\u0430\u0446\u0438\u044f \u0437\u0430\u0449\u0438\u0449\u0435\u043d\u0430 <code>RoleGuard<\/code>&#8216;\u043e\u043c, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0433\u0430\u0440\u0430\u043d\u0442\u0438\u0440\u0443\u0435\u0442 \u0447\u0442\u043e \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438 \u0441 \u0440\u043e\u043b\u0431\u044e <code>Admin<\/code> \u043c\u043e\u0433\u0443\u0442 \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0435\u0451. \u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c, \u0434\u043b\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439 \u043c\u0443\u0442\u0430\u0446\u0438\u0438:<\/p>\n<p><em>Example of mutation usage<\/em><\/p>\n<pre><code>mutation {   createPlanet(     planet: {       name: \"test_planet\"       type: TERRESTRIAL_PLANET       details: { meanRadius: \"10.5\", mass: \"8.8e24\", population: \"0.5\" }     }   ) {     id   } } <\/code><\/pre>\n<p>\u0432\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a <code>Authorization<\/code> \u0441 JWT, \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u043c \u0438\u0437 <code>auth-service<\/code> (\u044d\u0442\u043e \u0431\u0443\u0434\u0435\u0442 \u043e\u043f\u0438\u0441\u0430\u043d\u043e \u0434\u0430\u043b\u0435\u0435).<\/p>\n<h3>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438<\/h3>\n<p>\u0412 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0438 \u043c\u0443\u0442\u0430\u0446\u0438\u0438 \u0432\u044b\u0448\u0435 \u0432\u044b \u043c\u043e\u0433\u043b\u0438 \u0432\u0438\u0434\u0435\u0442\u044c \u0447\u0442\u043e \u043f\u0440\u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0438 \u043d\u043e\u0432\u043e\u0439 \u043f\u043b\u0430\u043d\u0435\u0442\u044b \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435:<\/p>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/blob\/master\/planets-service\/src\/graphql.rs\" rel=\"noopener noreferrer nofollow\"><em>\u041e\u0442\u043f\u0440\u0430\u0432\u043a\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0432 Kafka<\/em><\/a><\/p>\n<pre><code class=\"rust\">let producer = ctx.data::().expect(\"Can't get Kafka producer\"); let message = serde_json::to_string(&amp;Planet::from(&amp;created_planet_entity)).expect(\"Can't serialize a planet\"); kafka::send_message(producer, message).await; <\/code><\/pre>\n<p>\u041a\u043b\u0438\u0435\u043d\u0442 API \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d \u043e\u0431 \u044d\u0442\u043e\u043c \u0441\u043e\u0431\u044b\u0442\u0438\u0438 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438, \u0441\u043b\u0443\u0448\u0430\u044e\u0449\u0435\u0439 Kafka consumer:<\/p>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/blob\/master\/planets-service\/src\/graphql.rs\" rel=\"noopener noreferrer nofollow\"><em>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438<\/em><\/a><\/p>\n<pre><code class=\"rust\">pub struct Subscription;  #[Subscription] impl Subscription {     async fn latest_planet&lt;'ctx&gt;(&amp;self, ctx: &amp;'ctx Context&lt;'_&gt;) -&gt; impl Stream + 'ctx {         let kafka_consumer_counter = ctx.data::&gt;().expect(\"Can't get Kafka consumer counter\");         let consumer_group_id = kafka::get_kafka_consumer_group_id(kafka_consumer_counter);         let consumer = kafka::create_consumer(consumer_group_id);          async_stream::stream! {             let mut stream = consumer.start();              while let Some(value) = stream.next().await {                 yield match value {                     Ok(message) =&gt; {                         let payload = message.payload().expect(\"Kafka message should contain payload\");                         let message = String::from_utf8_lossy(payload).to_string();                         serde_json::from_str(&amp;message).expect(\"Can't deserialize a planet\")                     }                     Err(e) =&gt; panic!(\"Error while Kafka message processing: {}\", e)                 };             }         }     } } <\/code><\/pre>\n<p>\u041f\u043e\u0434\u043f\u0438\u0441\u043a\u0430 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0430 \u0442\u0430\u043a \u0436\u0435, \u043a\u0430\u043a \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u0438 \u043c\u0443\u0442\u0430\u0446\u0438\u0438:<\/p>\n<p><em>\u041f\u0440\u0438\u043c\u0435\u0440 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438<\/em><\/p>\n<pre><code>subscription {   latestPlanet {     id     name     type     details {       meanRadius     }   } } <\/code><\/pre>\n<p>\u041f\u043e\u0434\u043f\u0438\u0441\u043a\u0438 \u0434\u043e\u043b\u0436\u043d\u044b \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c\u0441\u044f \u043d\u0430 <code>ws:\/\/localhost:8001<\/code>.<\/p>\n<h3>\u0418\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b<\/h3>\n<p>\u0422\u0435\u0441\u0442\u044b \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0438 \u043c\u0443\u0442\u0430\u0446\u0438\u0439 \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0442\u0430\u043a:<\/p>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/blob\/master\/planets-service\/tests\/query_tests.rs\" rel=\"noopener noreferrer nofollow\"><em>\u0422\u0435\u0441\u0442 \u0437\u0430\u043f\u0440\u043e\u0441\u0430<\/em><\/a><\/p>\n<pre><code class=\"rust\">#[actix_rt::test] async fn test_get_planets() {     let docker = Cli::default();     let (_pg_container, pool) = common::setup(&amp;docker);      let mut service = test::init_service(App::new()         .configure(configure_service)         .data(create_schema_with_context(pool))     ).await;      let query = \"         {             getPlanets {                 id                 name                 type                 details {                     meanRadius                     mass                     ... on InhabitedPlanetDetails {                         population                     }                 }             }         }         \".to_string();      let request_body = GraphQLCustomRequest {         query,         variables: Map::new(),     };      let request = test::TestRequest::post().uri(\"\/\").set_json(&amp;request_body).to_request();      let response: GraphQLCustomResponse = test::read_response_json(&amp;mut service, request).await;      fn get_planet_as_json(all_planets: &amp;serde_json::Value, index: i32) -&gt; &amp;serde_json::Value {         jsonpath::select(all_planets, &amp;format!(\"$.getPlanets[{}]\", index)).expect(\"Can't get planet by JSON path\")[0]     }      let mercury_json = get_planet_as_json(&amp;response.data, 0);     common::check_planet(mercury_json, 1, \"Mercury\", \"TERRESTRIAL_PLANET\", \"2439.7\");      let earth_json = get_planet_as_json(&amp;response.data, 2);     common::check_planet(earth_json, 3, \"Earth\", \"TERRESTRIAL_PLANET\", \"6371.0\");      let neptune_json = get_planet_as_json(&amp;response.data, 7);     common::check_planet(neptune_json, 8, \"Neptune\", \"ICE_GIANT\", \"24622.0\"); } <\/code><\/pre>\n<p>\u0415\u0441\u043b\u0438 \u0447\u0430\u0441\u0442\u044c \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043f\u0435\u0440\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0430 \u0432 \u0434\u0440\u0443\u0433\u043e\u043c \u0437\u0430\u043f\u0440\u043e\u0441\u0435, \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c <a href=\"https:\/\/graphql.org\/learn\/queries\/#fragments\" rel=\"noopener noreferrer nofollow\">\u0444\u0440\u0430\u0433\u043c\u0435\u043d\u0442\u044b<\/a>:<\/p>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/blob\/master\/planets-service\/tests\/query_tests.rs\" rel=\"noopener noreferrer nofollow\"><em>\u0422\u0435\u0441\u0442 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u0444\u0440\u0430\u0433\u043c\u0435\u043d\u0442\u0430<\/em><\/a><\/p>\n<pre><code class=\"rust\">const PLANET_FRAGMENT: &amp;str = \"     fragment planetFragment on Planet {         id         name         type         details {             meanRadius             mass             ... on InhabitedPlanetDetails {                 population             }         }     } \";  #[actix_rt::test] async fn test_get_planet_by_id() {     ...      let query = \"         {             getPlanet(id: 3) {                 ... planetFragment             }         }         \".to_string() + PLANET_FRAGMENT;      let request_body = GraphQLCustomRequest {         query,         variables: Map::new(),     };      ... } <\/code><\/pre>\n<p>\u0427\u0442\u043e\u0431\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c <a href=\"https:\/\/graphql.org\/learn\/queries\/#variables\" rel=\"noopener noreferrer nofollow\">\u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435<\/a>, \u0437\u0430\u043f\u0438\u0448\u0438\u0442\u0435 \u0442\u0435\u0441\u0442 \u0442\u0430\u043a:<\/p>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/blob\/master\/planets-service\/tests\/query_tests.rs\" rel=\"noopener noreferrer nofollow\"><em>\u0422\u0435\u0441\u0442 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u0444\u0440\u0430\u0433\u043c\u0435\u043d\u0442\u0430 \u0438 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0439<\/em><\/a><\/p>\n<pre><code class=\"rust\">#[actix_rt::test] async fn test_get_planet_by_id_with_variable() {     ...      let query = \"         query testPlanetById($planetId: String!) {             getPlanet(id: $planetId) {                 ... planetFragment             }         }\".to_string() + PLANET_FRAGMENT;      let jupiter_id = 5;     let mut variables = Map::new();     variables.insert(\"planetId\".to_string(), jupiter_id.into());      let request_body = GraphQLCustomRequest {         query,         variables,     };      ... } <\/code><\/pre>\n<p>\u0412 \u044d\u0442\u043e\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 <code>Testcontainers-rs<\/code>, \u0447\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u0442\u044c \u0442\u0435\u0441\u0442\u043e\u0432\u043e\u0435 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u0435, \u0442\u043e \u0435\u0441\u0442\u044c, \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e \u0411\u0414 PostgreSQL.<\/p>\n<h3>\u041a\u043b\u0438\u0435\u043d\u0442 \u043a GraphQL API<\/h3>\n<p>\u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043a\u043e\u0434 \u0438\u0437 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0433\u043e \u0440\u0430\u0437\u0434\u0435\u043b\u0430 \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u043a \u0432\u043d\u0435\u0448\u043d\u0435\u043c\u0443 GraphQL API. \u0422\u0430\u043a\u0436\u0435 \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0442 \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0435 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, <a href=\"https:\/\/github.com\/graphql-rust\/graphql-client\" rel=\"noopener noreferrer nofollow\">graphql-client<\/a>, \u043d\u043e \u044f \u0438\u0445 \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b.<\/p>\n<h3>\u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u044c API<\/h3>\n<p>\u0421\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0442 \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u0443\u0433\u0440\u043e\u0437\u044b \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u0438 GraphQL API (\u0441\u043c. <a href=\"https:\/\/leapgraph.com\/graphql-api-security\" rel=\"noopener noreferrer nofollow\">\u0441\u043f\u0438\u0441\u043e\u043a<\/a>); \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0438\u0437 \u043d\u0438\u0445.<\/p>\n<h4>\u041e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u044f \u0433\u043b\u0443\u0431\u0438\u043d\u044b \u0438 \u0441\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u0430<\/h4>\n<p>\u0415\u0441\u043b\u0438 \u0431\u044b \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 <code>Satellite<\/code> \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043b\u0430 \u043f\u043e\u043b\u0435 <code>planet<\/code>, \u0431\u044b\u043b \u0431\u044b \u0432\u043e\u0437\u043c\u043e\u0436\u0435\u043d \u0442\u0430\u043a\u043e\u0439 \u0437\u0430\u043f\u0440\u043e\u0441:<\/p>\n<p><em>\u041f\u0440\u0438\u043c\u0435\u0440 \u0442\u044f\u0436\u0451\u043b\u043e\u0433\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u0430<\/em><\/p>\n<pre><code>{   getPlanet(id: \"1\") {     satellites {       planet {         satellites {           planet {             satellites {               ... # more deep nesting!             }           }         }       }     }   } } <\/code><\/pre>\n<p>\u0421\u0434\u0435\u043b\u0430\u0442\u044c \u0442\u0430\u043a\u043e\u0439 \u0437\u0430\u043f\u0440\u043e\u0441 \u043d\u0435\u0432\u0430\u043b\u0438\u0434\u043d\u044b\u043c \u043c\u043e\u0436\u043d\u043e \u0442\u0430\u043a:<\/p>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/blob\/master\/planets-service\/src\/lib.rs\" rel=\"noopener noreferrer nofollow\"><em>\u041f\u0440\u0438\u043c\u0435\u0440 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u044f \u0433\u043b\u0443\u0431\u0438\u043d\u044b \u0438 \u0441\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u0430<\/em><\/a><\/p>\n<pre><code class=\"rust\">pub fn create_schema_with_context(pool: PgPool) -&gt; Schema {     ...      Schema::build(Query, Mutation, Subscription)         .limit_depth(3)         .limit_complexity(15)      ... } <\/code><\/pre>\n<p>\u0421\u0442\u043e\u0438\u0442 \u043e\u0442\u043c\u0435\u0442\u0438\u0442\u044c, \u0447\u0442\u043e \u043f\u0440\u0438 \u0443\u043a\u0430\u0437\u0430\u043d\u0438\u0438 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0439 \u0432\u044b\u0448\u0435 \u043c\u043e\u0436\u0435\u0442 \u043f\u0435\u0440\u0435\u0441\u0442\u0430\u0442\u044c \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c\u0441\u044f \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u0432 GraphQL IDE. \u042d\u0442\u043e \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043f\u043e\u0442\u043e\u043c\u0443, \u0447\u0442\u043e IDE \u043f\u044b\u0442\u0430\u0435\u0442\u0441\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c introspection query, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0438\u043c\u0435\u0435\u0442 \u0437\u0430\u043c\u0435\u0442\u043d\u044b\u0435 \u0433\u043b\u0443\u0431\u0438\u043d\u0443 \u0438 \u0441\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u044c.<\/p>\n<h4>\u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f<\/h4>\n<p>\u042d\u0442\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u0430 \u0432 <code>auth-service<\/code> \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u043a\u0440\u044d\u0439\u0442\u043e\u0432 <code>argonautica<\/code> \u0438 <code>jsonwebtoken<\/code>. \u041f\u0435\u0440\u0432\u044b\u0439 \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0437\u0430 \u0445\u044d\u0448\u0438\u0440\u043e\u043e\u0432\u0430\u043d\u0438\u0435 \u043f\u0430\u0440\u043e\u043b\u0435\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c\u0430 <a href=\"https:\/\/en.wikipedia.org\/wiki\/Argon2\" rel=\"noopener noreferrer nofollow\">Argon2<\/a>. \u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u044b \u0438\u0441\u043a\u043b\u044e\u0447\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0432 \u0434\u0435\u043c\u043e\u043d\u0441\u0442\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0445 \u0446\u0435\u043b\u044f\u0445; \u043f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u0440\u043e\u0432\u0435\u0434\u0438\u0442\u0435 \u0438\u0437\u0443\u0447\u0438\u0442\u0435 \u0432\u043e\u043f\u0440\u043e\u0441 \u0431\u043e\u043b\u0435\u0435 \u0442\u0449\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u0440\u0435\u0435\u0434 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u0432 \u043f\u0440\u043e\u0434\u0430\u043a\u0448\u0435\u043d\u0435.<\/p>\n<p>\u0420\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043a\u0430\u043a \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d \u0432\u0445\u043e\u0434 \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0443:<\/p>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/blob\/master\/auth-service\/src\/graphql.rs\" rel=\"noopener noreferrer nofollow\"><em>\u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0432\u0445\u043e\u0434\u0430 \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0443<\/em><\/a><\/p>\n<pre><code class=\"rust\">pub struct Mutation;  #[Object] impl Mutation {      async fn sign_in(&amp;self, ctx: &amp;Context&lt;'_&gt;, input: SignInInput) -&gt; Result {         let maybe_user = repository::get_user(&amp;input.username, &amp;get_conn_from_ctx(ctx)).ok();          if let Some(user) = maybe_user {             if let Ok(matching) = verify_password(&amp;user.hash, &amp;input.password) {                 if matching {                     let role = AuthRole::from_str(user.role.as_str()).expect(\"Can't convert &amp;str to AuthRole\");                     return Ok(common_utils::create_token(user.username, role));                 }             }         }          Err(Error::new(\"Can't authenticate a user\"))     } }  #[derive(InputObject)] struct SignInInput {     username: String,     password: String, } <\/code><\/pre>\n<p>\u041f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u0438 <code>verify_password<\/code> \u043c\u043e\u0436\u043d\u043e \u0432 <a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/blob\/master\/auth-service\/src\/utils.rs\" rel=\"noopener noreferrer nofollow\">\u043c\u043e\u0434\u0443\u043b\u0435<\/a> and <code>create_token<\/code> in <code>common_utils<\/code> <a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/blob\/master\/common-utils\/src\/lib.rs\" rel=\"noopener noreferrer nofollow\">module<\/a> <code>utils<\/code>. \u041a\u0430\u043a \u0432\u044b \u043c\u043e\u0433\u043b\u0438 \u0431\u044b \u043e\u0436\u0438\u0434\u0430\u0442\u044c, \u0444\u0443\u043d\u043a\u0446\u0438\u044f <code>sign_in<\/code> \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 JWT, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432 \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u043c \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0432 \u0434\u0440\u0443\u0433\u0438\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u0430\u0445.<\/p>\n<p>\u0414\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f JWT \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u043c\u0443\u0442\u0430\u0446\u0438\u044e:<\/p>\n<p><em>\u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 JWT<\/em><\/p>\n<pre><code>mutation {   signIn(input: { username: \"john_doe\", password: \"password\" }) } <\/code><\/pre>\n<p>\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b <em>john_doe\/password<\/em>. \u0412\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u043e\u0433\u043e JWT \u0432 \u043f\u043e\u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u0437\u0430\u0449\u0438\u0449\u0451\u043d\u043d\u044b\u043c \u0440\u0435\u0441\u0443\u0440\u0441\u0430\u043c (\u0441\u043c. \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u0440\u0430\u0437\u0434\u0435\u043b).<\/p>\n<h4>\u0410\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f<\/h4>\n<p>\u0427\u0442\u043e\u0431\u044b \u0437\u0430\u043f\u0440\u043e\u0441\u0438\u0442\u044c \u0437\u0430\u0449\u0438\u0449\u0451\u043d\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435, \u0434\u043e\u0431\u0430\u0432\u044c\u0442\u0435 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a \u0432 HTTP \u0437\u0430\u043f\u0440\u043e\u0441 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 <code>Authorization: Bearer $JWT<\/code>. \u0424\u0443\u043d\u043a\u0446\u0438\u044f <code>index<\/code> \u0438\u0437\u0432\u043b\u0435\u0447\u0451\u0442 \u0440\u043e\u043b\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438\u0437 HTTP \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u0438 \u0434\u043e\u0431\u0430\u0432\u0438\u0442 \u0435\u0451 \u0432 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b GraphQL \u0437\u0430\u043f\u0440\u043e\u0441\u0430\/\u043c\u0443\u0442\u0430\u0446\u0438\u0438:<\/p>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/blob\/master\/planets-service\/src\/lib.rs\" rel=\"noopener noreferrer nofollow\"><em>\u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0440\u043e\u043b\u0438<\/em><\/a><\/p>\n<pre><code class=\"rust\">async fn index(schema: web::Data, http_req: HttpRequest, req: Request) -&gt; Response {     let mut query = req.into_inner();      let maybe_role = common_utils::get_role(http_req);     if let Some(role) = maybe_role {         query = query.data(role);     }      schema.execute(query).await.into() } <\/code><\/pre>\n<p>\u041a \u0440\u0430\u043d\u0435\u0435 \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u043d\u043e\u0439 \u043c\u0443\u0442\u0430\u0446\u0438\u0438 <code>create_planet<\/code> \u043f\u0440\u0438\u043c\u0435\u043d\u0451\u043d \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u0430\u0442\u0440\u0438\u0431\u0443\u0442:<\/p>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/blob\/master\/planets-service\/src\/graphql.rs\" rel=\"noopener noreferrer nofollow\"><em>\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u0433\u0430\u0440\u0434\u0430<\/em><\/a><\/p>\n<pre><code class=\"rust\">#[graphql(guard(RoleGuard(role = \"Role::Admin\")))] <\/code><\/pre>\n<p>\u0421\u0430\u043c \u0433\u0430\u0440\u0434 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d \u0442\u0430\u043a:<\/p>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/blob\/master\/planets-service\/src\/graphql.rs\" rel=\"noopener noreferrer nofollow\"><em>\u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0433\u0430\u0440\u0434\u0430<\/em><\/a><\/p>\n<pre><code class=\"rust\">struct RoleGuard {     role: Role, }  #[async_trait::async_trait] impl Guard for RoleGuard {     async fn check(&amp;self, ctx: &amp;Context&lt;'_&gt;) -&gt; Result&lt;()&gt; {         if ctx.data_opt::() == Some(&amp;self.role) {             Ok(())         } else {             Err(\"Forbidden\".into())         }     } } <\/code><\/pre>\n<p>\u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c, \u0435\u0441\u043b\u0438 \u0432\u044b \u043d\u0435 \u0443\u043a\u0430\u0436\u0435\u0442\u0435 \u0442\u043e\u043a\u0435\u043d, \u0441\u0435\u0440\u0432\u0435\u0440 \u043e\u0442\u0432\u0435\u0442\u0438\u0442 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435\u043c &#171;Forbidden&#187;.<\/p>\n<h3>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u043f\u0435\u0440\u0435\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u044f<\/h3>\n<p>GraphQL \u043f\u0435\u0440\u0435\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u0435 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043e \u0442\u0430\u043a:<\/p>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/blob\/master\/satellites-service\/src\/graphql.rs\" rel=\"noopener noreferrer nofollow\"><em>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u043f\u0435\u0440\u0435\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u044f<\/em><\/a><\/p>\n<pre><code class=\"rust\">#[derive(SimpleObject)] struct Satellite {     ...     life_exists: LifeExists, }  #[derive(Copy, Clone, Eq, PartialEq, Debug, Enum, EnumString)] #[strum(serialize_all = \"SCREAMING_SNAKE_CASE\")] pub enum LifeExists {     Yes,     OpenQuestion,     NoData, } <\/code><\/pre>\n<h3>\u0420\u0430\u0431\u043e\u0442\u0430 \u0441 \u0434\u0430\u0442\u0430\u043c\u0438<\/h3>\n<p>Async-graphql \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0442\u0438\u043f\u044b \u0434\u0430\u0442\u044b\/\u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0438\u0437 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 <code>chrono<\/code>, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u0442\u0430\u043a\u0438\u0435 \u043f\u043e\u043b\u044f \u043a\u0430\u043a \u043e\u0431\u044b\u0447\u043d\u043e:<\/p>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/blob\/master\/satellites-service\/src\/graphql.rs\" rel=\"noopener noreferrer nofollow\"><em>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u043f\u043e\u043b\u044f \u0441 \u0434\u0430\u0442\u043e\u0439<\/em><\/a><\/p>\n<pre><code class=\"rust\">#[derive(SimpleObject)] struct Satellite {     ...     first_spacecraft_landing_date: Option, } <\/code><\/pre>\n<h3>\u041f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430 Apollo Federation<\/h3>\n<p>\u041e\u0434\u043d\u0430 \u0438\u0437 \u0446\u0435\u043b\u0435\u0439 <code>satellites-service<\/code> \u2014 \u043f\u0440\u043e\u0434\u0435\u043c\u043e\u043d\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043a\u0430\u043a \u0440\u0430\u0441\u043f\u0440\u0435\u0434\u0435\u043b\u0451\u043d\u043d\u0430\u044f GraphQL <a href=\"https:\/\/www.apollographql.com\/docs\/federation\/entities\/\" rel=\"noopener noreferrer nofollow\"><em>\u0441\u0443\u0449\u043d\u043e\u0441\u0442\u044c<\/em><\/a> (<code>Planet<\/code>) \u043c\u043e\u0436\u0435\u0442 \u0440\u0435\u0437\u043e\u043b\u0432\u0438\u0442\u044c\u0441\u044f \u0432 \u0434\u0432\u0443\u0445 (\u0438\u043b\u0438 \u0431\u043e\u043b\u0435\u0435) \u0441\u0435\u0440\u0432\u0438\u0441\u0430\u0445 \u0438 \u0437\u0430\u0442\u0435\u043c \u0437\u0430\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u0442\u044c\u0441\u044f \u0447\u0435\u0440\u0435\u0437 Apollo Server.<\/p>\n<p>\u0422\u0438\u043f <code>Planet<\/code> \u0431\u044b\u043b \u0440\u0430\u043d\u0435\u0435 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0451\u043d \u0432 <code>planets-service<\/code> \u0442\u0430\u043a:<\/p>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/blob\/master\/planets-service\/src\/graphql.rs\" rel=\"noopener noreferrer nofollow\"><em>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u0442\u0438\u043f\u0430 <\/em><\/a><code>Planet<\/code> \u0432 <code>planets-service<\/code><\/p>\n<pre><code class=\"rust\">#[derive(Serialize, Deserialize)] struct Planet {     id: ID,     name: String,     planet_type: PlanetType, } <\/code><\/pre>\n<p>\u0422\u0430\u043a\u0436\u0435 \u0432 <code>planets-service<\/code> \u0442\u0438\u043f <code>Planet<\/code> \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u044c\u044e:<\/p>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/blob\/master\/planets-service\/src\/graphql.rs\" rel=\"noopener noreferrer nofollow\"><em>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0438 <\/em><\/a><code>Planet<\/code><\/p>\n<pre><code class=\"rust\">#[Object] impl Query {     #[graphql(entity)]     async fn find_planet_by_id(&amp;self, ctx: &amp;Context&lt;'_&gt;, id: ID) -&gt; Option {         find_planet_by_id_internal(ctx, id)     } } <\/code><\/pre>\n<p><code>satellites-service<\/code> \u0440\u0430\u0441\u0448\u0438\u0440\u044f\u0435\u0442 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u044c <code>Planet<\/code> \u043f\u0443\u0442\u0451\u043c \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043f\u043e\u043b\u044f <code>satellites<\/code>:<\/p>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/blob\/master\/satellites-service\/src\/graphql.rs\" rel=\"noopener noreferrer nofollow\"><em>\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435 \u0442\u0438\u043f\u0430 <\/em><\/a><code>Planet<\/code> \u0432 <code>satellites-service<\/code><\/p>\n<pre><code class=\"rust\">struct Planet {     id: ID }  #[Object(extends)] impl Planet {     #[graphql(external)]     async fn id(&amp;self) -&gt; &amp;ID {         &amp;self.id     }      async fn satellites(&amp;self, ctx: &amp;Context&lt;'_&gt;) -&gt; Vec {         let id = self.id.to_string().parse::().expect(\"Can't get id from String\");         repository::get_by_planet_id(id, &amp;get_conn_from_ctx(ctx)).expect(\"Can't get satellites of planet\")             .iter()             .map(|e| { Satellite::from(e) })             .collect()     } } <\/code><\/pre>\n<p>\u0422\u0430\u043a\u0436\u0435 \u0432\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u043f\u043e\u0438\u0441\u043a\u0430 \u0434\u043b\u044f \u0440\u0430\u0441\u0448\u0438\u0440\u044f\u0435\u043c\u043e\u0433\u043e \u0442\u0438\u043f\u0430. \u0412 \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u043d\u0438\u0436\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u043f\u0440\u043e\u0441\u0442\u043e \u0441\u043e\u0437\u0434\u0430\u0451\u0442 \u043d\u043e\u0432\u044b\u0439 \u0438\u043d\u0441\u0442\u0430\u043d\u0441 <code>Planet<\/code>:<\/p>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/blob\/master\/satellites-service\/src\/graphql.rs\" rel=\"noopener noreferrer nofollow\"><em>\u0424\u0443\u043d\u043a\u0446\u0438\u044f \u043f\u043e\u0438\u0441\u043a\u0430 \u0434\u043b\u044f \u0442\u0438\u043f\u0430 <\/em><\/a><code>Planet<\/code><\/p>\n<pre><code class=\"rust\">#[Object] impl Query {      #[graphql(entity)]     async fn get_planet_by_id(&amp;self, id: ID) -&gt; Planet {         Planet { id }     } } <\/code><\/pre>\n<p>Async-graphql \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442 \u0434\u0432\u0430 \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 (<code>_service<\/code> and <code>_entities<\/code>), \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0431\u0443\u0434\u0443\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u044b Apollo Server&#8217;\u043e\u043c. \u042d\u0442\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u2014 \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0435, \u0442\u043e \u0435\u0441\u0442\u044c \u043e\u043d\u0438 \u043d\u0435 \u0431\u0443\u0434\u0443\u0442 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u044b \u0432 API Apollo Server&#8217;\u0430. \u041a\u043e\u043d\u0435\u0447\u043d\u043e, \u0441\u0435\u0440\u0432\u0438\u0441 \u0441 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u043e\u0439 Apollo Federation \u043f\u043e-\u043f\u0440\u0435\u0436\u043d\u0435\u043c\u0443 \u043c\u043e\u0436\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0430\u0432\u0442\u043e\u043d\u043e\u043c\u043d\u043e.<\/p>\n<h3>Apollo Server<\/h3>\n<p>Apollo Server \u0438 Apollo Federation \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u044e\u0442 \u0434\u043e\u0441\u0442\u0438\u0447\u044c \u0434\u0432\u0435 \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u0446\u0435\u043b\u0438:<\/p>\n<ul>\n<li>\n<p>\u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0435\u0434\u0438\u043d\u0443\u044e \u0442\u043e\u0447\u043a\u0443 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043a \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u043c GraphQL API<\/p>\n<\/li>\n<li>\n<p>\u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0435\u0434\u0438\u043d\u044b\u0439 \u0433\u0440\u0430\u0444 \u0434\u0430\u043d\u043d\u044b\u0445 \u0438\u0437 \u0440\u0430\u0441\u043f\u0440\u0435\u0434\u0435\u043b\u0451\u043d\u043d\u044b\u0445 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0435\u0439<\/p>\n<\/li>\n<\/ul>\n<p>\u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c, \u0434\u0430\u0436\u0435 \u0435\u0441\u043b\u0438 \u0432\u044b \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0435 \u0440\u0430\u0441\u043f\u0440\u0435\u0434\u0435\u043b\u0451\u043d\u043d\u044b\u0435 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0438, \u0434\u043b\u044f frontend \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u043e\u0432 \u0443\u0434\u043e\u0431\u043d\u0435\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043e\u0434\u043d\u0443 \u0442\u043e\u0447\u043a\u0443 \u0434\u043e\u0441\u0442\u0443\u043f\u0430, \u0447\u0435\u043c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e.<\/p>\n<p>\u0421\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442 \u0438 \u0434\u0440\u0443\u0433\u043e\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0435\u0434\u0438\u043d\u043e\u0439 GraphQL \u0441\u0445\u0435\u043c\u044b, <a href=\"https:\/\/www.graphql-tools.com\/docs\/schema-stitching\/\" rel=\"noopener noreferrer nofollow\">schema stitching<\/a>, \u043d\u043e \u043f\u043e\u043a\u0430 \u0447\u0442\u043e \u044f \u0435\u0433\u043e \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b.<\/p>\n<p>\u041c\u043e\u0434\u0443\u043b\u044c \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u043a\u043e\u0434:<\/p>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/blob\/master\/apollo-server\/package.json\" rel=\"noopener noreferrer nofollow\"><em>\u041c\u0435\u0442\u0430-\u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u0438 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438<\/em><\/a><\/p>\n<pre><code>{   \"name\": \"api-gateway\",   \"main\": \"gateway.js\",   \"scripts\": {     \"start-gateway\": \"nodemon gateway.js\"   },   \"devDependencies\": {     \"concurrently\": \"5.3.0\",     \"nodemon\": \"2.0.6\"   },   \"dependencies\": {     \"@apollo\/gateway\": \"0.21.3\",     \"apollo-server\": \"2.19.0\",     \"graphql\": \"15.4.0\"   } } <\/code><\/pre>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/blob\/master\/apollo-server\/gateway.js\" rel=\"noopener noreferrer nofollow\"><em>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 Apollo Server<\/em><\/a><\/p>\n<pre><code class=\"javascript\">const {ApolloServer} = require(\"apollo-server\"); const {ApolloGateway, RemoteGraphQLDataSource} = require(\"@apollo\/gateway\");  class AuthenticatedDataSource extends RemoteGraphQLDataSource {     willSendRequest({request, context}) {         if (context.authHeaderValue) {             request.http.headers.set('Authorization', context.authHeaderValue);         }     } }  let node_env = process.env.NODE_ENV;  function get_service_url(service_name, port) {     let host;     switch (node_env) {         case 'docker':             host = service_name;             break;         case 'local': {             host = 'localhost';             break         }     }      return \"http:\/\/\" + host + \":\" + port; }  const gateway = new ApolloGateway({     serviceList: [         {name: \"planets-service\", url: get_service_url(\"planets-service\", 8001)},         {name: \"satellites-service\", url: get_service_url(\"satellites-service\", 8002)},         {name: \"auth-service\", url: get_service_url(\"auth-service\", 8003)},     ],     buildService({name, url}) {         return new AuthenticatedDataSource({url});     }, });  const server = new ApolloServer({     gateway, subscriptions: false, context: ({req}) =&gt; ({         authHeaderValue: req.headers.authorization     }) });  server.listen({host: \"0.0.0.0\", port: 4000}).then(({url}) =&gt; {     console.log(`? Server ready at ${url}`); }); <\/code><\/pre>\n<p>\u0415\u0441\u043b\u0438 \u043a\u043e\u0434 \u0432\u044b\u0448\u0435 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0443\u043f\u0440\u043e\u0449\u0451\u043d, \u043d\u0435 \u0441\u0442\u0435\u0441\u043d\u044f\u0439\u0442\u0435\u0441\u044c \u043f\u043e\u043f\u0440\u0430\u0432\u0438\u0442\u044c.<\/p>\n<p>\u0410\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f \u0432 <code>apollo-service<\/code> \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0442\u0430\u043a \u0436\u0435, \u043a\u0430\u043a \u0431\u044b\u043b\u043e \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u043e \u0440\u0430\u043d\u0435\u0435 \u0434\u043b\u044f Rust \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 (\u0432\u0430\u043c \u043d\u0430\u0434\u043e \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a <code>Authorization<\/code> \u0438 \u0435\u0433\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435).<\/p>\n<p>\u041f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u043d\u043e\u0435 \u043d\u0430 \u043b\u044e\u0431\u043e\u043c \u044f\u0437\u044b\u043a\u0435 \u0438\u043b\u0438 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0435, \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043d\u0438\u0436\u0435\u043b\u0435\u0436\u0430\u0449\u0435\u0433\u043e \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u043f\u043e\u0434 Apollo Server, \u0435\u0441\u043b\u0438 \u043e\u043d\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u0442 <a href=\"https:\/\/www.apollographql.com\/docs\/federation\/federation-spec\/\" rel=\"noopener noreferrer nofollow\">\u0441\u043f\u0435\u0446\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044e Federation<\/a>; \u0441\u043f\u0438\u0441\u043e\u043a \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a, \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u044e\u0449\u0438\u0445 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0443 \u044d\u0442\u043e\u0439 \u0441\u043f\u0435\u0446\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0432 <a href=\"https:\/\/www.apollographql.com\/docs\/federation\/other-servers\/\" rel=\"noopener noreferrer nofollow\">\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438<\/a>.<\/p>\n<p>\u041f\u0440\u0438 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043c\u043e\u0434\u0443\u043b\u044f \u044f \u0441\u0442\u043e\u043b\u043a\u043d\u0443\u043b\u0441\u044f \u0441\u043e \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c\u0438 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u044f\u043c\u0438:<\/p>\n<ul>\n<li>\n<p>Apollo Gateway <a href=\"https:\/\/github.com\/apollographql\/apollo-server\/issues\/3357\" rel=\"noopener noreferrer nofollow\">\u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442<\/a> \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438 (\u043d\u043e \u043e\u043d\u0438 \u043f\u043e-\u043f\u0440\u0435\u0436\u043d\u0435\u043c\u0443 \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0442 \u0432 standalone Rust \u0441\u0435\u0440\u0432\u0438\u0441\u0435)<\/p>\n<\/li>\n<li>\n<p>\u0441\u0435\u0440\u0432\u0438\u0441\u0443, \u043f\u044b\u0442\u0430\u044e\u0449\u0435\u043c\u0443\u0441\u044f \u0440\u0430\u0441\u0448\u0438\u0440\u0438\u0442\u044c GraphQL \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 <a href=\"https:\/\/github.com\/apollographql\/apollo-server\/issues\/2849\" rel=\"noopener noreferrer nofollow\">\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043e \u0435\u0433\u043e \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u0445 \u0438\u043c\u043f\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f\u0445<\/a><\/p>\n<\/li>\n<\/ul>\n<h3>\u0412\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u0441 \u0411\u0414<\/h3>\n<p>\u0423\u0440\u043e\u0432\u0435\u043d\u044c \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e PostgreSQL and Diesel. \u0415\u0441\u043b\u0438 \u0432\u044b \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0435 Docker \u043f\u0440\u0438 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u043c \u0437\u0430\u043f\u0443\u0441\u043a\u0435, \u0442\u043e \u043d\u0443\u0436\u043d\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c <code>diesel setup<\/code>, \u043d\u0430\u0445\u043e\u0434\u044f\u0441\u044c \u0432 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0438\u0437 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432. \u042d\u0442\u043e \u0441\u043e\u0437\u0434\u0430\u0441\u0442 \u043f\u0443\u0441\u0442\u0443\u044e \u0411\u0414, \u043a \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0434\u0430\u043b\u0435\u0435 \u0431\u0443\u0434\u0443\u0442 \u043f\u0440\u0438\u043c\u0435\u043d\u0435\u043d\u044b \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438, \u0441\u043e\u0437\u0434\u0430\u044e\u0449\u0438\u0435 \u0442\u0430\u0431\u043b\u0438\u0446\u044b \u0438 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u044e\u0449\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0435.<\/p>\n<h3>\u0417\u0430\u043f\u0443\u0441\u043a \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0438 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 API<\/h3>\n<p>\u041a\u0430\u043a \u0431\u044b\u043b\u043e \u043e\u0442\u043c\u0435\u0447\u0435\u043d\u043e \u0440\u0430\u043d\u0435\u0435, \u043f\u0440\u043e\u0435\u043a\u0442 \u043c\u043e\u0436\u043d\u043e \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0434\u0432\u0443\u043c\u044f \u0441\u043f\u043e\u0441\u043e\u0431\u0430\u043c\u0438:<\/p>\n<ul>\n<li>\n<p>\u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c Docker Compose (<a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/blob\/master\/docker-compose.yml\" rel=\"noopener noreferrer nofollow\">docker-compose.yml<\/a>)<\/p>\n<p>\u0417\u0434\u0435\u0441\u044c, \u0432 \u0441\u0432\u043e\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u044c, \u0442\u0430\u043a\u0436\u0435 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u044b \u0434\u0432\u0430 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0430:<\/p>\n<ul>\n<li>\n<p>\u0440\u0435\u0436\u0438\u043c \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 (\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e \u0441\u043e\u0431\u0440\u0430\u043d\u043d\u044b\u0435 \u043e\u0431\u0440\u0430\u0437\u044b)<\/p>\n<p><code>docker-compose up<\/code><\/p>\n<\/li>\n<li>\n<p>production mode (\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u0440\u0435\u043b\u0438\u0437\u043d\u044b\u0435 \u043e\u0431\u0440\u0430\u0437\u044b)<\/p>\n<p><code>docker-compose -f docker-compose.yml up<\/code><\/p>\n<\/li>\n<\/ul>\n<\/li>\n<li>\n<p>\u0431\u0435\u0437 Docker<\/p>\n<p>\u0417\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0435 \u043a\u0430\u0436\u0434\u044b\u0439 Rust \u0441\u0435\u0440\u0432\u0438\u0441 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <code>cargo run<\/code>, \u043f\u043e\u0442\u043e\u043c \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0435 Apollo Server:<\/p>\n<ul>\n<li>\n<p><code>cd<\/code> \u0432 \u043f\u0430\u043f\u043a\u0443 <code>apollo-server<\/code><\/p>\n<\/li>\n<li>\n<p>\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u0435 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e \u0441\u0440\u0435\u0434\u044b <code>NODE_ENV<\/code>, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, <code>set NODE_ENV=local<\/code> (\u0434\u043b\u044f Windows)<\/p>\n<\/li>\n<li>\n<p><code>npm install<\/code><\/p>\n<\/li>\n<li>\n<p><code>npm run start-gateway<\/code><\/p>\n<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>\u0423\u0441\u043f\u0435\u0448\u043d\u044b\u0439 \u0437\u0430\u043f\u0443\u0441\u043a <code>apollo-server<\/code> \u0434\u043e\u043b\u0436\u0435\u043d \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u0442\u0430\u043a:<\/p>\n<p><em>\u041b\u043e\u0433 \u0437\u0430\u043f\u0443\u0441\u043a\u0430 Apollo Server<\/em><\/p>\n<pre><code>[nodemon] 2.0.6 [nodemon] to restart at any time, enter `rs` [nodemon] watching path(s): *.* [nodemon] watching extensions: js,mjs,json [nodemon] starting `node gateway.js` Server ready at http:\/\/0.0.0.0:4000\/ <\/code><\/pre>\n<p>\u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u0435\u0440\u0435\u0439\u0442\u0438 \u043d\u0430 <code>http:\/\/localhost:4000<\/code> \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435 \u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u0443\u044e Playground IDE:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/81e\/b18\/a26\/81eb18a266f1ed5c08b861ccfbcee579.png\" width=\"3741\" height=\"2008\"><figcaption><\/figcaption><\/figure>\n<p>\u0417\u0434\u0435\u0441\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0442\u044c \u0437\u0430\u043f\u0440\u043e\u0441\u044b, \u043c\u0443\u0442\u0430\u0446\u0438\u0438 \u0438 \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438, \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0451\u043d\u043d\u044b\u0435 \u0432 \u043d\u0438\u0436\u0435\u043b\u0435\u0436\u0430\u0449\u0438\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u0430\u0445. \u041a\u0440\u043e\u043c\u0435 \u0442\u043e\u0433\u043e, \u043a\u0430\u0436\u0434\u044b\u0439 \u0438\u0437 \u044d\u0442\u0438\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u0438\u043c\u0435\u0435\u0442 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u0443\u044e Playground IDE.<\/p>\n<h3>\u0422\u0435\u0441\u0442 \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438<\/h3>\n<p>\u0427\u0442\u043e\u0431\u044b \u0443\u0431\u0435\u0434\u0438\u0442\u044c\u0441\u044f \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0430 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442, \u043e\u0442\u043a\u0440\u043e\u0439\u0442\u0435 \u0434\u0432\u0435 \u0432\u043a\u043b\u0430\u0434\u043a\u0438 \u043b\u044e\u0431\u043e\u0439 GraphQL IDE; \u0432 \u043f\u0435\u0440\u0432\u043e\u0439 \u043f\u043e\u0434\u043f\u0438\u0448\u0438\u0442\u0435\u0441\u044c \u0442\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<p><em>\u041f\u0440\u0438\u043c\u0435\u0440 \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438<\/em><\/p>\n<pre><code>subscription {   latestPlanet {     name     type   } } <\/code><\/pre>\n<p>\u0412\u043e \u0432\u0442\u043e\u0440\u043e\u0439 \u0443\u043a\u0430\u0436\u0438\u0442\u0435 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a <code>Authorization<\/code> \u043a\u0430\u043a \u0431\u044b\u043b\u043e \u043e\u043f\u0438\u0441\u0430\u043d\u043e \u0440\u0430\u043d\u0435\u0435 \u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u0435 \u043c\u0443\u0442\u0430\u0446\u0438\u044e:<\/p>\n<p><em>\u041f\u0440\u0438\u043c\u0435\u0440 \u043c\u0443\u0442\u0430\u0446\u0438\u0438<\/em><\/p>\n<pre><code>mutation {   createPlanet(     planet: {       name: \"Pluto\"       type: DWARF_PLANET       details: { meanRadius: \"1188\", mass: \"1.303e22\" }     }   ) {     id   } } <\/code><\/pre>\n<p>\u041f\u043e\u0434\u043f\u0438\u0441\u0430\u043d\u043d\u044b\u0439 \u043a\u043b\u0438\u0435\u043d\u0442 \u0431\u0443\u0434\u0435\u0442 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d \u043e \u0441\u043e\u0431\u044b\u0442\u0438\u0438:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/1d5\/53e\/2ae\/1d553e2ae4545f098e04b26a3d10b851.gif\" width=\"1214\" height=\"630\"><figcaption><\/figcaption><\/figure>\n<h3>CI\/CD<\/h3>\n<p>CI\/CD \u0441\u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u043d \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e GitHub Actions (<a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/blob\/master\/.github\/workflows\/workflow.yml\" rel=\"noopener noreferrer nofollow\">workflow<\/a>), \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442 \u0442\u0435\u0441\u0442\u044b \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439, \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u0442 \u0438\u0445 Docker \u043e\u0431\u0440\u0430\u0437\u044b \u0438 \u0440\u0430\u0437\u0432\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u0442 \u0438\u0445 \u043d\u0430 Google Cloud Platform.<\/p>\n<p>\u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043d\u0430 \u043e\u043f\u0438\u0441\u0430\u043d\u043d\u044b\u0435 API <a href=\"http:\/\/graphql-rust.romankudryashov.com\" rel=\"noopener noreferrer nofollow\">\u0437\u0434\u0435\u0441\u044c<\/a>.<\/p>\n<p><strong>\u0417\u0430\u043c\u0435\u0447\u0430\u043d\u0438\u0435:<\/strong> \u041d\u0430 &#171;\u043f\u0440\u043e\u0434\u0430\u043a\u0448\u043d&#187; \u0441\u0440\u0435\u0434\u0435 \u043f\u0430\u0440\u043e\u043b\u044c \u043e\u0442\u043b\u0438\u0447\u0430\u0435\u0442\u0441\u044f \u043e\u0442 \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u043e\u0433\u043e \u0440\u0430\u043d\u0435\u0435, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u0435\u0434\u043e\u0442\u0432\u0440\u0430\u0442\u0438\u0442\u044c \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0445.<\/p>\n<h2>\u0417\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435<\/h2>\n<p>\u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0435\u043b \u043a\u0430\u043a \u0440\u0435\u0448\u0430\u0442\u044c \u043d\u0430\u0438\u0431\u043e\u043b\u0435\u0435 \u0447\u0430\u0441\u0442\u044b\u0435 \u0432\u043e\u043f\u0440\u043e\u0441\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u0433\u0443\u0442 \u0432\u043e\u0437\u043d\u0438\u043a\u043d\u0443\u0442\u044c \u043f\u0440\u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0435 GraphQL API \u043d\u0430 Rust. \u0422\u0430\u043a\u0436\u0435 \u0431\u044b\u043b\u043e \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u043e \u043a\u0430\u043a \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0438\u0442\u044c API Rust GraphQL \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0435\u0434\u0438\u043d\u043e\u0433\u043e GraphQL \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430; \u0432 \u043f\u043e\u0434\u043e\u0431\u043d\u043e\u0439 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0435 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u044c \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0440\u0430\u0441\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0430 \u0441\u0440\u0435\u0434\u0438 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432. \u042d\u0442\u043e \u0434\u043e\u0441\u0442\u0438\u0433\u0430\u0435\u0442\u0441\u044f \u0437\u0430 \u0441\u0447\u0451\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f Apollo Server, Apollo Federation \u0438 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 Async-graphql. \u0418\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u043a\u043e\u0434 \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0435\u043d\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u043d\u0430 <a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\" rel=\"noopener noreferrer nofollow\">GitHub<\/a>. \u041d\u0435 \u0441\u0442\u0435\u0441\u043d\u044f\u0439\u0442\u0435\u0441\u044c \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u043c\u043d\u0435, \u0435\u0441\u043b\u0438 \u043d\u0430\u0439\u0434\u0451\u0442\u0435 \u043e\u0448\u0438\u0431\u043a\u0438 \u0432 \u0441\u0442\u0430\u0442\u044c\u0435 \u0438\u043b\u0438 \u0438\u0441\u0445\u043e\u0434\u043d\u043e\u043c \u043a\u043e\u0434\u0435. \u0411\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u044e \u0437\u0430 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435!<\/p>\n<h2>\u041f\u043e\u043b\u0435\u0437\u043d\u044b\u0435 \u0441\u0441\u044b\u043b\u043a\u0438<\/h2>\n<ul>\n<li>\n<p><a href=\"https:\/\/graphql.org\/\" rel=\"noopener noreferrer nofollow\">graphql.org<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/spec.graphql.org\/\" rel=\"noopener noreferrer nofollow\">spec.graphql.org<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/graphql.org\/learn\/best-practices\/\" rel=\"noopener noreferrer nofollow\">graphql.org\/learn\/best-practices<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/www.howtographql.com\/\" rel=\"noopener noreferrer nofollow\">howtographql.com<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/github.com\/async-graphql\/async-graphql\" rel=\"noopener noreferrer nofollow\">Async-graphql<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/async-graphql.github.io\/async-graphql\/en\/index.html\" rel=\"noopener noreferrer nofollow\">Async-graphql book<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/github.com\/chentsulin\/awesome-graphql\" rel=\"noopener noreferrer nofollow\">Awesome GraphQL<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/github.com\/APIs-guru\/graphql-apis\" rel=\"noopener noreferrer nofollow\">Public GraphQL APIs<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/github.com\/apollographql\/federation-demo\" rel=\"noopener noreferrer nofollow\">Apollo Federation demo<\/a><\/p>\n<\/li>\n<\/ul>\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\/post\/546208\/\"> https:\/\/habr.com\/ru\/post\/546208\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"\n<div class=\"post__text post__text_v2\" id=\"post-content-body\">\n<p>\u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u043f\u043e\u043a\u0430\u0436\u0443 \u043a\u0430\u043a \u0441\u043e\u0437\u0434\u0430\u0442\u044c GraphQL \u0441\u0435\u0440\u0432\u0435\u0440, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f Rust \u0438 \u0435\u0433\u043e \u044d\u043a\u043e\u0441\u0438\u0441\u0442\u0435\u043c\u0443; \u0431\u0443\u0434\u0443\u0442 \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043d\u044b \u043f\u0440\u0438\u043c\u0435\u0440\u044b \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043d\u0430\u0438\u0431\u043e\u043b\u0435\u0435 \u0447\u0430\u0441\u0442\u043e \u0432\u0441\u0442\u0440\u0435\u0447\u0430\u044e\u0449\u0438\u0445\u0441\u044f \u0437\u0430\u0434\u0430\u0447 \u043f\u0440\u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0435 GraphQL API. \u0412 \u0438\u0442\u043e\u0433\u0435 API \u0442\u0440\u0451\u0445 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u0431\u0443\u0434\u0443\u0442 \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0435\u043d\u044b \u0432 \u0435\u0434\u0438\u043d\u0443\u044e \u0442\u043e\u0447\u043a\u0443 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e Apollo Server \u0438 <a href=\"https:\/\/www.apollographql.com\/docs\/federation\/\" rel=\"noopener noreferrer nofollow\">Apollo Federation<\/a>. \u042d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u043a\u043b\u0438\u0435\u043d\u0442\u0430\u043c \u0437\u0430\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e \u0438\u0437 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u043e\u0432 \u0431\u0435\u0437 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u0437\u043d\u0430\u0442\u044c \u043a\u0430\u043a\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u0440\u0438\u0445\u043e\u0434\u044f\u0442 \u0438\u0437 \u043a\u0430\u043a\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0438\u0441\u0430.<\/p>\n<h2>\u0412\u0432\u0435\u0434\u0435\u043d\u0438\u0435<\/h2>\n<h3>\u041e\u0431\u0437\u043e\u0440<\/h3>\n<p>\u0421 \u0442\u043e\u0447\u043a\u0438 \u0437\u0440\u0435\u043d\u0438\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c\u044b\u0439 \u043f\u0440\u043e\u0435\u043a\u0442 \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u043f\u043e\u0445\u043e\u0436 \u043d\u0430 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0439 \u0432 \u043c\u043e\u0435\u0439 <a href=\"https:\/\/romankudryashov.com\/blog\/2020\/02\/how-to-graphql\/\" rel=\"noopener noreferrer nofollow\">\u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0439 \u0441\u0442\u0430\u0442\u044c\u0435<\/a>, \u043d\u043e \u0432 \u044d\u0442\u043e\u0442 \u0440\u0430\u0437 \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u0441\u0442\u044d\u043a\u0430 Rust. \u0410\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u043d\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a:<\/p>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<p>\u041a\u0430\u0436\u0434\u044b\u0439 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u044b \u043e\u0441\u0432\u0435\u0449\u0430\u0435\u0442 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0432\u043e\u043f\u0440\u043e\u0441\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u0433\u0443\u0442 \u0432\u043e\u0437\u043d\u0438\u043a\u043d\u0443\u0442\u044c \u043f\u0440\u0438 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 GraphQL API. \u0414\u043e\u043c\u0435\u043d\u043d\u0430\u044f \u043c\u043e\u0434\u0435\u043b\u044c \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u0434\u0430\u043d\u043d\u044b\u0435 \u043e \u043f\u043b\u0430\u043d\u0435\u0442\u0430\u0445 \u0421\u043e\u043b\u043d\u0435\u0447\u043d\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u0438 \u0438\u0445 \u0441\u043f\u0443\u0442\u043d\u0438\u043a\u0430\u0445. \u041f\u0440\u043e\u0435\u043a\u0442 \u0438\u043c\u0435\u0435\u0442 \u043c\u043d\u043e\u0433\u043e\u043c\u043e\u0434\u0443\u043b\u044c\u043d\u0443\u044e \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 (\u0438\u043b\u0438 \u043c\u043e\u043d\u043e\u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439) \u0438 \u0441\u043e\u0441\u0442\u043e\u0438\u0442 \u0438\u0437 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u043c\u043e\u0434\u0443\u043b\u0435\u0439:<\/p>\n<ul>\n<li>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/tree\/master\/planets-service\" rel=\"noopener noreferrer nofollow\">planets-service<\/a> (Rust)<\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/tree\/master\/satellites-service\" rel=\"noopener noreferrer nofollow\">satellites-service<\/a> (Rust)<\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/tree\/master\/auth-service\" rel=\"noopener noreferrer nofollow\">auth-service<\/a> (Rust)<\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/tree\/master\/apollo-server\" rel=\"noopener noreferrer nofollow\">apollo-server<\/a> (JS)<\/p>\n<\/li>\n<\/ul>\n<p>\u0421\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0442 \u0434\u0432\u0435 \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0445 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 \u0434\u043b\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 GraphQL \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u043d\u0430 Rust: <a href=\"https:\/\/github.com\/graphql-rust\/juniper\" rel=\"noopener noreferrer nofollow\">Juniper<\/a> \u0438 <a href=\"https:\/\/github.com\/async-graphql\/async-graphql\" rel=\"noopener noreferrer nofollow\">Async-graphql<\/a>, \u043d\u043e \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u044f\u044f \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 Apollo Federation, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043e\u043d\u0430 \u0431\u044b\u043b\u0430 \u0432\u044b\u0431\u0440\u0430\u043d\u0430 \u0434\u043b\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 (\u0435\u0441\u0442\u044c \u0442\u0430\u043a\u0436\u0435 \u043e\u0442\u043a\u0440\u044b\u0442\u044b\u0439 <a href=\"https:\/\/github.com\/graphql-rust\/juniper\/issues\/376\" rel=\"noopener noreferrer nofollow\">\u0437\u0430\u043f\u0440\u043e\u0441<\/a> \u043d\u0430 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0438 Federation \u0432 Juniper). \u041e\u0431\u0435 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u044e\u0442 <a href=\"https:\/\/blog.logrocket.com\/code-first-vs-schema-first-development-graphql\/\" rel=\"noopener noreferrer nofollow\">code-first<\/a> \u043f\u043e\u0434\u0445\u043e\u0434.<\/p>\n<p>\u041f\u043e\u043c\u0438\u043c\u043e \u044d\u0442\u043e\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u044b PostgreSQL \u2014 \u0434\u043b\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0441\u043b\u043e\u044f \u0434\u0430\u043d\u043d\u044b\u0445, <a href=\"https:\/\/jwt.io\/\" rel=\"noopener noreferrer nofollow\">JWT<\/a> \u2014 \u0434\u043b\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u0438 Kafka \u2014 \u0434\u043b\u044f \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e\u0433\u043e \u043e\u0431\u043c\u0435\u043d\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f\u043c\u0438.<\/p>\n<h3>\u0421\u0442\u044d\u043a \u0442\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u0439<\/h3>\n<p>\u0412 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439 \u0442\u0430\u0431\u043b\u0438\u0446\u0435 \u043f\u043e\u043a\u0430\u0437\u0430\u043d \u0441\u0442\u044d\u043a \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0445 \u0442\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u0439, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u0432 \u043f\u0440\u043e\u0435\u043a\u0442\u0435:<\/p>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th>\n<p>\u0422\u0438\u043f<\/p>\n<\/th>\n<th>\n<p>\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435<\/p>\n<\/th>\n<th>\n<p>\u0421\u0430\u0439\u0442<\/p>\n<\/th>\n<th>\n<p>GitHub<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td>\n<p>\u042f\u0437\u044b\u043a \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f<\/p>\n<\/td>\n<td>\n<p>Rust<\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/www.rust-lang.org\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/github.com\/rust-lang\/rust\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>GraphQL \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430<\/p>\n<\/td>\n<td>\n<p>Async-graphql<\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/async-graphql.github.io\/async-graphql\/en\/index.html\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/github.com\/async-graphql\/async-graphql\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>\u0415\u0434\u0438\u043d\u0430\u044f GraphQL \u0442\u043e\u0447\u043a\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0430<\/p>\n<\/td>\n<td>\n<p>Apollo Server<\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/www.apollographql.com\/docs\/apollo-server\/\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/github.com\/apollographql\/apollo-server\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>Web \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a<\/p>\n<\/td>\n<td>\n<p>actix-web<\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/actix.rs\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/github.com\/actix\/actix-web\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>\u0411\u0430\u0437\u0430 \u0434\u0430\u043d\u044b\u0445<\/p>\n<\/td>\n<td>\n<p>PostgreSQL<\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/www.postgresql.org\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/github.com\/postgres\/postgres\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>\u0411\u0440\u043e\u043a\u0435\u0440 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439<\/p>\n<\/td>\n<td>\n<p>Apache Kafka<\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/kafka.apache.org\/\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/github.com\/apache\/kafka\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>\u041e\u0440\u043a\u0435\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u043e\u0432<\/p>\n<\/td>\n<td>\n<p>Docker Compose<\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/docs.docker.com\/compose\/\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/github.com\/docker\/compose\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>\u0422\u0430\u043a\u0436\u0435 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0435 Rust \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438:<\/p>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th>\n<p>\u0422\u0438\u043f<\/p>\n<\/th>\n<th>\n<p>\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435<\/p>\n<\/th>\n<th>\n<p>\u0421\u0430\u0439\u0442<\/p>\n<\/th>\n<th>\n<p>GitHub<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td>\n<p>ORM<\/p>\n<\/td>\n<td>\n<p>Diesel<\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/diesel.rs\/\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/github.com\/diesel-rs\/diesel\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>Kafka \u043a\u043b\u0438\u0435\u043d\u0442<\/p>\n<\/td>\n<td>\n<p>rust-rdkafka<\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/crates.io\/crates\/rdkafka\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/github.com\/fede1024\/rust-rdkafka\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>\u0425\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043f\u0430\u0440\u043e\u043b\u0435\u0439<\/p>\n<\/td>\n<td>\n<p>argonautica<\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/crates.io\/crates\/argonautica\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/github.com\/bcmyers\/argonautica\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>JWT \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430<\/p>\n<\/td>\n<td>\n<p>jsonwebtoken<\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/crates.io\/crates\/jsonwebtoken\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/github.com\/Keats\/jsonwebtoken\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>\u0411\u0438\u0431\u0438\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f<\/p>\n<\/td>\n<td>\n<p>Testcontainers-rs<\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/crates.io\/crates\/testcontainers\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<td>\n<p><a href=\"https:\/\/github.com\/testcontainers\/testcontainers-rs\" rel=\"noopener noreferrer nofollow\">link<\/a><\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<h3>\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0435 \u041f\u041e<\/h3>\n<p>\u0427\u0442\u043e\u0431\u044b \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u043f\u0440\u043e\u0435\u043a\u0442 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e, \u0432\u0430\u043c \u043d\u0443\u0436\u0435\u043d \u0442\u043e\u043b\u044c\u043a\u043e Docker Compose. \u0412 \u043f\u0440\u043e\u0442\u0438\u0432\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0432\u0430\u043c \u043c\u043e\u0436\u0435\u0442 \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u044c\u0441\u044f \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435:<\/p>\n<ul>\n<li>\n<p><a href=\"https:\/\/www.rust-lang.org\/tools\/install\" rel=\"noopener noreferrer nofollow\">Rust<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/diesel.rs\/guides\/getting-started\/\" rel=\"noopener noreferrer nofollow\">Diesel CLI<\/a> (\u0434\u043b\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u0435 <code>cargo install diesel_cli --no-default-features --features postgres<\/code>)<\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/releases.llvm.org\/download.html\" rel=\"noopener noreferrer nofollow\">LLVM<\/a> (\u044d\u0442\u043e \u043d\u0443\u0436\u043d\u043e \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u043a\u0440\u044d\u0439\u0442\u0430 <code>argonautica<\/code>)<\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/cmake.org\/install\/\" rel=\"noopener noreferrer nofollow\">CMake<\/a> (\u044d\u0442\u043e \u043d\u0443\u0436\u043d\u043e \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u043a\u0440\u044d\u0439\u0442\u0430 <code>rust-rdkafka<\/code>)<\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/www.postgresql.org\/download\/\" rel=\"noopener noreferrer nofollow\">PostgreSQL<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/kafka.apache.org\/quickstart\" rel=\"noopener noreferrer nofollow\">Apache Kafka<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/www.npmjs.com\/get-npm\" rel=\"noopener noreferrer nofollow\">npm<\/a><\/p>\n<\/li>\n<\/ul>\n<h2>\u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f<\/h2>\n<p>\u0412 <code>Cargo.toml<\/code> \u0432 \u043a\u043e\u0440\u043d\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0443\u043a\u0430\u0437\u0430\u043d\u044b \u0442\u0440\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438 \u043e\u0434\u043d\u0430 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430:<\/p>\n<p><em>Root <\/em><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/blob\/master\/Cargo.toml\" rel=\"noopener noreferrer nofollow\"><em>Cargo.toml<\/em><\/a><\/p>\n<pre><code class=\"rust\">[workspace] members = [     \"auth-service\",     \"planets-service\",     \"satellites-service\",     \"common-utils\", ] <\/code><\/pre>\n<p>\u041d\u0430\u0447\u043d\u0451\u043c \u0441 <code>planets-service<\/code>.<\/p>\n<h3>\u0417\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438<\/h3>\n<p><code>Cargo.toml<\/code> \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a:<\/p>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/blob\/master\/planets-service\/Cargo.toml\" rel=\"noopener noreferrer nofollow\"><em>Cargo.toml<\/em><\/a><\/p>\n<pre><code>[package] name = \"planets-service\" version = \"0.1.0\" edition = \"2018\"  [dependencies] common-utils = { path = \"..\/common-utils\" } async-graphql = \"2.4.3\" async-graphql-actix-web = \"2.4.3\" actix-web = \"3.3.2\" actix-rt = \"1.1.1\" actix-web-actors = \"3.0.0\" futures = \"0.3.8\" async-trait = \"0.1.42\" bigdecimal = { version = \"0.1.2\", features = [\"serde\"] } serde = { version = \"1.0.118\", features = [\"derive\"] } serde_json = \"1.0.60\" diesel = { version = \"1.4.5\", features = [\"postgres\", \"r2d2\", \"numeric\"] } diesel_migrations = \"1.4.0\" dotenv = \"0.15.0\" strum = \"0.20.0\" strum_macros = \"0.20.1\" rdkafka = { version = \"0.24.0\", features = [\"cmake-build\"] } async-stream = \"0.3.0\" lazy_static = \"1.4.0\"  [dev-dependencies] jsonpath_lib = \"0.2.6\" testcontainers = \"0.9.1\" <\/code><\/pre>\n<p><code>async-graphql<\/code> \u2014 \u044d\u0442\u043e GraphQL \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430, <code>actix-web<\/code> \u2014 web \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a, \u0430 <code>async-graphql-actix-web<\/code> \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0432\u0430\u0435\u0442 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044e \u043c\u0435\u0436\u0434\u0443 \u043d\u0438\u043c\u0438.<\/p>\n<h3>\u041a\u043b\u044e\u0447\u0435\u0432\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438<\/h3>\n<p>\u041d\u0430\u0447\u043d\u0451\u043c \u0441 <code>main.rs<\/code>:<\/p>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/blob\/master\/planets-service\/src\/main.rs\" rel=\"noopener noreferrer nofollow\"><em>main.rs<\/em><\/a><\/p>\n<pre><code class=\"rust\">#[actix_rt::main] async fn main() -&gt; std::io::Result&lt;()&gt; {     dotenv().ok();     let pool = create_connection_pool();     run_migrations(&amp;pool);      let schema = create_schema_with_context(pool);      HttpServer::new(move || App::new()         .configure(configure_service)         .data(schema.clone())     )         .bind(\"0.0.0.0:8001\")?         .run()         .await } <\/code><\/pre>\n<p>\u0417\u0434\u0435\u0441\u044c \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u0435 \u0438 HTTP \u0441\u0435\u0440\u0432\u0435\u0440 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u0439, \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0451\u043d\u043d\u044b\u0445 \u0432 <code>lib.rs<\/code>:<\/p>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/blob\/master\/planets-service\/src\/lib.rs\" rel=\"noopener noreferrer nofollow\"><em>lib.rs<\/em><\/a><\/p>\n<pre><code class=\"rust\">pub fn configure_service(cfg: &amp;mut web::ServiceConfig) {     cfg         .service(web::resource(\"\/\")             .route(web::post().to(index))             .route(web::get().guard(guard::Header(\"upgrade\", \"websocket\")).to(index_ws))             .route(web::get().to(index_playground))         ); }  async fn index(schema: web::Data, http_req: HttpRequest, req: Request) -&gt; Response {     let mut query = req.into_inner();      let maybe_role = common_utils::get_role(http_req);     if let Some(role) = maybe_role {         query = query.data(role);     }      schema.execute(query).await.into() }  async fn index_ws(schema: web::Data, req: HttpRequest, payload: web::Payload) -&gt; Result {     WSSubscription::start(Schema::clone(&amp;*schema), &amp;req, payload) }  async fn index_playground() -&gt; HttpResponse {     HttpResponse::Ok()         .content_type(\"text\/html; charset=utf-8\")         .body(playground_source(GraphQLPlaygroundConfig::new(\"\/\").subscription_endpoint(\"\/\"))) }  pub fn create_schema_with_context(pool: PgPool) -&gt; Schema {     let arc_pool = Arc::new(pool);     let cloned_pool = Arc::clone(&amp;arc_pool);     let details_batch_loader = Loader::new(DetailsBatchLoader {         pool: cloned_pool     }).with_max_batch_size(10);      let kafka_consumer_counter = Mutex::new(0);      Schema::build(Query, Mutation, Subscription)         .data(arc_pool)         .data(details_batch_loader)         .data(kafka::create_producer())         .data(kafka_consumer_counter)         .finish() } <\/code><\/pre>\n<p>\u042d\u0442\u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0434\u0435\u043b\u0430\u044e\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435:<\/p>\n<ul>\n<li>\n<p><code>index<\/code> \u2014 \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 GraphQL <a href=\"https:\/\/graphql.org\/learn\/queries\/\" rel=\"noopener noreferrer nofollow\">\u0437\u0430\u043f\u0440\u043e\u0441\u044b (query) \u0438 \u043c\u0443\u0442\u0430\u0446\u0438\u0438<\/a><\/p>\n<\/li>\n<li>\n<p><code>index_ws<\/code> \u2014 \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 GraphQL <a href=\"https:\/\/www.apollographql.com\/docs\/react\/data\/subscriptions\/\" rel=\"noopener noreferrer nofollow\">\u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438<\/a><\/p>\n<\/li>\n<li>\n<p><code>index_playground<\/code> \u2014 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 Playground GraphQL IDE<\/p>\n<\/li>\n<li>\n<p><code>create_schema_with_context<\/code> \u2014 \u0441\u043e\u0437\u0434\u0430\u0451\u0442 GraphQL \u0441\u0445\u0435\u043c\u0443 \u0441 \u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u044b\u043c \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u043e\u043c \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u043c \u0432 \u0440\u0430\u043d\u0442\u0430\u0439\u043c\u0435, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043f\u0443\u043b \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0439 \u0441 \u0411\u0414<\/p>\n<\/li>\n<\/ul>\n<h3>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 GraphQL \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u0438 \u0442\u0438\u043f\u0430<\/h3>\n<p>\u0420\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043a\u0430\u043a \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u0437\u0430\u043f\u0440\u043e\u0441:<\/p>\n<p><em>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u0430<\/em><\/p>\n<pre><code class=\"rust\">#[Object] impl Query {     async fn get_planets(&amp;self, ctx: &amp;Context&lt;'_&gt;) -&gt; Vec {         repository::get_all(&amp;get_conn_from_ctx(ctx)).expect(\"Can't get planets\")             .iter()             .map(|p| { Planet::from(p) })             .collect()     }      async fn get_planet(&amp;self, ctx: &amp;Context&lt;'_&gt;, id: ID) -&gt; Option {         find_planet_by_id_internal(ctx, id)     }      #[graphql(entity)]     async fn find_planet_by_id(&amp;self, ctx: &amp;Context&lt;'_&gt;, id: ID) -&gt; Option {         find_planet_by_id_internal(ctx, id)     } }  fn find_planet_by_id_internal(ctx: &amp;Context&lt;'_&gt;, id: ID) -&gt; Option {     let id = id.to_string().parse::().expect(\"Can't get id from String\");     repository::get(id, &amp;get_conn_from_ctx(ctx)).ok()         .map(|p| { Planet::from(&amp;p) }) } <\/code><\/pre>\n<p>\u042d\u0442\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u043f\u043e\u043b\u0443\u0447\u0430\u044e\u0442 \u0434\u0430\u043d\u043d\u044b\u0435 \u0438\u0437 \u0411\u0414 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u0441\u043b\u043e\u0439 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u044f. \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0435 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0438 \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u0432 GraphQL DTO (\u044d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0441\u043e\u0431\u043b\u044e\u0441\u0442\u0438 \u043f\u0440\u0438\u043d\u0446\u0438\u043f \u0435\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0439 \u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0441\u0442\u0438 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0439 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b). \u0417\u0430\u043f\u0440\u043e\u0441\u044b <code>get_planets<\/code> \u0438 <code>get_planet<\/code> \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u044b \u0438\u0437 \u043b\u044e\u0431\u043e\u0439 GraphQL IDE \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 \u0442\u0430\u043a:<\/p>\n<p><em>\u041f\u0440\u0438\u043c\u0435\u0440 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0437\u0430\u043f\u0440\u043e\u0441\u0430<\/em><\/p>\n<pre><code>{   getPlanets {     name     type   } } <\/code><\/pre>\n<p>\u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 <code>Planet<\/code> \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0430 \u0442\u0430\u043a:<\/p>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/blob\/master\/planets-service\/src\/graphql.rs\" rel=\"noopener noreferrer nofollow\"><em>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 GraphQL \u0442\u0438\u043f\u0430<\/em><\/a><\/p>\n<pre><code class=\"rust\">#[derive(Serialize, Deserialize)] struct Planet {     id: ID,     name: String,     planet_type: PlanetType, }  #[Object] impl Planet {     async fn id(&amp;self) -&gt; &amp;ID {         &amp;self.id     }      async fn name(&amp;self) -&gt; &amp;String {         &amp;self.name     }      \/\/\/ From an astronomical point of view     #[graphql(name = \"type\")]     async fn planet_type(&amp;self) -&gt; &amp;PlanetType {         &amp;self.planet_type     }      #[graphql(deprecation = \"Now it is not in doubt. Do not use this field\")]     async fn is_rotating_around_sun(&amp;self) -&gt; bool {         true     }      async fn details(&amp;self, ctx: &amp;Context&lt;'_&gt;) -&gt; Details {         let loader = ctx.data::&gt;().expect(\"Can't get loader\");         let planet_id = self.id.to_string().parse::().expect(\"Can't convert id\");         loader.load(planet_id).await     } } <\/code><\/pre>\n<p>\u0412 <code>impl<\/code> \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442\u0441\u044f \u0440\u0435\u0437\u043e\u043b\u0432\u0435\u0440 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043f\u043e\u043b\u044f. \u0422\u0430\u043a\u0436\u0435 \u0434\u043b\u044f \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043f\u043e\u043b\u0435\u0439 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u044b \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 (\u0432 \u0432\u0438\u0434\u0435 Rust \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f) \u0438 deprecation reason. \u042d\u0442\u043e \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u043e \u0432 GraphQL IDE.<\/p>\n<h3>\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 N+1<\/h3>\n<p>\u0412 \u0441\u043b\u0443\u0447\u0430\u0435 <em>\u043d\u0430\u0438\u0432\u043d\u043e\u0439<\/em> \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 <code>Planet.details<\/code> \u0432\u044b\u0448\u0435 \u0432\u043e\u0437\u043d\u0438\u043a\u043b\u0430 \u0431\u044b \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 N+1, \u0442\u043e \u0435\u0441\u0442\u044c, \u043f\u0440\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0438 \u0442\u0430\u043a\u043e\u0433\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u0430:<\/p>\n<p><em>\u041f\u0440\u0438\u043c\u0435\u0440 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0433\u043e \u0440\u0435\u0441\u0443\u0440\u0441\u043e\u0451\u043c\u043a\u043e\u0433\u043e GraphQL \u0437\u0430\u043f\u0440\u043e\u0441\u0430<\/em><\/p>\n<pre><code>{   getPlanets {     name     details {       meanRadius     }   } } <\/code><\/pre>\n<p>\u0434\u043b\u044f \u043f\u043e\u043b\u044f <code>details<\/code> \u043a\u0430\u0436\u0434\u043e\u0439 \u0438\u0437 \u043f\u043b\u0430\u043d\u0435\u0442 \u0431\u044b\u043b \u0431\u044b \u0441\u0434\u0435\u043b\u0430\u043d \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 SQL \u0437\u0430\u043f\u0440\u043e\u0441, \u0442. \u043a. <code>Details<\/code> \u2014 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0430\u044f \u043e\u0442 <code>Planet<\/code> \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u044c \u0438 \u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f \u0432 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0439 \u0442\u0430\u0431\u043b\u0438\u0446\u0435.<\/p>\n<p>\u041d\u043e \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <a href=\"https:\/\/github.com\/graphql\/dataloader\" rel=\"noopener noreferrer nofollow\">DataLoader<\/a>, \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u0432 Async-graphql, \u0440\u0435\u0437\u043e\u043b\u0432\u0435\u0440 <code>details<\/code> \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0451\u043d \u0442\u0430\u043a:<\/p>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/blob\/master\/planets-service\/src\/graphql.rs\" rel=\"noopener noreferrer nofollow\"><em>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u0440\u0435\u0437\u043e\u043b\u0432\u0435\u0440\u0430<\/em><\/a><\/p>\n<pre><code class=\"rust\">async fn details(&amp;self, ctx: &amp;Context&lt;'_&gt;) -&gt; Result {     let data_loader = ctx.data::&gt;().expect(\"Can't get data loader\");     let planet_id = self.id.to_string().parse::().expect(\"Can't convert id\");     let details = data_loader.load_one(planet_id).await?;     details.ok_or_else(|| \"Not found\".into()) } <\/code><\/pre>\n<p><code>data_loader<\/code> \u2014 \u044d\u0442\u043e \u043e\u0431\u044a\u0435\u043a\u0442 \u0432 \u043a\u043e\u043d\u0442\u0435\u043a\u0442\u0441\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0451\u043d\u043d\u044b\u0439 \u0442\u0430\u043a:<\/p>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/blob\/master\/planets-service\/src\/lib.rs\" rel=\"noopener noreferrer nofollow\"><em>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 DataLoader&#8217;\u0430<\/em><\/a><\/p>\n<pre><code class=\"rust\">let details_data_loader = DataLoader::new(DetailsLoader {     pool: cloned_pool }).max_batch_size(10); <\/code><\/pre>\n<p><code>DetailsLoader<\/code> \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<p><a href=\"https:\/\/github.com\/rkudryashov\/graphql-rust-demo\/blob\/master\/planets-service\/src\/graphql.rs\" rel=\"noopener noreferrer nofollow\"><em>DetailsLoader definition<\/em><\/a><\/p>\n<pre><code class=\"rust\">pub struct DetailsLoader {     pub pool: Arc }  #[async_trait::async_trait] impl Loader for DetailsLoader {     type Value = Details;     type Error = Error;      async fn load(&amp;self, keys: &amp;[i32]) -&gt; Result, Self::Error&gt; {         let conn = self.pool.get().expect(\"Can't get DB connection\");         let details = repository::get_details(keys, &amp;conn).expect(\"Can't get planets' details\");          Ok(details.iter()             .map(|details_entity| (details_entity.planet_id, Details::from(details_entity)))             .collect::&gt;())     } } <\/code><\/pre>\n<p>\u0422\u0430\u043a\u043e\u0439 \u043f\u043e\u0434\u0445\u043e\u0434 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043f\u0440\u0435\u0434\u043e\u0442\u0432\u0440\u0430\u0442\u0438\u0442\u044c \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0443 N+1, \u0442. \u043a. \u043a\u0430\u0436\u0434\u044b\u0439 \u0432\u044b\u0437\u043e\u0432 <code>DetailsLoader.load<\/code> \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u0438\u043d SQL \u0437\u0430\u043f\u0440\u043e\u0441, \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u044e\u0449\u0438\u0439 \u043f\u0430\u0447\u043a\u0443 <code>DetailsEntity<\/code>.<\/p>\n<h3>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430<\/h3>\n<p>GraphQL<\/p>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-319251","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/319251","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=319251"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/319251\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=319251"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=319251"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=319251"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}