← Kursa Dön
📄 Text · 22 min

gRPC ile Microservice İletişimi

Giriş — REST'in Ötesinde

Düşün ki iki arkadaş iletişim kuruyor. Biri İstanbul'da, diğeri Ankara'da.

REST iletişimi, mektup göndermek gibi. Zarfa (HTTP) koy, üstüne adresi (URL) yaz, posta kutusuna (server) bırak. Mektup düz metin (JSON), herkes okuyabilir. Ama her mektup için zarf aç, oku, cevap yaz, zarfa koy, gönder... Yavaş. Üstelik zarfın üstünde gereksiz bilgiler var (HTTP header'lar), mektubun kendisi de "okunabilir" formatta olduğu için şişkin.

gRPC iletişimi, telefon görüşmesi gibi. Numarayı çevir (bağlantı kur), konuş (binary veri), anında cevap al. İki taraf da aynı dili konuşuyor (proto tanımı), araya gereksiz çeviri girmiyor. Üstelik hattı açık tutabilirsin — sürekli konuşma (streaming).

gRPC (Google Remote Procedure Call), Google'ın geliştirdiği yüksek performanslı, açık kaynaklı bir RPC framework'üdür. REST'in JSON/HTTP 1.1 yerine Protocol Buffers (protobuf) ve HTTP/2 kullanır. Microservice'ler arası iletişimde REST'e göre 2-10x daha hızlıdır.

Peki neden her yerde REST varken gRPC'ye ihtiyaç duyuyoruz?

ÖzellikREST (JSON/HTTP 1.1)gRPC (Protobuf/HTTP 2)
Veri formatıJSON (text, şişkin)Protobuf (binary, kompakt)
Boyut100 byte JSON mesaj~30 byte protobuf (3x küçük)
HızSerialize/deserialize yavaşBinary, çok hızlı
KontratSwagger/OpenAPI (opsiyonel).proto dosyası (zorunlu)
StreamingZor (WebSocket ayrı)Native (4 tip streaming)
Kod üretimiManuel veya codegenOtomatik (her dilde)
Tarayıcı desteği✅ Doğal❌ gRPC-Web gerekir

REST, dış dünyaya açık API'ler (mobil uygulama, SPA, üçüncü parti entegrasyon) için hâlâ en iyi seçim. Ama microservice'lerin kendi aralarında konuşmasında gRPC açık ara daha verimli.


Protocol Buffers (Protobuf) — Ortak Dil

gRPC'nin temelinde Protocol Buffers (protobuf) adlı bir veri serileştirme formatı var. JSON'ın binary, tip güvenli, hızlı versiyonu olarak düşünebilirsin.

.proto Dosyası — Sözleşme

İki microservice arasındaki iletişimin "sözleşmesi" .proto dosyasında tanımlanır. Bu dosya mesaj yapılarını ve servis metodlarını tanımlar:

// src/main/proto/user.proto

syntax = "proto3";  // Protobuf sürümü — her zaman proto3 kullan

package com.example.grpc;  // Java package ile eşleşir

option java_multiple_files = true;  // Her mesaj ayrı .java dosyasında
option java_package = "com.example.grpc.user";  // Üretilen kodun Java package'ı

// ========================
// Mesaj Tanımları (DTO gibi)
// ========================

message UserRequest {
    int64 id = 1;  // Field numarası — sıra değil, benzersiz ID
}

message UserResponse {
    int64 id = 1;
    string name = 2;
    string email = 3;
    string phone = 4;
    UserRole role = 5;
    repeated string tags = 6;  // repeated = liste (List<String>)
    Address address = 7;       // İç içe mesaj
}

message Address {
    string street = 1;
    string city = 2;
    string country = 3;
    string zip_code = 4;
}

enum UserRole {
    UNKNOWN = 0;  // Enum'ın ilk değeri her zaman 0 olmalı
    ADMIN = 1;
    USER = 2;
    MODERATOR = 3;
}

message CreateUserRequest {
    string name = 1;
    string email = 2;
    string phone = 3;
    UserRole role = 4;
}

message UserListResponse {
    repeated UserResponse users = 1;
    int32 total_count = 2;
}

message Empty {}

// ========================
// Servis Tanımı (API gibi)
// ========================

service UserService {
    // Unary — tek istek, tek cevap (GET /users/{id} gibi)
    rpc GetUser(UserRequest) returns (UserResponse);

    // Unary — oluşturma (POST /users gibi)
    rpc CreateUser(CreateUserRequest) returns (UserResponse);

    // Unary — listeleme
    rpc ListUsers(Empty) returns (UserListResponse);

    // Server Streaming — sunucu sürekli veri gönderir
    rpc StreamUsers(Empty) returns (stream UserResponse);

    // Client Streaming — istemci sürekli veri gönderir
    rpc BatchCreateUsers(stream CreateUserRequest) returns (UserListResponse);

    // Bidirectional Streaming — iki taraf da sürekli veri gönderir
    rpc ChatWithUser(stream ChatMessage) returns (stream ChatMessage);
}

message ChatMessage {
    string sender = 1;
    string content = 2;
    int64 timestamp = 3;
}

Protobuf Field Numaraları

Field numaraları (= 1, = 2) protobuf'un en önemli kuralıdır. Bu numaralar binary encoding'de kullanılır ve asla değiştirilmemeli:

message User {
    string name = 1;    // name her zaman 1 numaralı field
    string email = 2;   // email her zaman 2 numaralı field
    // Yeni field ekle — yeni numara ver
    string phone = 3;   // Sorun yok
    // ESKİ field'ı sil — numarayı TEKRAR KULLANMA
    // string old_field = 4;  // Silindi
    // string new_field = 4;  // ❌ YANLIŞ — 4'ü tekrar kullanma!
    string new_field = 5;    // ✅ Yeni numara ver
}

Protobuf Tip Eşleştirmesi

ProtobufJavaAçıklama
int32int32-bit tamsayı
int64long64-bit tamsayı
floatfloat32-bit kayan nokta
doubledouble64-bit kayan nokta
boolbooleanBoolean
stringStringUTF-8 string
bytesByteStringBinary veri
repeated TList<T>Liste
map<K,V>Map<K,V>Map

gRPC İletişim Tipleri

gRPC dört farklı iletişim tipi sunar. Telefon analojimizi genişletelim:

1. Unary — Tek Soru, Tek Cevap

Normal telefon görüşmesi: "Kaç yaşındasın?" → "25". Bir istek gider, bir cevap gelir. REST'in GET/POST'una karşılık gelir.

Client ──[UserRequest]──> Server
Client <──[UserResponse]── Server

2. Server Streaming — Sunucu Anlatıyor

Telefonda birini arıyorsun ve "Bana bugünün haberlerini oku" diyorsun. O anlatmaya başlıyor, sen dinliyorsun. Sunucu sürekli veri gönderiyor, client dinliyor.

Client ──[Empty]──> Server
Client <──[User 1]── Server
Client <──[User 2]── Server
Client <──[User 3]── Server
Client <──[DONE]──── Server

3. Client Streaming — İstemci Gönderiyor

Telefonda sesli mesaj bırakıyorsun. Sen konuşmaya devam ediyorsun, karşı taraf bittiğinde tek bir cevap veriyor.

Client ──[CreateUser 1]──> Server
Client ──[CreateUser 2]──> Server
Client ──[CreateUser 3]──> Server
Client ──[DONE]──────────> Server
Client <──[UserListResponse]── Server

4. Bidirectional Streaming — İki Taraflı Konuşma

Gerçek telefon görüşmesi: iki taraf da aynı anda konuşup dinleyebilir. Canlı sohbet, gerçek zamanlı veri senkronizasyonu gibi senaryolar.

Client ──[ChatMessage]──> Server
Client <──[ChatMessage]── Server
Client ──[ChatMessage]──> Server
Client ──[ChatMessage]──> Server
Client <──[ChatMessage]── Server

Spring Boot + gRPC Entegrasyonu

Proje Kurulumu

Spring Boot ile gRPC kullanmak için grpc-spring-boot-starter kütüphanesini ve protobuf Maven plugin'ini ekleriz:

<!-- pom.xml -->
<properties>
    <java.version>17</java.version>
    <grpc.version>1.62.2</grpc.version>
    <protobuf.version>3.25.3</protobuf.version>
    <grpc-spring-boot.version>3.1.0.RELEASE</grpc-spring-boot.version>
</properties>

<dependencies>
    <!-- Spring Boot -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>

    <!-- gRPC Spring Boot Starter (Server) -->
    <dependency>
        <groupId>net.devh</groupId>
        <artifactId>grpc-server-spring-boot-starter</artifactId>
        <version>${grpc-spring-boot.version}</version>
    </dependency>

    <!-- gRPC Spring Boot Starter (Client) — client projede -->
    <dependency>
        <groupId>net.devh</groupId>
        <artifactId>grpc-client-spring-boot-starter</artifactId>
        <version>${grpc-spring-boot.version}</version>
    </dependency>

    <!-- Protobuf Java -->
    <dependency>
        <groupId>com.google.protobuf</groupId>
        <artifactId>protobuf-java</artifactId>
        <version>${protobuf.version}</version>
    </dependency>

    <!-- gRPC Stub -->
    <dependency>
        <groupId>io.grpc</groupId>
        <artifactId>grpc-stub</artifactId>
        <version>${grpc.version}</version>
    </dependency>
    <dependency>
        <groupId>io.grpc</groupId>
        <artifactId>grpc-protobuf</artifactId>
        <version>${grpc.version}</version>
    </dependency>

    <!-- javax.annotation (gRPC generated code için) -->
    <dependency>
        <groupId>org.apache.tomcat</groupId>
        <artifactId>annotations-api</artifactId>
        <version>6.0.53</version>
        <scope>provided</scope>
    </dependency>

    <!-- Lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>

    <!-- Test -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

<!-- Protobuf Maven Plugin — .proto dosyalarından Java kodu üretir -->
<build>
    <extensions>
        <extension>
            <groupId>kr.motd.maven</groupId>
            <artifactId>os-maven-plugin</artifactId>
            <version>1.7.1</version>
        </extension>
    </extensions>
    <plugins>
        <plugin>
            <groupId>org.xolstice.maven.plugins</groupId>
            <artifactId>protobuf-maven-plugin</artifactId>
            <version>0.6.1</version>
            <configuration>
                <protocArtifact>
                    com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}
                </protocArtifact>
                <pluginId>grpc-java</pluginId>
                <pluginArtifact>
                    io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}
                </pluginArtifact>
            </configuration>
            <executions>
                <execution>
                    <goals>
                        <goal>compile</goal>
                        <goal>compile-custom</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

