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?
| Özellik | REST (JSON/HTTP 1.1) | gRPC (Protobuf/HTTP 2) |
|---|---|---|
| Veri formatı | JSON (text, şişkin) | Protobuf (binary, kompakt) |
| Boyut | 100 byte JSON mesaj | ~30 byte protobuf (3x küçük) |
| Hız | Serialize/deserialize yavaş | Binary, çok hızlı |
| Kontrat | Swagger/OpenAPI (opsiyonel) | .proto dosyası (zorunlu) |
| Streaming | Zor (WebSocket ayrı) | Native (4 tip streaming) |
| Kod üretimi | Manuel veya codegen | Otomatik (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
| Protobuf | Java | Açıklama |
|---|---|---|
int32 | int | 32-bit tamsayı |
int64 | long | 64-bit tamsayı |
float | float | 32-bit kayan nokta |
double | double | 64-bit kayan nokta |
bool | boolean | Boolean |
string | String | UTF-8 string |
bytes | ByteString | Binary veri |
repeated T | List<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]── Server2. 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]──── Server3. 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]── Server4. 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]── ServerSpring 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.xmlKod Ü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: plaintextStub Tipleri
gRPC üç farklı stub tipi sunar:
| Stub Tipi | Sınıf | Kullanım |
|---|---|---|
| Blocking Stub | UserServiceGrpc.UserServiceBlockingStub | Senkron çağrı — thread bloklanır, cevap gelince devam eder |
| Async Stub | UserServiceGrpc.UserServiceStub | Asenkron çağrı — callback ile cevap alır |
| Future Stub | UserServiceGrpc.UserServiceFutureStub | ListenableFuture 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
| Kriter | REST | gRPC | GraphQL |
|---|---|---|---|
| Protokol | HTTP/1.1 | HTTP/2 | HTTP/1.1 veya 2 |
| Veri formatı | JSON (text) | Protobuf (binary) | JSON (text) |
| Kontrat | OpenAPI (opsiyonel) | .proto (zorunlu) | Schema (zorunlu) |
| Performans | Orta | Çok yüksek | Orta |
| Payload boyutu | Büyük | Küçük (3-10x) | İstemci belirler |
| Streaming | Zor | Native (4 tip) | Subscription |
| Tarayıcı | ✅ Doğal | ❌ gRPC-Web gerekir | ✅ Doğal |
| Kod üretimi | Opsiyonel | Otomatik (her dilde) | Opsiyonel |
| Öğrenme eğrisi | Düşük | Orta-Yüksek | Orta-Yüksek |
| Debugging | Kolay (curl, Postman) | Zor (özel araç gerekir) | Orta (GraphiQL) |
| İdeal kullanım | Public API, CRUD | Internal microservice | Esnek client query |
| Polyglot destek | Her dilde HTTP var | 10+ dil desteği | Her 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
| Code | Karşılık | Anlamı |
|---|---|---|
OK (0) | 200 | Başarılı |
CANCELLED (1) | 499 | İstemci iptal etti |
INVALID_ARGUMENT (3) | 400 | Geçersiz parametre |
NOT_FOUND (5) | 404 | Kaynak bulunamadı |
ALREADY_EXISTS (6) | 409 | Zaten var (conflict) |
PERMISSION_DENIED (7) | 403 | Yetki yok |
UNAUTHENTICATED (16) | 401 | Kimlik doğrulanamadı |
RESOURCE_EXHAUSTED (8) | 429 | Rate limit aşıldı |
INTERNAL (13) | 500 | Sunucu hatası |
UNAVAILABLE (14) | 503 | Servis erişilemez |
DEADLINE_EXCEEDED (4) | 504 | Timeout |
UNIMPLEMENTED (12) | 501 | Metot 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 aktifHealth 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: 3Reflection
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 indirGeliş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
Internal microservice iletişimi — Servisler birbirleriyle konuşurken. Tarayıcı desteği gerekmez, performans kritik
Yüksek performans gereksinimi — Protobuf binary serileştirme, JSON'dan 3-10x daha küçük ve hızlı
Streaming senaryoları — Real-time veri akışı, canlı güncelleme, event streaming
Polyglot ortam — Java servisi Python servisiyle konuşacaksa. Proto dosyası her iki dilde de kod üretir, tip uyumluluğu garanti
Strict API kontratı —
.protodosyası sözleşmedir. İki ekip aynı proto'yu paylaşır, breaking change compile-time'da yakalanırDüşük latency — HTTP/2 multiplexing, header compression, persistent connection
gRPC İçin Uygun OLMAYAN Senaryolar
Public-facing API — Tarayıcılar gRPC'yi doğrudan desteklemez. Mobil uygulamalar için de REST daha yaygın
Basit CRUD — 5 endpoint'lik bir API için gRPC overkill. Proto dosyası, kod üretimi, araç kurulumu...
Human-readable debugging gereksinimi — Protobuf binary formatı, curl ile test edemezsin. JSON ise herkesin okuyabildiği metin
Ekipte gRPC deneyimi yoksa — Öğrenme eğrisi var. Proto dosyası, stub'lar, streaming kavramları yeni olabilir
Üçü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-pluginile Java kodu otomatik üretilirDö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-starterile kolay yapılır —@GrpcServiceile server,@GrpcClientile client oluşturulur. gRPC ve REST aynı uygulamada farklı portlarda birlikte çalışabilirHata yönetimi gRPC status code'ları ile yapılır (NOT_FOUND, INVALID_ARGUMENT, INTERNAL...) —
StatusRuntimeExceptionfırlatılır ve yakalanır. Deadline kavramı zincirdeki tüm servisler arasında propagate edergRPC, 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
AI Asistan
Sorularını yanıtlamaya hazır