GRPC - Go ve Java ile Örnek Uygulama

Niyazi Ekinci
Pia-Team Tech
Published in
6 min readApr 26, 2021

Selamlar, bu yazımızda GRPC, Java ve Go ile örnek bir uygulama yapımına değineceğiz.

GRPC, Go ve Java ile Örnek Uygulama

GRPC Google tarafından 2015–16 yıllarında open source olarak geliştirilen ve CNCF’e (Cloud Native Computing Foundation) bağışlanan bir RPC frameworküdür. CNCF’de mezunluğa aday projelerden birisidir. (https://www.cncf.io/projects/)

Uygulamaya geçmeden önce Protocol Buffers ve GRPC’yi kısaca açıklamak gerekirse;

Protocol Buffers

Protocol Buffers, Interface Definition Language (IDL) ve serialization kütüphanesidir. Bu arayüz tanımlama dili ile mesajlarımızı ve servislerimizi tanımlayabiliyoruz. Daha sonra bu tanımladığımız servisler ve mesajları desteklenen diller için otomatik olarak üretebiliyoruz.

Protocol Buffers, (kısaca protobuf denilmekte) verileri serileştirmek ve deserileştirmek için JSON ve XML gibi yapısal veri türlerinin yerine binary tipini kullanan, mesajları binary’e dönüştürme protokolüdür. Yani verimli veri iletişimi, verilerin insan tarafından daha kolay bir şekilde okunabilmesine tercih edilmiştir. Bunun nedenini düşündüğümüzde aslında Google tarafından geliştirildiği için Google servislerini örnek verecek olursak saniyede milyonlarca verinin işlendiği bir ortamda JSON gibi field’ların isimlerinin bile byte’larca yer kapladığı bir serialization yapmak servislerde yavaşlamalara neden olabilir.

GRPC

Protocol Buffers’dan kısaca bahsettiğimize göre GRPC’ye geçebiliriz. GRPC HTTP2 üstüne geliştirilmiş olan bir Remote Call Procedure (RPC)’dir. Yani bir client bir servisteki kodu sanki kendi code base’indeymiş gibi çağırabilir. Contract’lara bağlı kod geliştirme söz konusudur. Önce arayüzlerimizi yazdıktan sonra ilgili dil için gerekli kodu üretiriz daha sonra üretilen fonksiyonumuzun içini doldururuz. GRPC unary, server stream, client stream ve bi-directional (bi-di) stream’i desteklemektedir. Sonuç olarak GRPC ile birlikte tek yönlü stream veya çift yönlü stream yapabilmekteyiz. Unary’i açıklamak gerekirse de kısaca request response modeli diyebiliriz.

REST’e göre avantajları ve dezavantajları mevcuttur. Kısaca farklarını inceleyecek olursak;

REST genellikle HTTP1.1 üstüne kurulmuş; GRPC ise HTTP2 üstüne kurulmuştur. HTTP1.1 ve HTTP2 arasındaki farklardan birine küçük bir örnek vermek gerekirse HTTP2'de tek bir istekte birden fazla dosyayı alabilmekteyiz. HTTP1.1'de ise her bir dosya için ayrı ayrı istek atıp TCP Handshake aşamalarından tek tek geçmemiz gerekiyor bu da bize network maliyeti olarak geri dönüyor.

GRPC’de contract first yaklaşımı mevcuttur. Önce arayüzleri tanımlamamız gerekiyor. Arayüzleri tanımladıktan sonra bu arayüzleri kullanarak kod üretebiliyoruz. İlgili dil için kodu ürettikten sonra kullanabiliyoruz. Örneğin bir yazılım geliştiricisi olarak yazdığımız bir servisteki bilgileri diğer servisten çağırmamız gerekiyor olsun ve servislerden biri Java diğeri .NET ile yazılmış olsun. Database entity’lerimizin tüm alanlarını paylaşmak istemeyiz bu durumda REST kullandığımızda Data Transfer Object’leri yazarız. Yani hem .NET servisinde hem de Java servisinde DTO yazmak durumunda kalmış oluruz. GRPC kullandığımızda ise herşey protofile’larda tanımlı olduğu için kodları proto compiler ile ürettiğimizde iki servis içinde buna sahip olacaktık. Servislerimiz eğer aynı dilde yazılsaydı bunu çözmek için DTO’larımızı common package altında tanımlamayı tercih edebilirdik ama bu da bize getirisinin yanında dil bağımlılığı olarak geri dönecekti.

REST’te endpointler mevcuttur. GRPC’de ise uzaktan fonksiyon çağrımı vardır. REST’te eğer OpenAPI kullanılmaz ise ve iki geliştirici aynı anda farklı servisleri geliştiriyorsa geliştiriciler birbiriyle iletişimde kalmak durumundadır. Servisi tüketecek olan diğer servis, istek atacağı kaynağın ayrıntılarına (url, metod, request payload ve response payload gibi) hakim olmalıdır ve ayrıca değişiklik durumunda da haberdar olmak zorundadır. Ayrıca herkese açık bir REST kaynağımız varsa değişiklik durumunda API’mizi versiyonlamak zorundayız. GRPC’de ise her şey önceden belgelendiği için geliştiriciler belgelenen arayüze uyarlar ve geliştiriciler arasında iletişim problemleri daha aza indirgenmiş olur.

Olaya internet tarayıcıları ve javascript tarafından bakacak olursak; Javascript native olarak JSON’u desteklemekte ve REST ile uyumlu çalışmakta REST bu kullanım senaryosunda güzel olabilir. Yine bunun yanında dış dünya ile iletişimde isek yani public bir API’mız var ise human-readable bir API yani JSON ile REST daha iyi bir fikir olabilir. GRPC ile yazılmış olan servislerimizin önüne API Gateway gibi bir katman koyarak browserların hizmetine verebilme seçeneğine de sahibiz.
Grpc’nin de browserlar için olan GRPC Web sürümü de var dilerseniz buradan ulaşabilirsiniz. GRPC daha çok mikro servisler arasındaki iletişimimiz için veya Android, IOS ve IOT uygulamalarımız için oldukça faydalıdır. Çünkü 4G ile internete bağlanan bir akıllı telefonda veri alış verişini minimize etmek kullanıcı açısından kota dostu bir uygulama olacaktır.

Örnek Uygulama

Şimdi sunucusunu java ile istemcisini ise golang ile yazacağımız bir örneği inceleyelim. Java kullanırken Spring Boot veya Quarkus ile de yazabilirdik. Ama bu örnekte direkt olarak java ile yazmayı tercih ettik.

syntax = "proto3";
option java_multiple_files = true;
option go_package = "user/";
option java_package = "com.example.grpc";

message UserCreateRequest{
string name = 1;
string lastname = 2;
int32 age = 3;
}

message UserCreateResponse{
string message = 1;
bool ok = 2;
}

service UserService{
rpc create(UserCreateRequest) returns(UserCreateResponse);
}

UserService.proto isimli dosyanın içerisine yukarıdaki tanımlamaları yapalım. Kısaca; Yukarıda UserService adında bir servis tanımladık. Bu servis UserCreateRequest tipinde mesaj alan ve UserCreateResponse tipinde bir mesaj dönen create adında unary rpc methoduna sahiptir. UserCreateRequest mesajının içinde name , lastname , age bilgilerini gönderiyoruz ve UserCreateResponse mesajının içinde message ve ok bilgilerini geri alıyoruz. IDL’in versiyonları olduğu için en üst satırda syntax’ımızın proto3 olması gerektiğini belirttik.
(Protocol buffers ve syntax hakkında daha detaylı bilgiye sahip olmak için bu link üzerinden ulaşabilirsiniz)

Tanımlamaları yaptıktan sonra sunucumuzu yazmak için boş bir maven projesi oluşturalım. pom.xml dosyasına aşağıdaki dependencyleri eklememiz gerekiyor.

<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.37.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.37.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.37.0</version>
</dependency>
<dependency> <!-- necessary for Java 9+ -->
<groupId>org.apache.tomcat</groupId>
<artifactId>annotations-api</artifactId>
<version>6.0.53</version>
<scope>provided</scope>
</dependency>

Yukarıda yazmış olduğumuz proto dosyasını derlemek için yine pom.xml dosyasına aşağıdaki pluginleri ekleyelim.

<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.6.2</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:3.12.0:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.37.0:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

Daha sonra mvn clean install yaparak dependencylerin yüklenmesini bekleyelim. Dependencyler yüklendikten sonra src/main/proto klasörünün altına UserService.proto dosyamızı kopyalayalım. Yani dosya yapımız aşağıdaki gibi olmalıdır.

Proto klasörü

Bütün bunları yaptıktan sonra terminal üzerinden mvn compile komutunu çalıştıralım. Bu komutu çalıştırdıktan sonra proto compiler yazmış olduğumuz UserService.proto dosyasını okuyarak bizim için implemente edilmeyi bekleyen classlarımızı oluşturacaktır. İlgili classları oluşan target klasörünün altında görebilirsiniz. Daha sonra java klasörünün içine UserServiceImpl class’ını ekleyelim. Ardından aşağıdaki gibi düzenlemeleri yapalım.

Otomatik olarak ürettiğimiz class’ı UserServiceImpl class’ında extends ediyoruz. Daha sonra create metodunu override ediyoruz ve içini dolduruyoruz. Proto dosyasında tanımladığımız gibi UserCreateResponse nesnesini oluşturuyoruz. Üretilen kod bizim için builder patterni de uyguluyor. Daha sonra responseObserver.onNext(response) ve responseObserver.onCompleted() kodlarını yazarak response’un client’a gönderilmesini sağlıyoruz. Şimdi java klasörünün içine GrpcServer adında bir class daha oluşturalım. Ve aşağıdaki düzenlemeleri yapalım.

Bu yazdığımız class’da kısaca 9090 portunda bir uygulama çalıştırmasını ve requestleri kabul etmesi gerektiğini belirttik. Şimdi Golang ile client yazımına geçebiliriz.

Öncelikle proto compiler yüklememiz gerekiyor. Ben homebrew üzerinden yükledim siz dilerseniz bu link üzerinden yükleyebilirsiniz. Proto-compiler yükledikten sonra bir tane go mod projesi oluşturalım. go.mod dosyasının içini aşağıdaki gibi dolduralım.

module grpc.example.com/user-client

go 1.16

require (
github.com/golang/protobuf v1.5.2 // indirect
golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6 // indirect
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7 // indirect
google.golang.org/genproto v0.0.0-20210423144448-3a41ef94ed2b // indirect
google.golang.org/grpc v1.37.0 // indirect
google.golang.org/protobuf v1.26.0 // indirect
)

Daha sonra terminalden go mod download komutunu çalıştıralım. UserService.proto dosyasını ana dizinimize kopyalayalım. Bu işlemden sonra protoc --go_out=plugins=grpc:. UserService.proto komutunu çalıştıralım. user klasörü altında bizim için kodun üretildiğini görebiliriz. Daha sonra main.go adında bir dosya oluşturalım ve içini aşağıdaki gibi dolduralım.

package main

import (
"context"
"fmt"
"google.golang.org/grpc"
"grpc.example.com/user-client/user"
"log"
)

func main() {
conn, err := grpc.Dial("localhost:9090", grpc.WithInsecure())
if err != nil{
log.Fatalf("Hata oluştu: %s", err)
}
defer conn.Close()
userService := user.NewUserServiceClient(conn)
resp, err := userService.Create(context.Background(), &user.UserCreateRequest{
Name: "Foo",
Lastname: "Bar",
Age: 23,
})

if err != nil {
log.Printf("Bir sorun oluştu: %s", err)
}
fmt.Print(resp)

}

Her şey düzgün çalıştığında ekranda message:”Kullanıcı oluşturuldu: Foo Bar” ok:true şeklinde bir çıktı olması gerekir. Burada öncelikle localhost:9090 üstünde çalışan servera bağlantı kuruyoruz ve userService.Create fonksiyonunu sanki kendi ortamımızdaymış gibi çağırıyoruz. Aslında Java tarafında yazmış olduğumuz kod çalışmış oldu.

Özetle; GRPC kullanım senaryolarına bağlı olarak REST’e güzel bir alternatiftir tabi günün sonunda her uygulamanın kendi ihtiyacı ve gereklilikleri vardır. Bu gereklilikler geliştirici ekibi, müşteri kitlesi, uygulama türü, projenin yetişmesi gereken zaman ve bilgi birikimi gibi daha bir sürü etkenlerle tartılarak belirlenmelidir.

GRPC ve Protocol Buffers’a benzer olarak ise Apache Thrift kullanımı söz konusu olabilir. Apache Thrift ise Facebook tarafından geliştirilip Apache Foundation’a bağışlanmıştır. (bkz)

Şimdilik GRPC’yi biraz olsun anlamak için yazdığımız bu yazımızın sonuna gelmiş bulunmaktayız.

Vakit ayırıp okuduğunuz için teşekkürler.

--

--