Dizin Yapısı

user-service/
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/example/userservice/
│   │   │       ├── UserServiceApplication.java
│   │   │       ├── grpc/
│   │   │       │   └── UserGrpcService.java       ← gRPC Server
│   │   │       ├── service/
│   │   │       │   └── UserService.java
│   │   │       └── model/
│   │   │           └── User.java
│   │   ├── proto/
│   │   │   └── user.proto                          ← Proto tanımı
│   │   └── resources/
│   │       └── application.yml
│   └── test/
│       └── java/
├── target/
│   └── generated-sources/
│       └── protobuf/                               ← Otomatik üretilen Java kodu
│           ├── java/
│           │   └── com/example/grpc/user/
│           │       ├── UserRequest.java
│           │       ├── UserResponse.java
│           │       └── ...
│           └── grpc-java/
│               └── com/example/grpc/user/
│                   └── UserServiceGrpc.java         ← gRPC stub'ları
└── pom.xml

Kod Üretimi

.proto dosyasını src/main/proto/ dizinine koyduğunda, Maven compile sırasında otomatik olarak Java sınıfları üretilir:

# Proto dosyalarından Java kodu üret
mvn compile

# Üretilen kodlar target/generated-sources/protobuf/ altında:
# - UserRequest.java, UserResponse.java → Mesaj sınıfları (DTO)
# - UserServiceGrpc.java → Servis stub'ları (server + client base class'ları)

