Czym jest gRPC i dlaczego w ogóle powstał
Geneza gRPC i jego miejsce w ekosystemie
gRPC to framework RPC (Remote Procedure Call) stworzony i rozwijany przez Google, zbudowany na protokole HTTP/2 i formacie danych Protocol Buffers (Protobuf). W praktyce oznacza to, że zamiast operować na zasobach i metodach HTTP, jak w klasycznym REST, wywołujesz zdalne funkcje tak, jakby były lokalnymi metodami w kodzie. Serwer implementuje konkretne metody, klient je wywołuje, a całość jest mocno typowana i weryfikowana na etapie kompilacji.
gRPC rozwiązuje kilka problemów, z którymi REST radzi sobie przeciętnie: efektywne przesyłanie danych binarnych, streaming dwukierunkowy, lepsze narzędzia do generowania kodu klienckiego, spójne kontrakty interfejsów oraz wydajność przy komunikacji między mikroserwisami. Dlatego szczególnie dobrze sprawdza się w architekturach rozproszonych, gdzie ilość połączeń i wywołań między usługami jest bardzo duża.
W odróżnieniu od klasycznego SOAP czy starych rozwiązań RPC, gRPC jest lekki, dobrze integrowany z nowymi językami programowania i naturalnie wspiera wiele paradygmatów komunikacji: od prostych wywołań jednokrotnych, przez streaming serwera, aż po pełny streaming dwukierunkowy. To sprawia, że nadaje się zarówno do typowych API biznesowych, jak i do usług czasu rzeczywistego, np. komunikatorów czy systemów telemetrii.
Podstawowe pojęcia: serwis, metoda, wiadomość
W gRPC serwis to odpowiednik interfejsu API. Serwis zawiera jedną lub więcej metod, które są wywoływane przez klientów. Każda metoda ma sygnaturę zdefiniowaną w pliku .proto: typ wejściowy, typ wyjściowy oraz informację, czy jest to wywołanie jednokrotne, czy streaming. Na przykład metoda może przyjmować pojedynczą wiadomość i zwracać pojedynczą odpowiedź, albo przyjmować strumień wiadomości i zwracać strumień odpowiedzi.
Podstawową jednostką danych w gRPC jest wiadomość (message) zdefiniowana w Protobuf. Przypomina obiekt lub rekord zawierający pola o konkretnych typach, np. liczby całkowite, teksty, listy, zagnieżdżone struktury. Każde pole ma swój numer, który jest używany przy serializacji binarnej. Dzięki temu wiadomości są małe, wydajne i kompatybilne wstecznie, o ile rozsądnie zarządza się numerami pól.
Dwa kluczowe aspekty odróżniające gRPC od REST: silne typowanie i kontrakt jako punkt wyjścia. Zamiast najpierw pisać kod serwera, a potem dokumentować go w OpenAPI, w gRPC zaczynasz od pliku .proto, który definiuje kontrakt. Na tej podstawie generowany jest kod serwera i klienta. To drastycznie zmniejsza ryzyko niespójności między dokumentacją a implementacją.
Architektura komunikacji w gRPC
gRPC opiera się na protokole HTTP/2, dzięki czemu korzysta z funkcji takich jak multiplexing (wiele równoległych strumieni na jednym połączeniu TCP), nagłówki binarne (HPACK), utrzymywanie połączeń (connection pooling) i lepsza kontrola nad przepływem danych. Z punktu widzenia programisty widać to w postaci mniejszego narzutu na każde wywołanie i niższych opóźnień.
Komunikacja przebiega w modelu klient–serwer. Klient wywołuje zdefiniowaną w .proto metodę, gRPC runtime zajmuje się serializacją wiadomości do formatu binarnego Protobuf, wysyła je przez HTTP/2 do serwera, który deserializuje dane, wywołuje odpowiednią metodę w kodzie i odsyła odpowiedź. Programista nie musi ręcznie zarządzać requestami HTTP ani parserem JSON czy XML.
Ważną rolę odgrywają również tzw. interceptory (middleware). Pozwalają one na wpięcie logowania, autoryzacji, metryk czy obsługi błędów na poziomie frameworka, bez zaśmiecania kodu biznesowego. Podobnie jak w popularnych frameworkach webowych, można zbudować całe łańcuchy przetwarzania dla przychodzących i wychodzących wywołań.
gRPC vs REST: porównanie praktyczne
Model komunikacji: zasoby kontra wywołania funkcji
REST opiera się na koncepcji zasobów identyfikowanych przez adresy URL i standardowe metody HTTP (GET, POST, PUT, DELETE, itd.). Logika biznesowa kryje się za manipulacją zasobami, a wywołania mają charakter „operacji na danych”. To dobrze pasuje do CRUD-owych API oraz do publicznych interfejsów przeznaczonych dla szerokiego grona odbiorców.
gRPC używa modelu RPC: zamiast mówić „wyślij POST na /users”, mówisz „wywołaj metodę CreateUser z wiadomością CreateUserRequest i odbierz CreateUserResponse”. To bardzo naturalne dla programisty – przypomina wywołanie metod w kodzie, a nie konstrukcję zapytania HTTP. Szczególnie zyskują na tym zespoły, które intensywnie współpracują nad jednym systemem i mają wspólne repozytorium kontraktów.
Model RPC ułatwia też wersjonowanie logiki. Można dodawać nowe metody do serwisu, pozostawiając stare nietknięte, albo rozszerzać wiadomości o kolejne pola opcjonalne. Wywołanie, które nie zna nowego pola, po prostu je ignoruje. Dzięki temu możliwe są ewolucyjne zmiany w API bez łamania kompatybilności dla starszych klientów, co w rozbudowanych mikroserwisach jest niezwykle ważne.
Wydajność i narzut komunikacyjny
REST w połączeniu z JSON jest wygodny, ale ma istotny narzut: tekstowa serializacja, rozbudowane nagłówki HTTP/1.1, większe payloady. Dla pojedynczego wywołania to zwykle nie problem, ale przy tysiącach requestów na sekundę albo w komunikacji między mikroserwisami zaczyna być odczuwalny. Konwersja JSON <-> obiekty także zajmuje czas i zasoby CPU.
gRPC wykorzystuje binarny format Protobuf, który jest kompaktowy i szybki w serializacji. Do tego dochodzi HTTP/2, który pozwala na wiele strumieni na jednym połączeniu i lepsze zarządzanie przepływem danych. W praktyce przekłada się to na mniejsze opóźnienia i niższe zużycie pasma. W systemach o wysokich wymaganiach wydajnościowych różnica może być kilkukrotna.
Przykład: w systemie rozliczeniowym, gdzie mikroserwisy faktur, płatności i klientów komunikują się ze sobą kilkanaście razy w trakcie pojedynczej transakcji, przejście z REST/JSON na gRPC/Protobuf może znacząco obniżyć czas obsługi jednego żądania końcowego oraz zredukować ruch sieciowy między usługami. To nie jest magia, tylko suma wielu drobnych optymalizacji związanych z protokołem i formatem danych.
Obsługa streamingów i połączeń długotrwałych
REST w swojej podstawowej formie jest modelem request–response: klient wysyła żądanie, serwer odpowiada i połączenie jest zamykane. Istnieją oczywiście techniki takie jak SSE (Server-Sent Events), WebSockety czy długie pollingi, ale wymagają one dodatkowych bibliotek lub kompromisów. Nie są też wbudowane w sam standard REST.
gRPC natywnie wspiera cztery tryby wywołań:
- unary (jedna wiadomość przychodząca, jedna wychodząca),
- server streaming (jedno żądanie, wiele odpowiedzi w czasie),
- client streaming (wiele żądań, jedna odpowiedź po zakończeniu strumienia),
- bidirectional streaming (wiele żądań i odpowiedzi w obie strony, równolegle).
To otwiera drogę do efektywnych implementacji komunikatorów, systemów telemetrycznych, powiadomień w czasie rzeczywistym, przesyłania logów, czy streamingów multimedialnych. Programista nie musi kombinować z protokołami dodatkowymi – wszystko dostaje w jednym frameworku i jednym modelu programowania.
Strumieniowanie w gRPC jest nie tylko kwestią wygody, ale też skalowalności. Zamiast wysyłać ogromny JSON w jednym żądaniu, można przesyłać dane porcjami, reagować na przeciążenia i dynamicznie zarządzać tempem wysyłki. To ułatwia integrację z backpressure, kolejkowaniem oraz systemami przetwarzania strumieniowego.
Komfort pracy z typami i kontraktami
REST z JSON-em jest luźno typowany. Formalnie można dopisać schematy JSON Schema i definicje OpenAPI, ale narzędzie to jedno, a ich egzekwowanie i aktualizacja – drugie. Bardzo często backend wraca z nieco inną strukturą niż ta w dokumentacji, a frontend dowiaduje się o tym dopiero w runtime.
gRPC stawia na kontrakt jako źródło prawdy. Pliki .proto są kompilowane do klas w językach programowania. Jeśli zmienisz typ pola, dodasz nowe obowiązkowe pole albo usuniesz metodę, kompilator pokaże błędy w miejscach, które wymagają aktualizacji. Tego typu „twarda” weryfikacja znacząco redukuje liczbę klas problemów, które w REST wychodzą dopiero w testach integracyjnych lub produkcji.
Dochodzi do tego obsługa generowania klientów. W gRPC wygenerowany klient zna wszystkie metody serwisu, ich parametry i typy zwrotne. Programista wywołuje je tak samo, jak lokalne funkcje. Nie musi ręcznie sklejać adresów URL, serializować JSON-a ani implementować wrapperów HTTP. W dłuższej perspektywie to oszczędność czasu i mniej miejsca na pomyłki.
Kiedy gRPC ma przewagę nad REST w praktycznych projektach
Mikroserwisy i wewnętrzna komunikacja w systemie
Najbardziej oczywisty scenariusz dla gRPC to komunikacja wewnętrzna między mikroserwisami. Serwisy w ramach jednej organizacji mogą używać dowolnego protokołu, nie są ograniczone oczekiwaniami zewnętrznych integratorów czy przeglądarek. Liczy się wydajność, stabilność i łatwość utrzymania. gRPC spełnia te kryteria znacznie lepiej niż REST w wielu przypadkach.
W typowej architekturze mikroserwisowej, w której serwisy komunikują się ze sobą setki tysięcy razy na minutę, redukcja rozmiaru payloadów i kosztu serializacji ma wymierny wpływ na zużycie CPU i sieci. Do tego dochodzi wygodniejsze generowanie klientów i prostsze zarządzanie kontraktami między zespołami. Często przyjmuje się podejście: „na zewnątrz REST/HTTP+JSON, wewnątrz gRPC/Protobuf”.
W takim modelu brama API (API Gateway) albo serwis edge’owy pełni rolę tłumacza między światem HTTP/JSON a gRPC. Klient zewnętrzny (np. przeglądarka, klient mobilny) komunikuje się przez klasyczne REST-owe endpointy, a brama przekłada te wywołania na gRPC do serwisów wewnętrznych. Użytkownik nic o tym nie wie, a system zyskuje lepszą efektywność w środku.
Systemy o wysokich wymaganiach wydajnościowych
Druga kategoria to systemy, w których każda milisekunda i każdy bajt mają znaczenie. Mogą to być platformy tradingowe, systemy analityczne, rozproszone silniki gier, usługi ML/AI czy systemy IoT przesyłające tysiące małych komunikatów. Tekstowy JSON to w takich warunkach często niepotrzebny balast.
gRPC z Protobufem pozwala przekazywać dane bardzo kompaktowo. Nawet jeśli różnice w pojedynczym wywołaniu są niewielkie, to przy milionach komunikatów robią dużą różnicę. Dodatkowo HTTP/2 zapewnia wydajniejsze wykorzystanie połączeń sieciowych, co ma znaczenie w środowiskach o ograniczonej przepustowości lub wysokim opóźnieniu.
Warto zwrócić uwagę na przewidywalność. W silnikach gier czy systemach tradingowych ważne jest, aby opóźnienia były nie tylko niskie, ale i stabilne. gRPC, dzięki binarnej serializacji i braku dodatkowych warstw (np. tłumaczenia JSON), wprowadza mniej zmienności w czasie odpowiedzi. To ułatwia planowanie i strojenie całego systemu.
Streaming, komunikacja w czasie rzeczywistym i IoT
gRPC jest naturalnym wyborem tam, gdzie pojawia się ciągły strumień danych lub potrzeba dwukierunkowej komunikacji. Przykłady: śledzenie pozycji pojazdów, odczyty z setek tysięcy czujników IoT, komunikatory w czasie rzeczywistym, systemy monitoringu, powiadomienia push z serwera do klientów.
W takich zastosowaniach REST zaczyna się „łamać”. Można co prawda użyć WebSocketów, ale wymaga to dodatkowej logiki, własnych kontraktów i często ręcznej deserializacji JSON-a. gRPC daje streaming w standardzie: definicja metody z keywordem stream i gotowe. Wygenerowany klient i serwer rozumieją, że dane płyną w czasie, a programista ma do dyspozycji wygodne API do odczytu i zapisu kolejnych wiadomości.
W IoT dodatkowo liczy się lekkość formatu. Urządzenia o ograniczonych zasobach (mało RAM, słaby CPU, niestabilne łącza) lepiej radzą sobie z binarną strukturą Protobuf niż z tekstowym JSON-em, który trzeba parsować i trzymać w pamięci jako drzewo obiektów. W połączeniu z kompresją i kontrolą przepływu daje to realne oszczędności energii i pasma.
Silne typowanie i kontrakty między zespołami
W większych organizacjach zespoły backendu, frontendu, mobilne i DevOps pracują równolegle nad jednym ekosystemem. Uzgadnianie kontraktów API często staje się wąskim gardłem. REST i OpenAPI pomagają, ale nadal nietrudno o rozjazd między dokumentacją, kodem i implementacjami klientów.
gRPC wprowadza jedno źródło prawdy w postaci plików .proto. Zespół definiuje tam serwisy, metody i struktury danych. Na tej podstawie generowany jest kod w wielu językach. Jeśli coś się zmieni, kompilacja się wysypie lub narzędzia wskażą niezgodności. To wymusza dyscyplinę i upraszcza proces przeglądów (code review) dotyczących interfejsów.
Gdzie REST nadal wygrywa i kiedy lepiej zostać przy HTTP+JSON
gRPC nie jest złotym młotkiem. Są obszary, w których klasyczny REST z JSON-em wciąż jest prostszy, tańszy i bardziej przewidywalny.
Najbardziej typowy przykład to publiczne API dla przeglądarek i integracji zewnętrznych. JavaScript w przeglądarce nie ma natywnego wsparcia dla gRPC (standard gRPC-Web to de facto wariant, który wymaga dodatkowego proxy i ma pewne ograniczenia). Dla prostych integracji – partner techniczny, który potrzebuje pobrać listę produktów czy złożyć zamówienie – zwykłe HTTP+JSON jest bardziej zrozumiałe i wymaga mniejszego „onboardingu” technologicznego.
REST sprawdza się też przy prostych CRUD-ach i małej dynamice zmian kontraktów. Jeśli masz jeden monolit lub kilka usług, a API jest używane głównie przez wewnętrzny frontend w React/Vue/Angular, to koszty przejścia na gRPC mogą przewyższyć korzyści. Zespół musi poznać nowy stack, narzędzia, pipeline’y generowania kodu. Zysk wydajnościowy bywa wtedy niezauważalny, bo największym bottleneckiem jest baza danych albo logika biznesowa, a nie format komunikacji.
Są też przypadki, gdzie liczy się czytelność danych dla ludzi: debugowanie przez curl/Postmana, szybki podgląd odpowiedzi w logach czy integracja z systemami, które trudno rozszerzać o nowe biblioteki (stare ERP-y, skrypty w narzędziach ETL). Tekstowy JSON jest wygodniejszy do ręcznego grzebania niż binarny Protobuf, nawet jeśli później narzędzia częściowo tę barierę zdejmują.
Dlatego rozsądnym podejściem bywa model mieszany: zewnętrzne API publiczne w REST, a wewnętrzna komunikacja między serwisami w gRPC. To nie religia, tylko pragmatyka.
Przygotowanie środowiska pod pierwszy serwis gRPC
Aby uruchomić pierwszy serwis, dobrze jest przejść przez typowy zestaw kroków: wybór języka, instalacja narzędzi, przygotowanie struktury projektu i definicji kontraktu. Przykłady dalej będą oparte na prostym serwisie w stylu „Hello” lub „Greeter”, bo skupiamy się na mechanice, a nie złożonej domenie.
Wybór języka i stosu technologicznego
gRPC ma oficjalne implementacje dla wielu języków: Go, Java, Kotlin, C#, Python, Node.js, C++, Ruby, PHP i kilku innych. Przy wyborze dobrze oprzeć się na tym, co już jest w organizacji, oraz na jakości wsparcia narzędziowego.
- Go – bardzo popularny w mikrousługach, gRPC jest tu „naturalnym” wyborem, świetne wsparcie ekosystemu.
- Java/Kotlin – integracja z Spring Boot (spring-cloud-gcp, startery gRPC od społeczności), dobre narzędzia do generacji kodu (Gradle/Maven plugins).
- .NET (C#) – natywne wsparcie w ASP.NET Core, szablony projektów, dobre wsparcie w Visual Studio i Riderze.
- Node.js – wygodny do prostych gatewayów, adaptacja do gRPC-Web, integracja z istniejącymi backendami JS/TS.
- Python – sensowny wybór dla usług ML/AI i glue-code, choć pod dużym obciążeniem warto mierzyć wydajność.
Istotne są też narzędzia CI/CD. W pipeline’ach trzeba będzie skompilować pliki .proto do kodu. Jeśli w projekcie używasz Dockera, często opłaca się przygotować własny obraz bazowy zawierający kompilator Protobuf i pluginy gRPC, aby uniknąć zaskoczeń na różnych środowiskach.
Instalacja kompilatora Protobuf i pluginów gRPC
Podstawowym narzędziem jest protoc, czyli kompilator Protobuf. Do tego dochodzi plugin językowy, który generuje kod kliencki i serwerowy dla gRPC. Przykładowo, dla Go i Pythona wygląda to następująco (przykłady są poglądowe, bez wiązania do konkretnej platformy):
# Instalacja protoc (np. w systemach Linux)
# 1. Pobranie archiwum ze strony GitHub/google/protobuf
# 2. Rozpakowanie do /usr/local i dodanie do PATH
unzip protoc-*.zip -d /usr/local
export PATH="$PATH:/usr/local/bin"
# Przykład dla Go: plugin do gRPC
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
# Przykład dla Pythona: biblioteki runtime
pip install grpcio grpcio-toolsW wielu językach pluginy rejestrują się automatycznie na ścieżce (np. $GOPATH/bin w Go). Jeśli protoc nie widzi pluginu, trzeba dopilnować, aby katalog z binarką znalazł się w PATH.
Struktura plików w prostym projekcie gRPC
W niewielkim serwisie często wystarcza bardzo prosta struktura katalogów. Przykład w stylu Go, ale analogiczny układ można zastosować w innych językach:
/greeter-service
/proto
greeter.proto
/cmd
/server
main.go
/client
main.go
/internal
/greeter
service.go
go.mod
go.sumKatalog proto zawiera definicje kontraktów, cmd/server i cmd/client – aplikacje uruchamiane (entrypointy), a internal/greeter – implementację logiki serwisu. W repozytoriach wielo-usługowych (monorepo) pliki .proto często lądują we wspólnym module, np. /apis czy /contracts, aby wszystkie serwisy mogły korzystać z jednego źródła prawdy.
Pierwszy kontrakt: definiowanie pliku .proto
Plik .proto opisuje wiadomości (struktury danych) oraz serwisy (zestawy metod). Typowy minimalny przykład serwisu „powitalnego” z jedną metodą unary będzie wyglądał podobnie niezależnie od języka docelowego.
Prosty przykład definicji serwisu
Poniżej minimalny przykład greeter.proto z jedną metodą unary. W praktyce to właśnie od takiego kontraktu zwykle zaczyna się migrację z REST na gRPC dla pojedynczego przypadek użycia.
syntax = "proto3";
package greeter.v1;
// Opcjonalnie: przestrzeń nazw dla wygenerowanego kodu w różnych językach
option go_package = "example.com/greeter-service/proto;greeterv1";
service GreeterService {
// Unary RPC: jedno żądanie, jedna odpowiedź
rpc SayHello (HelloRequest) returns (HelloResponse) {}
}
message HelloRequest {
string name = 1;
}
message HelloResponse {
string message = 1;
}Kilka istotnych elementów:
syntax = "proto3"– aktualna wersja składni Protobuf, domyślny wybór w nowych projektach.package– przestrzeń nazw w ramach plików .proto, przydaje się przy większej liczbie serwisów.option go_package(lub odpowiednik dla innego języka) – kontroluje pakiet/moduł wewnątrz wygenerowanego kodu, co ma znaczenie dla importów.GreeterService– definicja serwisu z metodąSayHello.HelloRequestiHelloResponse– komunikaty wejściowy i wyjściowy.
Numery pól (= 1, = 2 itd.) są kluczowe dla kompatybilności wstecznej. Zmienianie ich po wdrożeniu do produkcji jest poważnym naruszeniem kontraktu. Zmieniać można nazwy pól czy komentarze, natomiast numer pola jest identyfikatorem wykorzystywanym podczas serializacji.
Definicje RPC z użyciem streamingów
W tym samym pliku .proto można zdefiniować różne typy wywołań. Rozszerzając przykład o strumieniowanie, kontrakt może wyglądać tak:
service GreeterService {
// Unary
rpc SayHello (HelloRequest) returns (HelloResponse) {}
// Server streaming: jedno żądanie, wiele odpowiedzi
rpc ManyHellos (HelloRequest) returns (stream HelloResponse) {}
// Client streaming: wiele żądań, jedna odpowiedź
rpc CollectNames (stream HelloRequest) returns (HelloSummary) {}
// Bidirectional streaming: wiele żądań i odpowiedzi
rpc Chat (stream HelloRequest) returns (stream HelloResponse) {}
}
message HelloSummary {
int32 count = 1;
}Tak zdefiniowane metody po wygenerowaniu kodu dadzą różne typy interfejsów po stronie serwera i klienta (np. strumienie asynchroniczne w C#, kanały w Go, asynchroniczne iteratory w Node/Python). Model programowania jest jednak spójny, co ułatwia późniejsze rozszerzanie API.
Generowanie kodu serwera i klienta
Po zdefiniowaniu kontraktu kolejnym krokiem jest wygenerowanie klas i interfejsów. Dokładne komendy zależą od języka i używanego build systemu, ale wzorzec jest podobny: wskazujesz protoc plik .proto, katalog wyjściowy i pluginy.
Przykładowe wywołanie protoc
Zakładając, że pracujesz w projekcie Go z katalogiem /proto, generowanie może wyglądać tak:
protoc
--proto_path=proto
--go_out=proto --go_opt=paths=source_relative
--go-grpc_out=proto --go-grpc_opt=paths=source_relative
proto/greeter.protoPo wykonaniu tej komendy w katalogu proto pojawią się dwa pliki (nazwy mogą się różnić w zależności od konfiguracji):
greeter.pb.go– struktury wiadomości, typy danych, helpery Protobuf,greeter_grpc.pb.go– interfejs serwisu, klient gRPC, rejestracja serwera itd.
W innych językach pluginy i rozszerzenia plików będą oczywiście inne, ale idea pozostaje taka sama: nie piszesz klienta ani „szkieletu” serwera ręcznie, tylko korzystasz z wygenerowanego kodu.
Integracja generowania z build systemem
Ręczne wywoływanie protoc szybko staje się uciążliwe. W poważniejszych projektach generowanie włącza się do systemu buildów:
- w Go – skrypty
make, narzędzia takie jakbuflub task runner (np.mage), - w Javie/Kotlinie – pluginy Gradle/Maven (
protobuf-gradle-plugin), - w .NET – integracja przez pliki
.csprojzGrpc.Tools, - w Node.js/Pythonie – skrypty npm/pipenv/poetry dodane do pipeline’ów CI.
Sensowną praktyką jest niewrzucanie wygenerowanych plików do repozytorium i generowanie ich w czasie builda. Uproszcza to aktualizacje pluginów i unika konfliktów w PR-ach. Są jednak organizacje, które wersjonują wygenerowany kod, jeśli repozytoria klientów są zupełnie niezależne.

Implementacja serwera gRPC – praktyczny szkic
Kiedy kontrakt i wygenerowany kod są gotowe, można zaimplementować serwer. Różne języki oferują inne API, lecz ogólne kroki są podobne: zaimplementować interfejs serwisu, utworzyć instancję serwera gRPC, zarejestrować usługę i nasłuchiwać na porcie.
Implementacja metody unary
W pseudokodzie, niezależnym od języka, wygląda to tak:
// 1. Implementacja serwisu
class GreeterServiceImpl : GreeterService {
override SayHello(request, context) {
response = new HelloResponse()
response.message = "Hello, " + request.name
return response
}
}
// 2. Tworzenie i uruchomienie serwera
server = new GrpcServer()
server.AddService(new GreeterServiceImpl())
server.Listen("0.0.0.0:50051")
server.Start()W konkretnym języku kod będzie miał inną składnię, ale idea jest zawsze identyczna: implementujesz metody z wygenerowanego interfejsu i rejestrujesz tę implementację w serwerze gRPC.
Obsługa kontekstu, deadline’ów i metadanych
W gRPC każda metoda otrzymuje kontekst wywołania (czasem jako context, czasem jako ServerCallContext itp.). Zawiera on:
- deadline – czas, po którym żądanie powinno zostać anulowane,
- metadane (metadata, headers) – nagłówki użytkownika, np. tokeny autoryzacyjne, ID żądania,
- informację o anulowaniu – możliwość przerwania długotrwałego RPC po stronie klienta.
Dla serwisów o większym obciążeniu kluczowe jest reagowanie na anulowanie – np. przerwanie zapytań do bazy lub wyjście z pętli przetwarzania. Zignorowanie sygnałów z kontekstu prowadzi do wykonywania niepotrzebnej pracy po stronie serwera, nawet jeśli klient dawno się rozłączył.
Serwer dla streamingów – wzorzec pracy z pętlami
Metody streamingowe implementuje się zwykle jako pętle odczytu/zapisu. W pseudokodzie bidirectional streaming może wyglądać tak:
Implementacja bidirectional streamingu w serwerze
Kontynuując przykład, schemat obsługi dwukierunkowego strumienia na serwerze zwykle sprowadza się do pętli czytającej i pętli zapisującej – czasem połączonych w jedną, czasem rozdzielonych na osobne go-routiny / wątki.
rpc Chat(stream HelloRequest) returns (stream HelloResponse) {}
service GreeterServiceImpl {
override Chat(stream, context) {
loop {
// Odczytaj kolejną wiadomość od klienta
req = stream.Recv()
if req == EOF {
break
}
// Opcjonalnie: obsługa anulowania / deadline'u
if context.IsCancelled() {
break
}
// Przygotuj odpowiedź
resp = new HelloResponse()
resp.message = "You said: " + req.name
// Wyślij odpowiedź do klienta
stream.Send(resp)
}
}
}W językach z dobrym wsparciem współbieżności często wygodniej jest rozdzielić logikę na producenta i konsumenta. Przykład w pseudokodzie inspirowanym Go:
func (s *GreeterServiceImpl) Chat(stream GreeterService_ChatServer) error {
// Kanał na przychodzące komunikaty
msgs := make(chan *HelloRequest)
// Reader: odczyt ze strumienia
go func() {
defer close(msgs)
for {
req, err := stream.Recv()
if err == io.EOF {
return
}
if err != nil {
// Obsługa błędu, logowanie itd.
return
}
msgs <- req
}
}()
// Writer: zapis odpowiedzi
for {
select {
case <-stream.Context().Done():
return stream.Context().Err()
case req, ok := <-msgs:
if !ok {
return nil
}
resp := &HelloResponse{Message: "You said: " + req.Name}
if err := stream.Send(resp); err != nil {
return err
}
}
}
}Taki wzorzec daje większą elastyczność – można dołożyć kolejkę, buforowanie lub osobny worker pool, gdy jedna sesja strumienia wykonuje więcej zadań w tle.
Obsługa błędów i statusów gRPC
gRPC nie używa kodów HTTP bezpośrednio na poziomie API aplikacyjnego. Zamiast tego posługuje się własnym zbiorem statusów (np. OK, INVALID_ARGUMENT, NOT_FOUND, DEADLINE_EXCEEDED, UNAUTHENTICATED, PERMISSION_DENIED). W praktyce przekłada się to na inny sposób zwracania błędów niż w HTTP/REST.
Generalny schemat wygląda tak:
- zwracasz wynik lub błąd typu „status” (w Go:
status.Status, w .NET:RpcException, w Javie:StatusRuntimeException), - każdy błąd ma kod (code) i opis (message),
- dodatkowo można przekazywać szczegóły (error details) – zserializowane komunikaty Protobuf.
Przykład stylu rodem z Go, ale idea jest identyczna w innych językach:
import "google.golang.org/grpc/status"
import "google.golang.org/grpc/codes"
func (s *GreeterServiceImpl) SayHello(
ctx context.Context, req *HelloRequest,
) (*HelloResponse, error) {
if req.GetName() == "" {
return nil, status.Error(codes.InvalidArgument, "name must not be empty")
}
// normalna ścieżka
return &HelloResponse{
Message: "Hello, " + req.GetName(),
}, nil
}Po stronie klienta taki błąd można przechwycić i zmapować np. na wyjątek domenowy, komunikat w UI lub kod błędu zwracany dalej przez API brzegowe.
Minimalny serwer gRPC w Go – od main.go do nasłuchiwania
Aby przejść z pseudokodu do działającego procesu, warto przejść przez kompletny przykład serwera uruchamianego z katalogu cmd/server. Zakładamy, że wygenerowany kod Protobuf znajduje się w pakiecie proto, a implementacja serwisu w internal/greeter.
Implementacja serwisu w Go
Najpierw prosta implementacja metody unary w katalogu internal/greeter/service.go:
package greeter
import (
"context"
"fmt"
greeterv1 "example.com/greeter-service/proto"
)
type Service struct {
greeterv1.UnimplementedGreeterServiceServer
}
func NewService() *Service {
return &Service{}
}
func (s *Service) SayHello(
ctx context.Context,
req *greeterv1.HelloRequest,
) (*greeterv1.HelloResponse, error) {
if req.GetName() == "" {
return nil, fmt.Errorf("name is required")
}
msg := fmt.Sprintf("Hello, %s!", req.GetName())
return &greeterv1.HelloResponse{Message: msg}, nil
}Wkomponowanie UnimplementedGreeterServiceServer jako pola anonimowego chroni przed złamaniem kontraktu po dodaniu nowych metod w pliku .proto – kompilator szybko pokaże brakujące implementacje.
Uruchomienie serwera i rejestracja usługi
Teraz prosty main.go w katalogu cmd/server:
package main
import (
"log"
"net"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
greeterv1 "example.com/greeter-service/proto"
"example.com/greeter-service/internal/greeter"
)
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
server := grpc.NewServer()
// Rejestracja serwisu
srv := greeter.NewService()
greeterv1.RegisterGreeterServiceServer(server, srv)
// Reflection: przydatne np. dla grpcurl / narzędzi debugujących
reflection.Register(server)
log.Printf("gRPC server listening on %s", lis.Addr().String())
if err := server.Serve(lis); err != nil {
log.Fatalf("server error: %v", err)
}
}Tak prosty serwer wystarczy, żeby pierwszy klient mógł wywołać metodę SayHello. Z czasem konfiguracja rozrasta się o interceptory, TLS, logowanie, metrics czy rejestrację w service discovery.
Minimalny klient gRPC – od wywołania RPC do obsługi deadline
Po stronie klienta wygenerowany kod dostarcza stub, z którego korzysta się jak z „zwykłego” obiektu serwisowego. Najprostszy klient można umieścić w katalogu cmd/client.
Przykładowy klient w Go
package main
import (
"context"
"log"
"time"
"google.golang.org/grpc"
greeterv1 "example.com/greeter-service/proto"
)
func main() {
conn, err := grpc.Dial(
"localhost:50051",
grpc.WithInsecure(), // tylko do lokalnego dev
grpc.WithBlock(), // poczekaj na połączenie
)
if err != nil {
log.Fatalf("could not connect: %v", err)
}
defer conn.Close()
client := greeterv1.NewGreeterServiceClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
resp, err := client.SayHello(ctx, &greeterv1.HelloRequest{
Name: "World",
})
if err != nil {
log.Fatalf("RPC error: %v", err)
}
log.Printf("Response: %s", resp.GetMessage())
}Taki klient uruchamiany lokalnie jest dobrym sanity-checkiem po każdym dodaniu nowej metody RPC – można szybko zweryfikować poprawność konfiguracji serwera, kontraktu oraz sieci (port, certyfikaty).
Wywołania streamingowe po stronie klienta
Jeśli kontrakt zawiera metody strumieniowe, wywołania też przyjmą formę pętli. Dla server streamingu ManyHellos kod może wyglądać tak:
func callManyHellos(client greeterv1.GreeterServiceClient) error {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
stream, err := client.ManyHellos(ctx, &greeterv1.HelloRequest{
Name: "StreamUser",
})
if err != nil {
return err
}
for {
resp, err := stream.Recv()
if err == io.EOF {
// Koniec strumienia
break
}
if err != nil {
return err
}
log.Printf("Got message: %s", resp.GetMessage())
}
return nil
}Client streaming i bidirectional streaming wymagają zarówno wysyłania, jak i odbierania komunikatów, często w osobnych go-routinach – schemat jest podobny do serwera.
Bezpieczeństwo: TLS, certyfikaty i autoryzacja
W produkcji gRPC niemal zawsze działa na HTTPS/HTTP2 z TLS. Łączenie się przez grpc.WithInsecure() powinno pozostać w sferze lokalnych eksperymentów.
Konfiguracja TLS na serwerze
Załóżmy, że masz certyfikat serwera i klucz zapisane w plikach PEM. Prosty przykład konfiguracji w Go:
import "google.golang.org/grpc/credentials"
func main() {
certFile := "certs/server.crt"
keyFile := "certs/server.key"
creds, err := credentials.NewServerTLSFromFile(certFile, keyFile)
if err != nil {
log.Fatalf("failed to load TLS credentials: %v", err)
}
server := grpc.NewServer(
grpc.Creds(creds),
)
// rejestracja serwisu itd.
}Podobny mechanizm istnieje w innych językach – konfiguracja polega na podaniu certyfikatów w opcjach tworzenia serwera i klienta.
Konfiguracja TLS na kliencie
Klient musi ufać CA, który podpisał certyfikat serwera. W środowiskach testowych często wykorzystuje się self-signed CA i własny bundle:
import "google.golang.org/grpc/credentials"
func dialSecure(addr string) (*grpc.ClientConn, error) {
creds, err := credentials.NewClientTLSFromFile("certs/ca.crt", "")
if err != nil {
return nil, err
}
return grpc.Dial(
addr,
grpc.WithTransportCredentials(creds),
)
}Na tym poziomie zapewniona jest poufność i integralność. Kolejny krok to autoryzacja – np. tokeny JWT przekazywane w metadanych.
Autoryzacja z użyciem metadanych
Tokeny i inne nagłówki aplikacyjne przechodzą w gRPC w metadanych (metadata). Na kliencie najczęściej dodaje się je do kontekstu:
import "google.golang.org/grpc/metadata"
func callWithToken(
client greeterv1.GreeterServiceClient,
token string,
) (*greeterv1.HelloResponse, error) {
md := metadata.New(map[string]string{
"authorization": "Bearer " + token,
})
ctx := metadata.NewOutgoingContext(context.Background(), md)
return client.SayHello(ctx, &greeterv1.HelloRequest{Name: "Secured"})
}Na serwerze metadane można odczytać z kontekstu wywołania i zweryfikować token przed obsługą żądania lub w interceptorze, aby centralnie wprowadzić reguły bezpieczeństwa.
Interceptors: logowanie, metrics i cross-cutting concerns
Interceptors w gRPC pełnią podobną rolę jak middleware w HTTP. Umożliwiają opakowanie wywołań serwisów logiką przekrojową: logowaniem, monitoringiem, retry, rate limitingiem czy walidacją.
Unary interceptor w Go
func loggingUnaryInterceptor(
ctx context.Context,
req interface{},
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler,
) (interface{}, error) {
start := time.Now()
resp, err := handler(ctx, req)
duration := time.Since(start)
statusCode := "OK"
if err != nil {
st, _ := status.FromError(err)
statusCode = st.Code().String()
}
log.Printf(
"method=%s duration=%s status=%s",
info.FullMethod, duration, statusCode,
)
return resp, err
}
func main() {
server := grpc.NewServer(
grpc.UnaryInterceptor(loggingUnaryInterceptor),
)
// rejestracja serwisu...
}Podobny interceptor można dołożyć także dla wywołań strumieniowych. To wygodne miejsce na integrację z Prometheusem, OpenTelemetry lub systemem trace’owania rozproszonego.
Integracja z istniejącą infrastrukturą REST
Wielu zespołów nie może pozwolić sobie na natychmiastowe przełączenie wszystkiego na gRPC. Typowy scenariusz to współistnienie HTTP/JSON i gRPC w tej samej domenie problemu – a czasem nawet w tym samym procesie.
gRPC-Gateway: HTTP/JSON ↔ gRPC
W ekosystemie Go popularnym rozwiązaniem jest grpc-gateway. Na bazie plików .proto generuje ono reverse proxy, które przyjmuje zapytania HTTP/JSON, a następnie wywołuje serwis gRPC.
Typowy przepływ:
- dodajesz opcje HTTP do pliku
.proto(np. przezgoogle.api.http), - generujesz kod gatewaya,
- uruchamiasz proces HTTP, który mapuje requesty REST na wywołania gRPC.
W rezultacie zewnętrzni klienci mogą nadal korzystać z prostego API HTTP/JSON, a wewnętrznie komunikacja między usługami odbywa się po gRPC.
Serwis gRPC i HTTP w jednym procesie
Inna strategia to uruchamianie serwera HTTP i gRPC w jednym binarium, ale na różnych portach. W Go często spina się to wspólnym net.Listener i multiplexingiem HTTP/2, ale prostsze na start jest użycie dwóch portów:
Najczęściej zadawane pytania (FAQ)
Czym jest gRPC i czym różni się od REST API?
gRPC to framework RPC (Remote Procedure Call) oparty na HTTP/2 i formacie danych Protocol Buffers (Protobuf). Zamiast operować na zasobach pod adresami URL, jak w REST, w gRPC wywołujesz zdalne funkcje (metody serwisu) tak, jakby były lokalnymi metodami w kodzie.
Od REST odróżnia go przede wszystkim: binarny, mocno typowany format danych (Protobuf zamiast JSON), kontrakt definiowany w pliku .proto jako punkt wyjścia oraz natywne wsparcie dla różnych trybów komunikacji, w tym streamingu dwukierunkowego. Dzięki temu gRPC jest zwykle szybsze i bardziej wydajne w komunikacji między usługami niż klasyczne REST/JSON.
Kiedy warto wybrać gRPC zamiast REST?
gRPC najlepiej sprawdza się w komunikacji wewnętrznej między mikroserwisami, gdzie liczba wywołań jest bardzo duża, a istotne są niskie opóźnienia i mały narzut sieciowy. Jest też dobrym wyborem tam, gdzie przesyłasz dużo danych binarnych lub potrzebujesz stałych połączeń i streamingu (np. telemetryka, komunikatory, systemy powiadomień w czasie rzeczywistym).
REST pozostaje lepszym wyborem dla publicznych API przeznaczonych dla szerokiego grona odbiorców, gdzie liczy się prostota integracji (HTTP/JSON) i czytelność zasobów. gRPC jest bardziej „inżynierskie” i nastawione na wydajną komunikację w ramach jednego systemu lub organizacji.
Jak wygląda podstawowa struktura serwisu w gRPC (service, method, message)?
W gRPC głównymi elementami są:
- service – odpowiednik interfejsu API; zawiera zestaw metod, które może wywołać klient,
- method – pojedyncza operacja RPC, ma określony typ wejściowy, typ wyjściowy i tryb (unary lub streaming),
- message – struktura danych zdefiniowana w Protobuf, składająca się z pól o konkretnych typach i numerach pól.
Cały kontrakt serwisu opisujesz w pliku .proto – na jego podstawie generowany jest kod serwera i klienta w wybranych językach programowania.
Jak gRPC wykorzystuje HTTP/2 i dlaczego ma to znaczenie?
gRPC działa na protokole HTTP/2, co pozwala na utrzymywanie jednego połączenia TCP z wieloma równoległymi strumieniami (multiplexing), binarne nagłówki (HPACK) oraz lepszą kontrolę przepływu danych. Każde wywołanie RPC jest osobnym strumieniem w ramach tego samego połączenia.
W praktyce oznacza to mniejszy narzut przy dużej liczbie wywołań, niższe opóźnienia oraz efektywniejsze wykorzystanie pasma sieciowego w porównaniu z tradycyjnym REST opartym na HTTP/1.1 i JSON.
Jakie typy wywołań i streamingu obsługuje gRPC?
gRPC natywnie wspiera cztery tryby wywołań RPC:
- unary – jedna wiadomość żądania, jedna odpowiedź,
- server streaming – jedno żądanie, wiele odpowiedzi w czasie,
- client streaming – wiele żądań, jedna odpowiedź po zakończeniu strumienia,
- bidirectional streaming – równoległy strumień wiadomości w obie strony.
Dzięki temu możesz budować zarówno klasyczne API biznesowe, jak i usługi czasu rzeczywistego, bez potrzeby sięgania po dodatkowe protokoły (np. WebSockety).
Dlaczego mówi się, że gRPC jest „mocno typowane” i czym jest kontrakt .proto?
W gRPC definicje serwisów i wiadomości zapisujesz w plikach .proto. Zawierają one dokładnie określone typy pól (np. int32, string, zagnieżdżone message) oraz sygnatury metod. Na tej podstawie generowany jest kod klienta i serwera, co gwarantuje spójność między implementacją a kontraktem.
Takie podejście („contract-first”) różni się od typowego REST/JSON, gdzie struktura danych bywa nieformalna lub opisana tylko dokumentacją. W gRPC typy są weryfikowane na etapie kompilacji, co zmniejsza ryzyko błędów wynikających z niespójnych schematów danych.
Czy gRPC jest lepsze od SOAP i starszych rozwiązań RPC?
gRPC można traktować jako nowoczesne podejście do RPC. W porównaniu z SOAP jest lżejsze (brak XML i rozbudowanych nagłówków), łatwiej integruje się ze współczesnymi językami programowania i domyślnie wspiera streaming oraz HTTP/2. Dzięki Protobuf ma też kompaktowy, binarny format danych.
W odróżnieniu od wielu starych systemów RPC, gRPC ma jednolity, dobrze udokumentowany ekosystem, narzędzia do generowania kodu i mechanizmy takie jak interceptory, które ułatwiają wpinanie logowania, autoryzacji czy metryk bez mieszania ich z logiką biznesową.
Co warto zapamiętać
- gRPC to nowoczesny framework RPC zbudowany na HTTP/2 i Protobuf, który zamiast operować na zasobach HTTP pozwala wywoływać metody zdalne jak lokalne funkcje w kodzie.
- Silne typowanie i kontrakt w pliku .proto są punktem wyjścia – na ich podstawie generowany jest kod serwera i klienta, co minimalizuje niespójności między dokumentacją a implementacją.
- Wykorzystanie binarnego formatu Protobuf oraz HTTP/2 (multiplexing, nagłówki binarne, connection pooling) znacząco zmniejsza narzut komunikacyjny i opóźnienia względem REST/JSON.
- gRPC szczególnie dobrze sprawdza się w komunikacji między mikroserwisami i systemach o dużej liczbie wywołań, gdzie kluczowe są wydajność, małe payloady i niskie zużycie pasma.
- Model RPC w gRPC jest naturalny dla programistów (wywołania metod zamiast operacji na zasobach REST) i ułatwia współpracę zespołów dzielących wspólne repozytorium kontraktów.
- Protobuf i numerowane pola wiadomości zapewniają małe, szybkie w serializacji komunikaty oraz dobrą kompatybilność wsteczną przy rozsądnym wersjonowaniu i rozbudowie struktur.
- Interceptors (middleware) w gRPC pozwalają centralnie obsługiwać logowanie, autoryzację, metryki i błędy, bez zaśmiecania kodu logiką techniczną.






