[{"data":1,"prerenderedAt":1292},["ShallowReactive",2],{"content:\u002F07-rpc-grpc\u002F01-grpc-overview":3},{"title":4,"description":5,"path":6,"body":7},"gRPC и RPC: когда REST уже не хватает","REST хорошо подходит для публичных HTTP API: ресурсы, URL, методы, статус-коды, JSON, понятная отладка через браузер и curl. Но в микросервисах часто появляется другая боль: десятки внутренних сервисов должны быстро и строго общаться друг с другом, клиенты на разных языках должны получать одинаковый контракт, а схема запроса не должна жить только в README.","\u002F07-rpc-grpc\u002F01-grpc-overview",{"type":8,"value":9,"toc":1276},"minimark",[10,14,17,29,59,62,121,124,127,132,135,143,146,170,273,276,278,282,285,291,295,298,314,320,327,330,344,347,350,370,373,377,380,428,439,441,445,448,517,520,541,543,547,550,570,573,590,593,595,599,602,608,633,639,641,645,737,740,775,777,781,826,828,832,864,866,870,903,1095,1272],[11,12,4],"h1",{"id":13},"grpc-и-rpc-когда-rest-уже-не-хватает",[15,16,5],"p",{},[15,18,19,20,24,25,28],{},"gRPC решает эту задачу через ",[21,22,23],"strong",{},"RPC"," и ",[21,26,27],{},"контракт-first подход",". Вместо \"ресурс + HTTP-метод\" мы описываем сервис и его методы:",[30,31,36],"pre",{"className":32,"code":33,"language":34,"meta":35,"style":35},"language-proto shiki shiki-themes github-dark","service CourseService {\n  rpc GetLesson(GetLessonRequest) returns (GetLessonResponse);\n}\n","proto","",[37,38,39,47,53],"code",{"__ignoreMap":35},[40,41,44],"span",{"class":42,"line":43},"line",1,[40,45,46],{},"service CourseService {\n",[40,48,50],{"class":42,"line":49},2,[40,51,52],{},"  rpc GetLesson(GetLessonRequest) returns (GetLessonResponse);\n",[40,54,56],{"class":42,"line":55},3,[40,57,58],{},"}\n",[15,60,61],{},"Для Go-разработчика это похоже на вызов обычного метода, только этот метод живёт на другом процессе или сервере:",[30,63,67],{"className":64,"code":65,"language":66,"meta":35,"style":35},"language-go shiki shiki-themes github-dark","lesson, err := client.GetLesson(ctx, &coursev1.GetLessonRequest{\n    Slug: \"grpc-overview\",\n})\n","go",[37,68,69,104,116],{"__ignoreMap":35},[40,70,71,75,79,82,86,89,92,95,98,101],{"class":42,"line":43},[40,72,74],{"class":73},"s95oV","lesson, err ",[40,76,78],{"class":77},"snl16",":=",[40,80,81],{"class":73}," client.",[40,83,85],{"class":84},"svObZ","GetLesson",[40,87,88],{"class":73},"(ctx, ",[40,90,91],{"class":77},"&",[40,93,94],{"class":84},"coursev1",[40,96,97],{"class":73},".",[40,99,100],{"class":84},"GetLessonRequest",[40,102,103],{"class":73},"{\n",[40,105,106,109,113],{"class":42,"line":49},[40,107,108],{"class":73},"    Slug: ",[40,110,112],{"class":111},"sU2Wk","\"grpc-overview\"",[40,114,115],{"class":73},",\n",[40,117,118],{"class":42,"line":55},[40,119,120],{"class":73},"})\n",[15,122,123],{},"Под капотом всё равно сеть, таймауты, ошибки, сериализация и контракты. Просто gRPC прячет транспортную механику за сгенерированным клиентом и серверным интерфейсом.",[125,126],"hr",{},[128,129,131],"h2",{"id":130},"rest-и-grpc-разные-модели","REST и gRPC - разные модели",[15,133,134],{},"REST моделирует систему как набор ресурсов:",[30,136,141],{"className":137,"code":139,"language":140,"meta":35},[138],"language-text","GET  \u002Flessons\u002Fgrpc-overview\nPOST \u002Fsubmissions\n","text",[37,142,139],{"__ignoreMap":35},[15,144,145],{},"gRPC моделирует систему как набор сервисов и методов:",[30,147,149],{"className":32,"code":148,"language":34,"meta":35,"style":35},"service LessonService {\n  rpc GetLesson(GetLessonRequest) returns (GetLessonResponse);\n  rpc SubmitSolution(SubmitSolutionRequest) returns (SubmitSolutionResponse);\n}\n",[37,150,151,156,160,165],{"__ignoreMap":35},[40,152,153],{"class":42,"line":43},[40,154,155],{},"service LessonService {\n",[40,157,158],{"class":42,"line":49},[40,159,52],{},[40,161,162],{"class":42,"line":55},[40,163,164],{},"  rpc SubmitSolution(SubmitSolutionRequest) returns (SubmitSolutionResponse);\n",[40,166,168],{"class":42,"line":167},4,[40,169,58],{},[171,172,173,189],"table",{},[174,175,176],"thead",{},[177,178,179,183,186],"tr",{},[180,181,182],"th",{},"Вопрос",[180,184,185],{},"REST",[180,187,188],{},"gRPC",[190,191,192,204,218,229,240,251,262],"tbody",{},[177,193,194,198,201],{},[195,196,197],"td",{},"Основная модель",[195,199,200],{},"Ресурсы",[195,202,203],{},"Сервисы и методы",[177,205,206,209,212],{},[195,207,208],{},"Контракт",[195,210,211],{},"OpenAPI\u002Fдокументация, часто отдельно",[195,213,214,217],{},[37,215,216],{},".proto"," как источник истины",[177,219,220,223,226],{},[195,221,222],{},"Данные",[195,224,225],{},"Обычно JSON",[195,227,228],{},"Обычно Protocol Buffers",[177,230,231,234,237],{},[195,232,233],{},"Транспорт",[195,235,236],{},"HTTP\u002F1.1 или HTTP\u002F2",[195,238,239],{},"HTTP\u002F2",[177,241,242,245,248],{},[195,243,244],{},"Клиенты",[195,246,247],{},"Пишутся руками или генерируются",[195,249,250],{},"Обычно генерируются",[177,252,253,256,259],{},[195,254,255],{},"Удобство для браузера",[195,257,258],{},"Высокое",[195,260,261],{},"Низкое без прокси\u002FgRPC-Web",[177,263,264,267,270],{},[195,265,266],{},"Service-to-service",[195,268,269],{},"Можно, но много ручной работы",[195,271,272],{},"Основной сценарий",[15,274,275],{},"Важно: gRPC не \"лучше REST\" вообще. Это другой инструмент. REST проще для публичного API, интеграций и браузерного мира. gRPC сильнее там, где нужен строгий контракт, быстрый бинарный формат, много внутренних клиентов и streaming.",[125,277],{},[128,279,281],{"id":280},"что-происходит-под-капотом","Что происходит под капотом",[15,283,284],{},"gRPC использует несколько слоёв:",[30,286,289],{"className":287,"code":288,"language":140,"meta":35},[138],"Ваш Go-код\n   ↓\nСгенерированный gRPC client\u002Fserver\n   ↓\nProtocol Buffers messages\n   ↓\ngRPC status, metadata, deadlines\n   ↓\nHTTP\u002F2 streams\n   ↓\nTCP\u002FTLS\n",[37,290,288],{"__ignoreMap":35},[292,293,239],"h3",{"id":294},"http2",[15,296,297],{},"gRPC работает поверх HTTP\u002F2. Это даёт:",[299,300,301,305,308,311],"ul",{},[302,303,304],"li",{},"multiplexing: несколько RPC могут идти по одному соединению;",[302,306,307],{},"headers\u002Ftrailers: metadata и финальный gRPC status передаются отдельно от body;",[302,309,310],{},"flow control: транспорт умеет тормозить отправителя, если получатель не успевает читать;",[302,312,313],{},"streaming: один RPC может передавать много сообщений в одну или обе стороны.",[15,315,316,317,319],{},"Обычно вы не работаете с HTTP\u002F2 напрямую. Вы пишете ",[37,318,216],{},", генерируете код и реализуете Go-интерфейс.",[292,321,323,324],{"id":322},"почему-это-не-проверяют-обычным-curl","Почему это не проверяют обычным ",[37,325,326],{},"curl",[15,328,329],{},"REST endpoint часто можно быстро потрогать так:",[30,331,335],{"className":332,"code":333,"language":334,"meta":35,"style":35},"language-bash shiki shiki-themes github-dark","curl http:\u002F\u002Flocalhost:8080\u002Flessons\u002Fgrpc-overview\n","bash",[37,336,337],{"__ignoreMap":35},[40,338,339,341],{"class":42,"line":43},[40,340,326],{"class":84},[40,342,343],{"class":111}," http:\u002F\u002Flocalhost:8080\u002Flessons\u002Fgrpc-overview\n",[15,345,346],{},"С gRPC так обычно не получится. У gRPC другой wire protocol: HTTP\u002F2, protobuf body, специальные headers\u002Ftrailers и gRPC status. Обычный HTTP-запрос не знает, какой protobuf message сериализовать и как прочитать gRPC trailers.",[15,348,349],{},"Для локальной проверки используют другие варианты:",[299,351,352,355,358,367],{},[302,353,354],{},"маленький Go-клиент на generated stub;",[302,356,357],{},"интеграционный тест, который поднимает gRPC server на локальном listener;",[302,359,360,363,364,366],{},[37,361,362],{},"grpcurl"," или GUI-клиент вроде Kreya\u002FBloomRPC, если включена reflection или передан ",[37,365,216],{},";",[302,368,369],{},"server reflection в dev\u002Fstaging, чтобы инструмент мог сам узнать список сервисов.",[15,371,372],{},"Главная мысль: gRPC debug-инструменты должны понимать контракт. Если хочется \"просто открыть в браузере\", значит для этого сценария лучше подходит REST, gateway или gRPC-Web, а не чистый service-to-service gRPC.",[292,374,376],{"id":375},"protocol-buffers","Protocol Buffers",[15,378,379],{},"Protocol Buffers, или protobuf, описывает структуру сообщений:",[30,381,383],{"className":32,"code":382,"language":34,"meta":35,"style":35},"message GetLessonRequest {\n  string slug = 1;\n}\n\nmessage GetLessonResponse {\n  string title = 1;\n  string body_markdown = 2;\n}\n",[37,384,385,390,395,399,405,411,417,423],{"__ignoreMap":35},[40,386,387],{"class":42,"line":43},[40,388,389],{},"message GetLessonRequest {\n",[40,391,392],{"class":42,"line":49},[40,393,394],{},"  string slug = 1;\n",[40,396,397],{"class":42,"line":55},[40,398,58],{},[40,400,401],{"class":42,"line":167},[40,402,404],{"emptyLinePlaceholder":403},true,"\n",[40,406,408],{"class":42,"line":407},5,[40,409,410],{},"message GetLessonResponse {\n",[40,412,414],{"class":42,"line":413},6,[40,415,416],{},"  string title = 1;\n",[40,418,420],{"class":42,"line":419},7,[40,421,422],{},"  string body_markdown = 2;\n",[40,424,426],{"class":42,"line":425},8,[40,427,58],{},[15,429,430,431,434,435,438],{},"Числа ",[37,432,433],{},"1",", ",[37,436,437],{},"2"," - это не порядок для красоты, а часть wire-формата. Клиент и сервер используют номера полей, чтобы понимать бинарные данные. Поэтому номера существующих полей нельзя переиспользовать для другого смысла.",[125,440],{},[128,442,444],{"id":443},"четыре-типа-rpc","Четыре типа RPC",[15,446,447],{},"gRPC поддерживает не только один запрос - один ответ.",[30,449,451],{"className":32,"code":450,"language":34,"meta":35,"style":35},"service LessonService {\n  \u002F\u002F Unary: один request, один response.\n  rpc GetLesson(GetLessonRequest) returns (GetLessonResponse);\n\n  \u002F\u002F Server-side streaming: один request, поток response.\n  rpc ListLessons(ListLessonsRequest) returns (stream Lesson);\n\n  \u002F\u002F Client-side streaming: поток request, один response.\n  rpc UploadProgress(stream ProgressEvent) returns (ProgressSummary);\n\n  \u002F\u002F Bidirectional streaming: поток request и поток response.\n  rpc MentorChat(stream ChatMessage) returns (stream ChatMessage);\n}\n",[37,452,453,457,462,466,470,475,480,484,489,495,500,506,512],{"__ignoreMap":35},[40,454,455],{"class":42,"line":43},[40,456,155],{},[40,458,459],{"class":42,"line":49},[40,460,461],{},"  \u002F\u002F Unary: один request, один response.\n",[40,463,464],{"class":42,"line":55},[40,465,52],{},[40,467,468],{"class":42,"line":167},[40,469,404],{"emptyLinePlaceholder":403},[40,471,472],{"class":42,"line":407},[40,473,474],{},"  \u002F\u002F Server-side streaming: один request, поток response.\n",[40,476,477],{"class":42,"line":413},[40,478,479],{},"  rpc ListLessons(ListLessonsRequest) returns (stream Lesson);\n",[40,481,482],{"class":42,"line":419},[40,483,404],{"emptyLinePlaceholder":403},[40,485,486],{"class":42,"line":425},[40,487,488],{},"  \u002F\u002F Client-side streaming: поток request, один response.\n",[40,490,492],{"class":42,"line":491},9,[40,493,494],{},"  rpc UploadProgress(stream ProgressEvent) returns (ProgressSummary);\n",[40,496,498],{"class":42,"line":497},10,[40,499,404],{"emptyLinePlaceholder":403},[40,501,503],{"class":42,"line":502},11,[40,504,505],{},"  \u002F\u002F Bidirectional streaming: поток request и поток response.\n",[40,507,509],{"class":42,"line":508},12,[40,510,511],{},"  rpc MentorChat(stream ChatMessage) returns (stream ChatMessage);\n",[40,513,515],{"class":42,"line":514},13,[40,516,58],{},[15,518,519],{},"В этом модуле мы пойдём от простого к сложному:",[521,522,523,526,529,532,535,538],"ol",{},[302,524,525],{},"Сначала разберём protobuf-схему.",[302,527,528],{},"Потом сгенерируем Go-код.",[302,530,531],{},"Потом напишем unary server\u002Fclient.",[302,533,534],{},"Потом разберём context, deadlines, metadata и ошибки.",[302,536,537],{},"Потом встроим gRPC в Clean Architecture.",[302,539,540],{},"В конце разберём streaming и production-практики.",[125,542],{},[128,544,546],{"id":545},"когда-выбирать-grpc","Когда выбирать gRPC",[15,548,549],{},"Выбирайте gRPC, если:",[299,551,552,555,558,561,564,567],{},[302,553,554],{},"сервисы общаются внутри backend-системы;",[302,556,557],{},"нужен строгий контракт и генерация клиентов;",[302,559,560],{},"есть клиенты на нескольких языках;",[302,562,563],{},"важны deadlines, cancellation и единая модель ошибок;",[302,565,566],{},"нужны server\u002Fclient\u002Fbidirectional streams;",[302,568,569],{},"payload большой или запросов очень много.",[15,571,572],{},"REST обычно проще, если:",[299,574,575,578,581,584,587],{},[302,576,577],{},"API публичное и его будут вызывать внешние пользователи;",[302,579,580],{},"нужен простой доступ из браузера;",[302,582,583],{},"важна читаемость JSON руками;",[302,585,586],{},"команда маленькая и контракт пока быстро меняется;",[302,588,589],{},"нет выигрыша от codegen и HTTP\u002F2 streaming.",[15,591,592],{},"Практический подход: публичный edge API часто оставляют REST\u002FHTTP, а внутреннее service-to-service взаимодействие делают на gRPC.",[125,594],{},[128,596,598],{"id":597},"как-выглядит-маленький-grpc-проект","Как выглядит маленький gRPC-проект",[15,600,601],{},"В учебном проекте удобно держать контракт отдельно от реализации:",[30,603,606],{"className":604,"code":605,"language":140,"meta":35},[138],"grpc-demo\u002F\n  proto\u002F\n    calculator\u002Fv1\u002Fcalculator.proto\n  gen\u002F\n    calculator\u002Fv1\u002F\n      calculator.pb.go\n      calculator_grpc.pb.go\n  cmd\u002F\n    calculator-server\u002Fmain.go\n    calculator-client\u002Fmain.go\n  internal\u002F\n    calculator\u002F\n      server.go\n      service.go\n      mapper.go\n",[37,607,605],{"__ignoreMap":35},[15,609,610,613,614,617,618,621,622,625,626,628,629,632],{},[37,611,612],{},"proto\u002F"," - источник истины. ",[37,615,616],{},"gen\u002F"," - результат codegen, его не редактируют руками. ",[37,619,620],{},"cmd\u002Fcalculator-server"," собирает приложение-сервер. ",[37,623,624],{},"cmd\u002Fcalculator-client"," полезен как локальная проверка без браузера и ",[37,627,326],{},". ",[37,630,631],{},"internal\u002Fcalculator"," содержит вашу реализацию: бизнес-методы, маппинг и glue-код вокруг generated interface.",[15,634,635,636,638],{},"В реальных сервисах generated code часто лежит в отдельном module\u002Frepository с контрактами. Для обучения проще держать всё рядом, чтобы видеть весь путь от ",[37,637,216],{}," до работающего вызова.",[125,640],{},[128,642,644],{"id":643},"мини-пример-контракта","Мини-пример контракта",[30,646,648],{"className":32,"code":647,"language":34,"meta":35,"style":35},"syntax = \"proto3\";\n\npackage course.v1;\n\noption go_package = \"example.com\u002Fmentor\u002Fgen\u002Fcourse\u002Fv1;coursev1\";\n\nservice CourseService {\n  rpc GetLesson(GetLessonRequest) returns (GetLessonResponse);\n}\n\nmessage GetLessonRequest {\n  string slug = 1;\n}\n\nmessage GetLessonResponse {\n  string slug = 1;\n  string title = 2;\n  string body_markdown = 3;\n}\n",[37,649,650,655,659,664,668,673,677,681,685,689,693,697,701,705,710,715,720,726,732],{"__ignoreMap":35},[40,651,652],{"class":42,"line":43},[40,653,654],{},"syntax = \"proto3\";\n",[40,656,657],{"class":42,"line":49},[40,658,404],{"emptyLinePlaceholder":403},[40,660,661],{"class":42,"line":55},[40,662,663],{},"package course.v1;\n",[40,665,666],{"class":42,"line":167},[40,667,404],{"emptyLinePlaceholder":403},[40,669,670],{"class":42,"line":407},[40,671,672],{},"option go_package = \"example.com\u002Fmentor\u002Fgen\u002Fcourse\u002Fv1;coursev1\";\n",[40,674,675],{"class":42,"line":413},[40,676,404],{"emptyLinePlaceholder":403},[40,678,679],{"class":42,"line":419},[40,680,46],{},[40,682,683],{"class":42,"line":425},[40,684,52],{},[40,686,687],{"class":42,"line":491},[40,688,58],{},[40,690,691],{"class":42,"line":497},[40,692,404],{"emptyLinePlaceholder":403},[40,694,695],{"class":42,"line":502},[40,696,389],{},[40,698,699],{"class":42,"line":508},[40,700,394],{},[40,702,703],{"class":42,"line":514},[40,704,58],{},[40,706,708],{"class":42,"line":707},14,[40,709,404],{"emptyLinePlaceholder":403},[40,711,713],{"class":42,"line":712},15,[40,714,410],{},[40,716,718],{"class":42,"line":717},16,[40,719,394],{},[40,721,723],{"class":42,"line":722},17,[40,724,725],{},"  string title = 2;\n",[40,727,729],{"class":42,"line":728},18,[40,730,731],{},"  string body_markdown = 3;\n",[40,733,735],{"class":42,"line":734},19,[40,736,58],{},[15,738,739],{},"Что здесь важно:",[299,741,742,748,754,760,766,772],{},[302,743,744,747],{},[37,745,746],{},"syntax = \"proto3\""," выбирает современную версию языка protobuf;",[302,749,750,753],{},[37,751,752],{},"package course.v1"," задаёт пространство имён на уровне protobuf;",[302,755,756,759],{},[37,757,758],{},"option go_package"," говорит генератору, какой Go package создать;",[302,761,762,765],{},[37,763,764],{},"service"," описывает API;",[302,767,768,771],{},[37,769,770],{},"message"," описывает request\u002Fresponse DTO;",[302,773,774],{},"номера полей фиксируют wire-contract.",[125,776],{},[128,778,780],{"id":779},"вопросы-на-собеседовании","Вопросы на собеседовании",[299,782,783,786,789,792,795,798,801,807,813],{},[302,784,785],{},"Чем RPC отличается от REST?",[302,787,788],{},"Почему gRPC обычно используют для service-to-service?",[302,790,791],{},"Зачем gRPC нужен HTTP\u002F2?",[302,793,794],{},"Почему gRPC не всегда удобен как публичный браузерный API?",[302,796,797],{},"Что такое unary RPC?",[302,799,800],{},"Какие типы streaming есть в gRPC?",[302,802,803,804,806],{},"Почему ",[37,805,216],{}," лучше держать как источник истины, а не как \"ещё одну документацию\"?",[302,808,809,810,812],{},"Почему чистый gRPC неудобно проверять обычным ",[37,811,326],{},"?",[302,814,815,816,434,818,434,820,24,823,812],{},"Что должно лежать в ",[37,817,612],{},[37,819,616],{},[37,821,822],{},"cmd\u002F",[37,824,825],{},"internal\u002F",[125,827],{},[128,829,831],{"id":830},"источники","Источники",[299,833,834,843,850,857],{},[302,835,836],{},[837,838,842],"a",{"href":839,"rel":840},"https:\u002F\u002Fgrpc.io\u002Fdocs\u002Fwhat-is-grpc\u002Fintroduction\u002F",[841],"nofollow","gRPC Introduction",[302,844,845],{},[837,846,849],{"href":847,"rel":848},"https:\u002F\u002Fgrpc.io\u002Fdocs\u002Fwhat-is-grpc\u002Fcore-concepts\u002F",[841],"gRPC Core Concepts",[302,851,852],{},[837,853,856],{"href":854,"rel":855},"https:\u002F\u002Fgrpc.io\u002Fdocs\u002Flanguages\u002Fgo\u002Fbasics\u002F",[841],"gRPC Go Basics",[302,858,859],{},[837,860,863],{"href":861,"rel":862},"https:\u002F\u002Fprotobuf.dev\u002Freference\u002Fprotobuf\u002Fproto3-spec\u002F",[841],"Protocol Buffers Proto3 Specification",[125,865],{},[128,867,869],{"id":868},"практика","Практика",[871,872,875,878,895],"quiz",{"answer":437,"id":873,"xp":874},"rpc-overview-q1","10",[15,876,877],{},"Когда gRPC обычно выигрывает у REST внутри микросервисной системы?",[879,880,881],"template",{"v-slot:options":35},[299,882,883,886,889,892],{},[302,884,885],{},"Когда нужен простой публичный API для браузера без прокси",[302,887,888],{},"Когда важен строгий контракт, generated clients и service-to-service взаимодействие",[302,890,891],{},"Когда API должен открываться обычным HTML form",[302,893,894],{},"Когда нужно полностью отказаться от deadlines и status codes",[879,896,897],{"v-slot:explanation":35},[15,898,899,900,902],{},"gRPC особенно полезен для внутренних вызовов между сервисами: контракт описан в ",[37,901,216],{},", клиенты генерируются, а deadline\u002Ferror model встроены в runtime.",[904,905,909,912,1090],"predict",{"answer":906,"id":907,"xp":908},"rpc\\nrest","rpc-overview-p1","15",[15,910,911],{},"Что выведет программа?",[879,913,914],{"v-slot:code":35},[30,915,917],{"className":64,"code":916,"language":66,"meta":35,"style":35},"package main\n\nimport \"fmt\"\n\nfunc chooseAPI(internal bool, needsBrowser bool) string {\n    if internal && !needsBrowser {\n        return \"rpc\"\n    }\n    return \"rest\"\n}\n\nfunc main() {\n    fmt.Println(chooseAPI(true, false))\n    fmt.Println(chooseAPI(false, true))\n}\n",[37,918,919,927,931,945,949,983,1000,1008,1013,1021,1025,1029,1039,1066,1086],{"__ignoreMap":35},[40,920,921,924],{"class":42,"line":43},[40,922,923],{"class":77},"package",[40,925,926],{"class":84}," main\n",[40,928,929],{"class":42,"line":49},[40,930,404],{"emptyLinePlaceholder":403},[40,932,933,936,939,942],{"class":42,"line":55},[40,934,935],{"class":77},"import",[40,937,938],{"class":111}," \"",[40,940,941],{"class":84},"fmt",[40,943,944],{"class":111},"\"\n",[40,946,947],{"class":42,"line":167},[40,948,404],{"emptyLinePlaceholder":403},[40,950,951,954,957,960,964,967,969,972,974,977,980],{"class":42,"line":407},[40,952,953],{"class":77},"func",[40,955,956],{"class":84}," chooseAPI",[40,958,959],{"class":73},"(",[40,961,963],{"class":962},"s9osk","internal",[40,965,966],{"class":77}," bool",[40,968,434],{"class":73},[40,970,971],{"class":962},"needsBrowser",[40,973,966],{"class":77},[40,975,976],{"class":73},") ",[40,978,979],{"class":77},"string",[40,981,982],{"class":73}," {\n",[40,984,985,988,991,994,997],{"class":42,"line":413},[40,986,987],{"class":77},"    if",[40,989,990],{"class":73}," internal ",[40,992,993],{"class":77},"&&",[40,995,996],{"class":77}," !",[40,998,999],{"class":73},"needsBrowser {\n",[40,1001,1002,1005],{"class":42,"line":419},[40,1003,1004],{"class":77},"        return",[40,1006,1007],{"class":111}," \"rpc\"\n",[40,1009,1010],{"class":42,"line":425},[40,1011,1012],{"class":73},"    }\n",[40,1014,1015,1018],{"class":42,"line":491},[40,1016,1017],{"class":77},"    return",[40,1019,1020],{"class":111}," \"rest\"\n",[40,1022,1023],{"class":42,"line":497},[40,1024,58],{"class":73},[40,1026,1027],{"class":42,"line":502},[40,1028,404],{"emptyLinePlaceholder":403},[40,1030,1031,1033,1036],{"class":42,"line":508},[40,1032,953],{"class":77},[40,1034,1035],{"class":84}," main",[40,1037,1038],{"class":73},"() {\n",[40,1040,1041,1044,1047,1049,1052,1054,1058,1060,1063],{"class":42,"line":514},[40,1042,1043],{"class":73},"    fmt.",[40,1045,1046],{"class":84},"Println",[40,1048,959],{"class":73},[40,1050,1051],{"class":84},"chooseAPI",[40,1053,959],{"class":73},[40,1055,1057],{"class":1056},"sDLfK","true",[40,1059,434],{"class":73},[40,1061,1062],{"class":1056},"false",[40,1064,1065],{"class":73},"))\n",[40,1067,1068,1070,1072,1074,1076,1078,1080,1082,1084],{"class":42,"line":707},[40,1069,1043],{"class":73},[40,1071,1046],{"class":84},[40,1073,959],{"class":73},[40,1075,1051],{"class":84},[40,1077,959],{"class":73},[40,1079,1062],{"class":1056},[40,1081,434],{"class":73},[40,1083,1057],{"class":1056},[40,1085,1065],{"class":73},[40,1087,1088],{"class":42,"line":712},[40,1089,58],{"class":73},[879,1091,1092],{"v-slot:hint":35},[15,1093,1094],{},"Внутренний service-to-service вызов без браузера — типичный сценарий для RPC. Публичный браузерный API чаще остаётся REST\u002FHTTP.",[1096,1097,1101,1115,1259],"code-task",{"expected":1098,"id":1099,"xp":1100},"grpc\\nrest\\nrest","rpc-overview-ct1","20",[15,1102,1103,1104,1107,1108,1111,1112,97],{},"Реализуй ",[37,1105,1106],{},"TransportChoice",": если вызов внутренний и нужен строгий контракт, верни ",[37,1109,1110],{},"\"grpc\"",", иначе ",[37,1113,1114],{},"\"rest\"",[879,1116,1117],{"v-slot:template":35},[30,1118,1120],{"className":64,"code":1119,"language":66,"meta":35,"style":35},"package main\n\nimport \"fmt\"\n\nfunc TransportChoice(internal bool, strictContract bool) string {\n    return \"todo\"\n}\n\nfunc main() {\n    fmt.Println(TransportChoice(true, true))\n    fmt.Println(TransportChoice(true, false))\n    fmt.Println(TransportChoice(false, true))\n}\n",[37,1121,1122,1128,1132,1142,1146,1172,1179,1183,1187,1195,1215,1235,1255],{"__ignoreMap":35},[40,1123,1124,1126],{"class":42,"line":43},[40,1125,923],{"class":77},[40,1127,926],{"class":84},[40,1129,1130],{"class":42,"line":49},[40,1131,404],{"emptyLinePlaceholder":403},[40,1133,1134,1136,1138,1140],{"class":42,"line":55},[40,1135,935],{"class":77},[40,1137,938],{"class":111},[40,1139,941],{"class":84},[40,1141,944],{"class":111},[40,1143,1144],{"class":42,"line":167},[40,1145,404],{"emptyLinePlaceholder":403},[40,1147,1148,1150,1153,1155,1157,1159,1161,1164,1166,1168,1170],{"class":42,"line":407},[40,1149,953],{"class":77},[40,1151,1152],{"class":84}," TransportChoice",[40,1154,959],{"class":73},[40,1156,963],{"class":962},[40,1158,966],{"class":77},[40,1160,434],{"class":73},[40,1162,1163],{"class":962},"strictContract",[40,1165,966],{"class":77},[40,1167,976],{"class":73},[40,1169,979],{"class":77},[40,1171,982],{"class":73},[40,1173,1174,1176],{"class":42,"line":413},[40,1175,1017],{"class":77},[40,1177,1178],{"class":111}," \"todo\"\n",[40,1180,1181],{"class":42,"line":419},[40,1182,58],{"class":73},[40,1184,1185],{"class":42,"line":425},[40,1186,404],{"emptyLinePlaceholder":403},[40,1188,1189,1191,1193],{"class":42,"line":491},[40,1190,953],{"class":77},[40,1192,1035],{"class":84},[40,1194,1038],{"class":73},[40,1196,1197,1199,1201,1203,1205,1207,1209,1211,1213],{"class":42,"line":497},[40,1198,1043],{"class":73},[40,1200,1046],{"class":84},[40,1202,959],{"class":73},[40,1204,1106],{"class":84},[40,1206,959],{"class":73},[40,1208,1057],{"class":1056},[40,1210,434],{"class":73},[40,1212,1057],{"class":1056},[40,1214,1065],{"class":73},[40,1216,1217,1219,1221,1223,1225,1227,1229,1231,1233],{"class":42,"line":502},[40,1218,1043],{"class":73},[40,1220,1046],{"class":84},[40,1222,959],{"class":73},[40,1224,1106],{"class":84},[40,1226,959],{"class":73},[40,1228,1057],{"class":1056},[40,1230,434],{"class":73},[40,1232,1062],{"class":1056},[40,1234,1065],{"class":73},[40,1236,1237,1239,1241,1243,1245,1247,1249,1251,1253],{"class":42,"line":508},[40,1238,1043],{"class":73},[40,1240,1046],{"class":84},[40,1242,959],{"class":73},[40,1244,1106],{"class":84},[40,1246,959],{"class":73},[40,1248,1062],{"class":1056},[40,1250,434],{"class":73},[40,1252,1057],{"class":1056},[40,1254,1065],{"class":73},[40,1256,1257],{"class":42,"line":514},[40,1258,58],{"class":73},[879,1260,1261],{"v-slot:hints":35},[299,1262,1263,1269],{},[302,1264,1265,1268],{},[37,1266,1267],{},"internal && strictContract"," — хороший сигнал для gRPC",[302,1270,1271],{},"Публичный API или слабый контракт — оставь REST",[1273,1274,1275],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}",{"title":35,"searchDepth":49,"depth":49,"links":1277},[1278,1279,1285,1286,1287,1288,1289,1290,1291],{"id":130,"depth":49,"text":131},{"id":280,"depth":49,"text":281,"children":1280},[1281,1282,1284],{"id":294,"depth":55,"text":239},{"id":322,"depth":55,"text":1283},"Почему это не проверяют обычным curl",{"id":375,"depth":55,"text":376},{"id":443,"depth":49,"text":444},{"id":545,"depth":49,"text":546},{"id":597,"depth":49,"text":598},{"id":643,"depth":49,"text":644},{"id":779,"depth":49,"text":780},{"id":830,"depth":49,"text":831},{"id":868,"depth":49,"text":869},1781022064703]