gRPC Server Oluşturma

@GrpcService — Servis İmplementasyonu

@GrpcService
@RequiredArgsConstructor
@Slf4j
public class UserGrpcService extends UserServiceGrpc.UserServiceImplBase {

    private final UserService userService;

    // ========================
    // Unary RPC — GetUser
    // ========================
    @Override
    public void getUser(UserRequest request, StreamObserver<UserResponse> responseObserver) {
        log.info("gRPC GetUser çağrıldı, id: {}", request.getId());

        try {
            User user = userService.findById(request.getId());

            if (user == null) {
                // Hata — NOT_FOUND status
                responseObserver.onError(
                    Status.NOT_FOUND
                        .withDescription("Kullanıcı bulunamadı: " + request.getId())
                        .asRuntimeException()
                );
                return;
            }

            // Başarılı — domain model → protobuf mesaj dönüşümü
            UserResponse response = UserResponse.newBuilder()
                .setId(user.getId())
                .setName(user.getName())
                .setEmail(user.getEmail())
                .setPhone(user.getPhone() != null ? user.getPhone() : "")
                .setRole(mapRole(user.getRole()))
                .addAllTags(user.getTags() != null ? user.getTags() : List.of())
                .build();

            responseObserver.onNext(response);     // Cevabı gönder
            responseObserver.onCompleted();         // Akışı kapat

        } catch (Exception e) {
            log.error("GetUser hatası", e);
            responseObserver.onError(
                Status.INTERNAL
                    .withDescription("Sunucu hatası: " + e.getMessage())
                    .asRuntimeException()
            );
        }
    }

    // ========================
    // Unary RPC — CreateUser
    // ========================
    @Override
    public void createUser(CreateUserRequest request, StreamObserver<UserResponse> responseObserver) {
        log.info("gRPC CreateUser çağrıldı, name: {}", request.getName());

        // Validation
        if (request.getName().isBlank()) {
            responseObserver.onError(
                Status.INVALID_ARGUMENT
                    .withDescription("İsim boş olamaz")
                    .asRuntimeException()
            );
            return;
        }

        if (request.getEmail().isBlank()) {
            responseObserver.onError(
                Status.INVALID_ARGUMENT
                    .withDescription("Email boş olamaz")
                    .asRuntimeException()
            );
            return;
        }

        User user = userService.create(
            request.getName(),
            request.getEmail(),
            request.getPhone(),
            request.getRole().name()
        );

        UserResponse response = toProto(user);
        responseObserver.onNext(response);
        responseObserver.onCompleted();
    }

    // ========================
    // Server Streaming — StreamUsers
    // ========================
    @Override
    public void streamUsers(Empty request, StreamObserver<UserResponse> responseObserver) {
        log.info("gRPC StreamUsers çağrıldı — tüm kullanıcıları stream ediyorum");

        List<User> users = userService.findAll();

        for (User user : users) {
            // Her kullanıcıyı teker teker gönder
            responseObserver.onNext(toProto(user));

            // Gerçek senaryoda burada gecikme olabilir (DB cursor, external API)
        }

        responseObserver.onCompleted();  // Stream bitti
        log.info("StreamUsers tamamlandı, {} kullanıcı gönderildi", users.size());
    }

    // ========================
    // Client Streaming — BatchCreateUsers
    // ========================
    @Override
    public StreamObserver<CreateUserRequest> batchCreateUsers(
            StreamObserver<UserListResponse> responseObserver) {

        List<UserResponse> createdUsers = new ArrayList<>();

        return new StreamObserver<>() {
            @Override
            public void onNext(CreateUserRequest request) {
                // Client her mesaj gönderdiğinde burası çağrılır
                log.info("Batch create — kullanıcı alındı: {}", request.getName());
                User user = userService.create(
                    request.getName(), request.getEmail(),
                    request.getPhone(), request.getRole().name()
                );
                createdUsers.add(toProto(user));
            }

            @Override
            public void onError(Throwable t) {
                log.error("Client streaming hatası", t);
            }

            @Override
            public void onCompleted() {
                // Client stream'i bitirdiğinde tek bir cevap gönder
                UserListResponse response = UserListResponse.newBuilder()
                    .addAllUsers(createdUsers)
                    .setTotalCount(createdUsers.size())
                    .build();

                responseObserver.onNext(response);
                responseObserver.onCompleted();
                log.info("Batch create tamamlandı, {} kullanıcı oluşturuldu", createdUsers.size());
            }
        };
    }

    // ========================
    // Yardımcı metotlar
    // ========================

    private UserResponse toProto(User user) {
        return UserResponse.newBuilder()
            .setId(user.getId())
            .setName(user.getName())
            .setEmail(user.getEmail())
            .setPhone(user.getPhone() != null ? user.getPhone() : "")
            .setRole(mapRole(user.getRole()))
            .addAllTags(user.getTags() != null ? user.getTags() : List.of())
            .build();
    }

    private UserRole mapRole(String role) {
        try {
            return UserRole.valueOf(role);
        } catch (IllegalArgumentException e) {
            return UserRole.UNKNOWN;
        }
    }
}

Konfigürasyon

# application.yml — gRPC Server
grpc:
  server:
    port: 9090  # gRPC portu (HTTP portundan farklı!)
    # TLS — production'da zorunlu
    # security:
    #   enabled: true
    #   certificate-chain: classpath:certs/server.crt
    #   private-key: classpath:certs/server.key

⚠️ Dikkat: gRPC sunucusu HTTP sunucusundan farklı bir portta çalışır. Spring Boot'un web portu (8080) ve gRPC portu (9090) ayrıdır. Aynı uygulamada hem REST hem gRPC endpoint sunabilirsin — biri 8080'de, diğeri 9090'da.


gRPC Client Oluşturma

@GrpcClient ile Client

@Service
@Slf4j
public class UserGrpcClient {

    @GrpcClient("user-service")  // application.yml'daki isimle eşleşir
    private UserServiceGrpc.UserServiceBlockingStub userStub;

    @GrpcClient("user-service")
    private UserServiceGrpc.UserServiceStub asyncUserStub;  // Async stub

    // ========================
    // Unary — Blocking
    // ========================
    public UserResponse getUser(Long id) {
        log.info("gRPC client: GetUser çağrılıyor, id: {}", id);

        try {
            UserResponse response = userStub.getUser(
                UserRequest.newBuilder()
                    .setId(id)
                    .build()
            );
            log.info("gRPC client: Kullanıcı alındı: {}", response.getName());
            return response;

        } catch (StatusRuntimeException e) {
            if (e.getStatus().getCode() == Status.Code.NOT_FOUND) {
                log.warn("Kullanıcı bulunamadı: {}", id);
                return null;
            }
            log.error("gRPC hatası: {} - {}", e.getStatus().getCode(), e.getMessage());
            throw e;
        }
    }

    // ========================
    // Unary — Create
    // ========================
    public UserResponse createUser(String name, String email) {
        CreateUserRequest request = CreateUserRequest.newBuilder()
            .setName(name)
            .setEmail(email)
            .setRole(UserRole.USER)
            .build();

        return userStub.createUser(request);
    }

    // ========================
    // Server Streaming — Tüm kullanıcıları stream olarak al
    // ========================
    public List<UserResponse> streamAllUsers() {
        List<UserResponse> users = new ArrayList<>();
        Iterator<UserResponse> iterator = userStub.streamUsers(Empty.newBuilder().build());

        while (iterator.hasNext()) {
            UserResponse user = iterator.next();
            log.info("Stream'den kullanıcı geldi: {}", user.getName());
            users.add(user);
        }

        return users;
    }

    // ========================
    // Server Streaming — Async (non-blocking)
    // ========================
    public void streamAllUsersAsync(Consumer<UserResponse> onUser, Runnable onComplete) {
        asyncUserStub.streamUsers(
            Empty.newBuilder().build(),
            new StreamObserver<>() {
                @Override
                public void onNext(UserResponse user) {
                    onUser.accept(user);
                }

                @Override
                public void onError(Throwable t) {
                    log.error("Stream hatası", t);
                }

                @Override
                public void onCompleted() {
                    onComplete.run();
                }
            }
        );
    }
}

Client Konfigürasyonu

# application.yml — gRPC Client
grpc:
  client:
    user-service:  # @GrpcClient("user-service") ile eşleşir
      address: static://localhost:9090  # Sabit adres
      negotiation-type: plaintext       # Geliştirme ortamı — TLS yok
      # Eureka ile service discovery:
      # address: discovery:///user-service
      # Load balancing:
      # default-load-balancing-policy: round_robin

    # Birden fazla servis tanımlayabilirsin
    order-service:
      address: static://localhost:9091
      negotiation-type: plaintext

Stub Tipleri

gRPC üç farklı stub tipi sunar:

Stub TipiSınıfKullanım
Blocking StubUserServiceGrpc.UserServiceBlockingStubSenkron çağrı — thread bloklanır, cevap gelince devam eder
Async StubUserServiceGrpc.UserServiceStubAsenkron çağrı — callback ile cevap alır
Future StubUserServiceGrpc.UserServiceFutureStubListenableFuture döner — sadece unary RPC'de

Çoğu durumda blocking stub yeterlidir. Streaming RPC'lerde async stub gerekir.


Gerçek Dünya: UserService — Tam Örnek

Tüm kavramları bir araya getiren bütünleşik bir örnek. Bir order-service'in user-service'ten kullanıcı bilgisi çektiği senaryo:

User Service (gRPC Server)

// Domain model
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private Long id;
    private String name;
    private String email;
    private String phone;
    private String role;
    private List<String> tags;
}

// Service katmanı
@Service
public class UserService {

    // Basitlik için in-memory store — gerçek projede JPA/R2DBC kullan
    private final Map<Long, User> users = new ConcurrentHashMap<>();
    private final AtomicLong idGenerator = new AtomicLong(1);

    @PostConstruct
    public void init() {
        create("Ahmet Yılmaz", "ahmet@example.com", "+905551234567", "ADMIN");
        create("Ayşe Demir", "ayse@example.com", "+905559876543", "USER");
        create("Mehmet Kaya", "mehmet@example.com", "+905553456789", "MODERATOR");
    }

    public User findById(Long id) {
        return users.get(id);
    }

    public List<User> findAll() {
        return new ArrayList<>(users.values());
    }

    public User create(String name, String email, String phone, String role) {
        Long id = idGenerator.getAndIncrement();
        User user = new User(id, name, email, phone, role, List.of());
        users.put(id, user);
        return user;
    }
}

Order Service (gRPC Client + REST API)

Order service, dış dünyaya REST sunar ama user bilgisi için gRPC ile user-service'e gider:

// Order service — REST controller, gRPC client kullanır
@RestController
@RequestMapping("/api/orders")
@RequiredArgsConstructor
public class OrderController {

    private final OrderService orderService;
    private final UserGrpcClient userGrpcClient;

    @GetMapping("/{id}")
    public ResponseEntity<OrderDetailResponse> getOrder(@PathVariable Long id) {
        Order order = orderService.findById(id);
        if (order == null) {
            return ResponseEntity.notFound().build();
        }

        // User bilgisini gRPC ile al — REST çağrısından çok daha hızlı
        UserResponse user = userGrpcClient.getUser(order.getUserId());

        return ResponseEntity.ok(new OrderDetailResponse(
            order.getId(),
            order.getProduct(),
            order.getAmount(),
            user != null ? user.getName() : "Bilinmeyen",
            user != null ? user.getEmail() : ""
        ));
    }

    @GetMapping("/with-users")
    public List<OrderDetailResponse> getAllOrdersWithUsers() {
        return orderService.findAll().stream()
            .map(order -> {
                UserResponse user = userGrpcClient.getUser(order.getUserId());
                return new OrderDetailResponse(
                    order.getId(),
                    order.getProduct(),
                    order.getAmount(),
                    user != null ? user.getName() : "Bilinmeyen",
                    user != null ? user.getEmail() : ""
                );
            })
            .toList();
    }
}

public record OrderDetailResponse(
    Long orderId,
    String product,
    Double amount,
    String userName,
    String userEmail
) {}

Bu mimari çok yaygındır: dış dünya (mobil, SPA) → REST → iç servisler → gRPC → diğer servisler.


gRPC vs REST vs GraphQL — Karşılaştırma

KriterRESTgRPCGraphQL
ProtokolHTTP/1.1HTTP/2HTTP/1.1 veya 2
Veri formatıJSON (text)Protobuf (binary)JSON (text)
KontratOpenAPI (opsiyonel).proto (zorunlu)Schema (zorunlu)
PerformansOrtaÇok yüksekOrta
Payload boyutuBüyükKüçük (3-10x)İstemci belirler
StreamingZorNative (4 tip)Subscription
Tarayıcı✅ Doğal❌ gRPC-Web gerekir✅ Doğal
Kod üretimiOpsiyonelOtomatik (her dilde)Opsiyonel
Öğrenme eğrisiDüşükOrta-YüksekOrta-Yüksek
DebuggingKolay (curl, Postman)Zor (özel araç gerekir)Orta (GraphiQL)
İdeal kullanımPublic API, CRUDInternal microserviceEsnek client query
Polyglot destekHer dilde HTTP var10+ dil desteğiHer dilde HTTP var

Kısa kural:

  • REST → Dış dünyaya açık API'ler, basit CRUD, tarayıcı desteği gerekiyorsa

  • gRPC → Microservice'ler arası iç iletişim, yüksek performans, streaming

  • GraphQL → Karmaşık ilişkisel veri, farklı client'lar (web/mobil), esnek sorgulama

💡 İpucu: Bu üç teknoloji birlikte kullanılabilir. Aynı uygulamada dış dünyaya REST veya GraphQL, microservice'ler arası gRPC sunabilirsin. Spotify, Netflix, Google hepsi bu karma yaklaşımı kullanıyor.


Hata Yönetimi

gRPC, HTTP status kodları yerine kendi status code sistemini kullanır:

gRPC Status Codes

CodeKarşılıkAnlamı
OK (0)200Başarılı
CANCELLED (1)499İstemci iptal etti
INVALID_ARGUMENT (3)400Geçersiz parametre
NOT_FOUND (5)404Kaynak bulunamadı
ALREADY_EXISTS (6)409Zaten var (conflict)
PERMISSION_DENIED (7)403Yetki yok
UNAUTHENTICATED (16)401Kimlik doğrulanamadı
RESOURCE_EXHAUSTED (8)429Rate limit aşıldı
INTERNAL (13)500Sunucu hatası
UNAVAILABLE (14)503Servis erişilemez
DEADLINE_EXCEEDED (4)504Timeout
UNIMPLEMENTED (12)501Metot implement edilmemiş

Server Tarafında Hata Fırlatma

@GrpcService
public class UserGrpcService extends UserServiceGrpc.UserServiceImplBase {

    @Override
    public void getUser(UserRequest request, StreamObserver<UserResponse> responseObserver) {

        // Validation hatası
        if (request.getId() <= 0) {
            responseObserver.onError(
                Status.INVALID_ARGUMENT
                    .withDescription("ID pozitif olmalı, gelen değer: " + request.getId())
                    .asRuntimeException()
            );
            return;
        }

        User user = userService.findById(request.getId());

        // Not found hatası
        if (user == null) {
            responseObserver.onError(
                Status.NOT_FOUND
                    .withDescription("Kullanıcı bulunamadı: " + request.getId())
                    .asRuntimeException()
            );
            return;
        }

        // Metadata ile zengin hata bilgisi
        try {
            // İş mantığı...
            responseObserver.onNext(toProto(user));
            responseObserver.onCompleted();
        } catch (DatabaseException e) {
            Metadata metadata = new Metadata();
            metadata.put(
                Metadata.Key.of("error-code", Metadata.ASCII_STRING_MARSHALLER),
                "DB_CONNECTION_FAILED"
            );
            responseObserver.onError(
                Status.INTERNAL
                    .withDescription("Veritabanı hatası")
                    .withCause(e)
                    .asRuntimeException(metadata)
            );
        }
    }
}

Client Tarafında Hata Yakalama

@Service
public class UserGrpcClient {

    @GrpcClient("user-service")
    private UserServiceGrpc.UserServiceBlockingStub userStub;

    public UserResponse getUser(Long id) {
        try {
            return userStub.getUser(
                UserRequest.newBuilder().setId(id).build()
            );
        } catch (StatusRuntimeException e) {
            switch (e.getStatus().getCode()) {
                case NOT_FOUND:
                    log.warn("Kullanıcı bulunamadı: {}", id);
                    return null;

                case INVALID_ARGUMENT:
                    log.error("Geçersiz parametre: {}", e.getStatus().getDescription());
                    throw new IllegalArgumentException(e.getStatus().getDescription());

                case UNAVAILABLE:
                    log.error("User servisi erişilemez, retry yapılabilir");
                    throw new ServiceUnavailableException("User servisi down");

                case DEADLINE_EXCEEDED:
                    log.error("Timeout — user servisi çok yavaş");
                    throw new TimeoutException("gRPC deadline aşıldı");

                default:
                    log.error("Beklenmeyen gRPC hatası: {} - {}",
                        e.getStatus().getCode(), e.getStatus().getDescription());
                    throw new RuntimeException("gRPC hatası", e);
            }
        }
    }
}

Global Exception Handler (Server Interceptor)

@Component
public class GrpcExceptionInterceptor implements ServerInterceptor {

    @Override
    public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
            ServerCall<ReqT, RespT> call,
            Metadata headers,
            ServerCallHandler<ReqT, RespT> next) {

        ServerCall.Listener<ReqT> listener = next.startCall(call, headers);

        return new ForwardingServerCallListener.SimpleForwardingServerCallListener<>(listener) {
            @Override
            public void onHalfClose() {
                try {
                    super.onHalfClose();
                } catch (Exception e) {
                    Status status;
                    if (e instanceof IllegalArgumentException) {
                        status = Status.INVALID_ARGUMENT.withDescription(e.getMessage());
                    } else if (e instanceof NotFoundException) {
                        status = Status.NOT_FOUND.withDescription(e.getMessage());
                    } else {
                        status = Status.INTERNAL.withDescription("Sunucu hatası");
                    }
                    call.close(status, new Metadata());
                }
            }
        };
    }
}

Deadline ve Timeout

gRPC'de timeout yerine deadline kavramı kullanılır. Deadline, çağrının "en geç ne zaman tamamlanması gerektiğini" belirler:

// Client tarafında deadline ayarlama
UserResponse response = userStub
    .withDeadlineAfter(3, TimeUnit.SECONDS)  // 3 saniye içinde cevap gelmezse DEADLINE_EXCEEDED
    .getUser(UserRequest.newBuilder().setId(1L).build());

// Her çağrıda ayrı deadline
public UserResponse getUserWithTimeout(Long id, Duration timeout) {
    return userStub
        .withDeadlineAfter(timeout.toMillis(), TimeUnit.MILLISECONDS)
        .getUser(UserRequest.newBuilder().setId(id).build());
}
# Global deadline konfigürasyonu
grpc:
  client:
    user-service:
      address: static://localhost:9090
      negotiation-type: plaintext
      enable-keep-alive: true
      keep-alive-time: 30s
      keep-alive-timeout: 5s

⚠️ Dikkat: gRPC'de deadline propagate eder. User-service → order-service → payment-service zincirinde, ilk çağrıda 5 saniye deadline koyarsan, zincirin tamamı 5 saniye içinde tamamlanmalı. Order-service 3 saniye harcadıysa, payment-service'e kalan 2 saniye deadline ile gider.


Health Check ve Reflection

Health Check

Production'da servisin sağlığını kontrol etmek için gRPC Health Check Protocol kullanılır:

<!-- Health check desteği -->
<dependency>
    <groupId>io.grpc</groupId>
    <artifactId>grpc-services</artifactId>
    <version>${grpc.version}</version>
</dependency>
# application.yml
grpc:
  server:
    port: 9090
  health:
    enabled: true  # Health check endpoint otomatik aktif

Health check, Kubernetes liveness/readiness probe'larında kullanılır:

# Kubernetes deployment — gRPC health check
livenessProbe:
  grpc:
    port: 9090
  initialDelaySeconds: 10
  periodSeconds: 5
readinessProbe:
  grpc:
    port: 9090
  initialDelaySeconds: 5
  periodSeconds: 3

Reflection

gRPC Reflection, sunucunun hangi servisleri sunduğunu runtime'da keşfetmeyi sağlar. Swagger'ın gRPC versiyonu gibi düşünebilirsin:

# application.yml — geliştirme ortamında aç
grpc:
  server:
    reflection-service-enabled: true  # Production'da kapat!

Reflection aktifken grpcurl aracı ile servisleri keşfedebilir ve test edebilirsin:

# Servisleri listele
grpcurl -plaintext localhost:9090 list
# Output:
# com.example.grpc.UserService
# grpc.health.v1.Health
# grpc.reflection.v1alpha.ServerReflection

# Servisin metodlarını listele
grpcurl -plaintext localhost:9090 list com.example.grpc.UserService
# Output:
# com.example.grpc.UserService.GetUser
# com.example.grpc.UserService.CreateUser
# com.example.grpc.UserService.ListUsers
# com.example.grpc.UserService.StreamUsers

# Mesaj yapısını incele
grpcurl -plaintext localhost:9090 describe com.example.grpc.UserRequest
# Output:
# com.example.grpc.UserRequest is a message:
# message UserRequest {
#   int64 id = 1;
# }

# RPC çağrısı yap (curl gibi)
grpcurl -plaintext -d '{"id": 1}' localhost:9090 com.example.grpc.UserService/GetUser
# Output:
# {
#   "id": "1",
#   "name": "Ahmet Yılmaz",
#   "email": "ahmet@example.com",
#   "role": "ADMIN"
# }

grpcurl kurulumu:

# macOS
brew install grpcurl

# Linux
go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest

# Veya GitHub releases'ten binary indir

Geliştirme ortamında grpcurl, gRPC API'ni test etmek için vazgeçilmez bir araçtır. REST'te Postman/curl ne ise, gRPC'de grpcurl odur.


Ne Zaman gRPC Kullanmalı?

gRPC İçin Uygun Senaryolar

  1. Internal microservice iletişimi — Servisler birbirleriyle konuşurken. Tarayıcı desteği gerekmez, performans kritik

  2. Yüksek performans gereksinimi — Protobuf binary serileştirme, JSON'dan 3-10x daha küçük ve hızlı

  3. Streaming senaryoları — Real-time veri akışı, canlı güncelleme, event streaming

  4. Polyglot ortam — Java servisi Python servisiyle konuşacaksa. Proto dosyası her iki dilde de kod üretir, tip uyumluluğu garanti

  5. Strict API kontratı.proto dosyası sözleşmedir. İki ekip aynı proto'yu paylaşır, breaking change compile-time'da yakalanır

  6. Düşük latency — HTTP/2 multiplexing, header compression, persistent connection

gRPC İçin Uygun OLMAYAN Senaryolar

  1. Public-facing API — Tarayıcılar gRPC'yi doğrudan desteklemez. Mobil uygulamalar için de REST daha yaygın

  2. Basit CRUD — 5 endpoint'lik bir API için gRPC overkill. Proto dosyası, kod üretimi, araç kurulumu...

  3. Human-readable debugging gereksinimi — Protobuf binary formatı, curl ile test edemezsin. JSON ise herkesin okuyabildiği metin

  4. Ekipte gRPC deneyimi yoksa — Öğrenme eğrisi var. Proto dosyası, stub'lar, streaming kavramları yeni olabilir

  5. Üçüncü parti entegrasyon — Dış firmalara gRPC endpoint sunmak zor. REST universal standarttır

Hibrit Mimari (En Yaygın Yaklaşım)

                    ┌─────────────┐
 Mobil/Web (REST) ──│ API Gateway │
                    └──────┬──────┘
                           │ gRPC
              ┌────────────┼────────────┐
              │            │            │
        ┌─────┴─────┐ ┌───┴────┐ ┌────┴─────┐
        │User Service│ │Order   │ │Payment   │
        │            │ │Service │ │Service   │
        └────────────┘ └────────┘ └──────────┘
              ↕ gRPC        ↕ gRPC
        ┌────────────┐ ┌────────────┐
        │Notification│ │Inventory   │
        │Service     │ │Service     │
        └────────────┘ └────────────┘

API Gateway dış dünyaya REST sunar, arkada tüm servisler gRPC ile konuşur. Bu pattern'i Netflix, Spotify, Google ve birçok büyük şirket kullanır.


Özet

  • gRPC, Protocol Buffers (binary) ve HTTP/2 kullanan yüksek performanslı bir RPC framework'üdür — REST'in JSON/HTTP 1.1 yaklaşımından 2-10x daha hızlı ve kompakt veri transferi sağlar

  • `.proto` dosyası servisler arasındaki sözleşmedir — mesaj yapıları ve servis metodları burada tanımlanır, protobuf-maven-plugin ile Java kodu otomatik üretilir

  • Dört iletişim tipi vardır: Unary (tek istek-tek cevap), Server Streaming (sunucu sürekli gönderir), Client Streaming (istemci sürekli gönderir), Bidirectional Streaming (iki taraf da aynı anda gönderir)

  • Spring Boot entegrasyonu grpc-spring-boot-starter ile kolay yapılır — @GrpcService ile server, @GrpcClient ile client oluşturulur. gRPC ve REST aynı uygulamada farklı portlarda birlikte çalışabilir

  • Hata yönetimi gRPC status code'ları ile yapılır (NOT_FOUND, INVALID_ARGUMENT, INTERNAL...) — StatusRuntimeException fırlatılır ve yakalanır. Deadline kavramı zincirdeki tüm servisler arasında propagate eder

  • gRPC, REST'in yerini almaz — dış dünyaya REST veya GraphQL, microservice'ler arası iletişimde gRPC kullan. Bu hibrit yaklaşım sektörde en yaygın mimari pattern'dir