Teknoloji baş döndüren bir hızla ilerlerken beynimizin tembelleştiğini de kabul etmemiz gerekiyor. Artık pek çok işimiz otonomlaştırıldığından zihnimiz eski egzersizleri yapmıyor. Yıllar önce İngiltere'de yapılan bir araştırmada çocukların hesap makinesi kullanması sebebiyle temel dört işlem matematiğinde sorunlar yaşadığı tespit edilmişti. Yine Kanada'da yapılan bir araştırma insanların dikkat dağılma sürelerinin 8 saniyelere kadar indiğini gösterdi. Hafızamızı dinç tutma noktasında Japon balıkları ile yarışır bir konumda olduğumuz da aşikar. Kaçımız aklından ezbere 4 telefon numarasını sayabilir(Üç haneliler yasak) Otonomlaşan dünya sebebiyle tembelleşen ve dış uyarıcılar yüzünden sürekli dikkati dağılan zihnimiz...Gerçekten de dikkatimizi dağıtan, odaklanmamızı bozan o kadar çok şey var ki. Dolayısıyla kendimizi yetiştirmek istediğimiz konulara çalışırken ne kadar verimli olabiliyoruz bir bakmak gerekiyor. Tekrar satın alınamayacak olan zamanın ne kadar kıymetli olduğunu düşünürsek verimli çalışmanın ilerleyen yaşlarda çok çok önemli bir mesele haline geldiğini vurgulamak isterim.
İşte bu sebepten birkaç haftadır
Saturday-Night-Works çalışmalarım sırasında pomodoro tekniğini neden kullanmadığımı karar kara düşünmekteyim. Oysa ki çok verimli bir çalışma pratiği. Atladığım bu önemli detayı SkyNet çalışmalarımın ilk gününden itibaren uygulamaya karar verdim. Genellikle gece 22:00 sularında başlayarak 4X25 dakikalık seanslar halinde ilerliyorum. Her seans arasında 5er dakikalık standart molalar var. Tabii bu tekniği uygularken en önemli kural çalışmayı bölecek unsurları mutlak suretle dışarıda bırakmak. Cep/ev telefonu, televizyon, radyo, e-mail programı ve benzeri odak dağıtıcı ne kadar şey varsa kapatmak gerekiyor. Bunun faydasını epeyce gördüğümü ve 25 dakikalık zaman dilimlerindeki çalışmalardan iyi seviyede verim aldığımı ifade edebilirim. Aranızda uygulamayanlar varsa bir göz atsınlar derim ;)
Pomodoro tekniğini uygularken size akıllı bir kronometre gerekecek. Tarayıcıda çalışan
Tomato-Timer tam size göre. Hatta Visual Studio Code için eklentisi bile var ;)
Gelelim SkyNet'te geçirdiğim ikinci güne. Elimizdeki malzemeleri sayalım. MongoDB için bir docker imajı, gRPC ve GoLang. Bu üçünü kullanarak CRUD(Create Read Update Delete) operasyonlarını icra eden basit bir uygulama geliştirmek niyetindeyim. Bir önceki öğretide Redis docker container'dan yararlanmıştım. Kaynakları kıymetli olan Ahch-To sistemini kirletmemek adına MongoDB için de benzer şekilde hareket edeceğim. Açıkçası GoLang bilgim epey paslanmış durumda ve sistemde yüklü olup olmadığını dahi bilmiyorum.
go version
terminal komutu da bana yüklü olmadığını söylüyor. Dolayısıyla ilk adım onu MacOS üzerine kurmak.
İlk Hazırlıklar(Go Kurulumu ve MongoDB)
GoLang'i Ahch-To adasına yüklemek için
şu adrese gidip Apple macOS sürümünü indirmem gerekti. Ben öğretiyi hazırlarken go1.13.4.darwin-amd64.pkg dosyasını kullandım. Kurulum işlemini tamamladıktan sonra komut satırından go versiyonunu sorgulattım ve aşağıdaki çıktıyı elde ettim.
Pek tabii içim rahat değildi. Versiyon bilgisi gelmişti ama bir "hello world" uygulamasını çalışır halde görmeliydim ki kurulumun sorunsuz olduğundan emin olayım. Hemen resmi dokümanı takip ederek $HOME\go\src\ altında helloworld isimli bir klasör açıp aşağıdaki kod parçasını içeren helloworld.go dosyasını oluşturdum(Visual Studio Code kullandığım için editörün önerdiği go ile ilgili extension'ları yüklemeyi de ihmal etmedim)
package main
import "fmt"
func main() {
fmt.Printf("Artık go çalışmaya hazırım :) \n")
}
Terminalden aşağıdaki komutları işlettikten sonra çıktıyı görebildim.
go build
./helloworld
Go ile kod yazabildiğime göre MongoDB docker imajını indirip bir deneme turuna çıkabilirim. İşte terminal komutları.
docker pull mongo
docker run -d -p 27017-27019:27017-27019 --name gondor mongo
docker container ps -a
docker exec -it gondor bash
mongo
show dbs
use AdventureWorks
db.category.save({title:"Book"})
db.category.save({title:"Movie"})
db.category.find().pretty()
exit
exit
İlk komutla mongo imajı çekiliyor. İzleyen komut docker container'ını varsayılan portları ile sistemin kullanımına açmak için. Container listesinde göründüğüne göre sorun yok. MongoDB veritabanını container üzerinden test etmek amacıyla içine girmek lazım. 4ncü komutu bu işe yarıyor. Ardından mongo shell'e geçip bir kaç işlem gerçekleştirilebilir. Önce var olan veritabanlarını listeliyor sonra AdventureWorks isimli yeni bir tane oluşturuyoruz. Devam eden kısımda category isimli bir koleksiyona iki doküman ekleniyor ve tümünü güzel bir formatta listeliyoruz. Arka arkaya gelen iki exit komutunu fark etmişsinizdir. İlki mongo shell'den, ikincisi de container içinden çıkmak için.
Ah çok önemli bir detayı unuttum! Örnekte gRPC protokolünü kullanacağız. Bu da bir proto dosyamız olacağı ve Golang için gerekli stub içeriğine derleyeceğimiz anlamına geliyor. Dolayısıyla sistemde protobuf ve go için gerekli derleyici eklentisine ihtiyacım var. brew ile bunları sisteme yüklemek oldukça kolay.
brew install protobuf
protoc --version
brew install protoc-gen-go
Kod tarafına geçmeye hazırız ama öncesinde ufak bir bilgi.
gRPC Hakkında Azıcık Bilgi
gRPC, HTTP2 bazlı modern bir iletişim protokolü ve JSON yerine ProtoBuffers olarak isimlendirilen kuvvetle türlendirilmiş bir ikili veri formatını kullanmakta(strongly-typed binary data format) JSON özellikle REST tabanlı servislerde popüler bir format olmasına rağmen serileştirme sırasında CPU'yu yoran bir performans sergiliyor. HTTP/2 özelliklerini iyi kullanan gRPC ise 5 ile 25 kata kadar daha hızlı. Bu noktada hatırlamak için bile olsa gRPC ile REST'i kıyaslamakta yarar var. İşte karşılaştırma tablosu.
REST Tarafı |
gRPC Tarafı |
HTTP 1.1 nedeniyle gecikme yüksek |
HTTP/2 sebebiyle daha düşük gecikme |
Sadece Request/Response |
Stream desteği(Örneğimizde bir kullanımı var) |
CRUD odaklı servisler için |
API odaklı(Burada CRUD odaklı yapacağız çaktırmayın) |
HTTP Get,Post,Put,Delete gibi fiil tabanlı |
RPC tabanlı, sunucu üzerinden fonksiyon çağırabilme özelliği |
Sadece Client->Server yönlü talepler |
Çift yönlü ve asenkron iletişim |
JSON kullanıyor(serileşme yavaş, boyut büyük) |
Protobuffer kullanıyor(veri daha küçük boyutta ve serileşme hızlı) |
Örnek Uygulama
Gelelim kod tarafına... Uygulamanın temel klasör yapısını aşağıdaki gibi oluşturabiliriz. Ben bu işlemleri $HOME\go\src\ altında gerçekleştirdim.
mkdir gRPC-sample
cd gRPC-sample
mkdir playerserver
mkdir clientapp
mkdir proto
playerserver ve clientapp tahmin edileceği üzere sunucu ve istemci uygulama görevini üstleniyorlar. proto klasöründe yer alan player.proto, gRPC mesaj sözleşmesine ait tanımlamaları içermekte. Servis metodları, parametre tipleri ve içerikleri bu dosyada aşağıdaki gibi bildiriliyor.
syntax="proto3"; //protobuffers v3 versiyonu kullaniliyor
package player; // proto paketinin adi
option go_package="playerpb"; // generate edilecek go paketinin adi
// Player mesaj tipinin tanimi
message Player{
string id=1;
string player_id=2;
string fullname=3;
string position=4;
string bio=5;
}
// Operasyonlarin kullandigi request ve response mesajlarina ait tanimlamalar
message AddPlayerReq{
Player plyr=1;
}
message AddPlayerRes{
Player plyr=1;
}
message EditPlayerReq{
Player plyr=1;
}
message EditPlayerRes{
Player plyr=1;
}
message RemovePlayerReq{
string player_id=1;
}
message RemovePlayerRes{
bool removed=1;
}
message GetPlayerReq{
string player_id=1;
}
message GetPlayerRes{
Player plyr=1;
}
message GetPlayerListReq{}
message GetPlayerListRes{
Player plyr=1;
}
// servis ve operasyon tanimlari
service PlayerService{
rpc GetPlayer(GetPlayerReq) returns (GetPlayerRes);
rpc GetPlayerList(GetPlayerListReq) returns (stream GetPlayerListRes); //server bazlı streaming kullanacağımız için dönüş parametresi stream tipinden
rpc AddPlayer(AddPlayerReq) returns (AddPlayerRes);
rpc EditPlayer(EditPlayerReq) returns (EditPlayerRes);
rpc RemovePlayer(RemovePlayerReq) returns (RemovePlayerRes);
}
Bu içeriği Go tarafında kullanabilmek için derlememiz lazım. Derlemeyi aşağıdaki terminal komutu ile gerçekleştirebiliriz(proto dosyasını VS Code tarafında daha kolay düzenlemek için vscode-proto3 isimli extension'ı kullandım)
protoc player.proto --go_out=plugins=grpc:.
Proto dosyasının tamamlanmasını takiben playerserver klasöründeki main.go dosyasını yazmaya başlayabiliriz. Biraz uzun bir kod dosyası ama sabırla yazıp, yorum satırlarını da okuyarak neler yaptığımızı anlamaya çalışmakta yarar var.
package main
import (
"context"
"fmt"
"net"
"os"
"os/signal"
"strings"
"go.mongodb.org/mongo-driver/bson/primitive"
playerpb "gRPC-sample/proto"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
/* proto'dan otomatik üretilen player.pb.go içerisindeki RegisterPlayerServiceServer metoduna bir bakın.
Pointer olarak gelen grpc server nesnesi ikinci parametre olarak gelen tipi register etmek için kullanılır.
Bir nevi interface üzerinden enjekte işlemi yaptığımızı düşünebilir miyiz?
*/
type PlayerServiceServer struct{}
var db *mongo.Client
var playerCollection *mongo.Collection
var mongoContext context.Context
func main() {
// TCP üzerinden 5555 nolu portu dinleyecek olan nesne oluşturuluyor
server, err := net.Listen("tcp", ":5555")
// Olası bir hata durumunu kontrol ediyoruz
if err != nil {
fmt.Printf("5555 dinlenemiyor: %v", err)
}
// gPRC sunucusu için kayıt(register) işlemleri
grpcOptions := []grpc.ServerOption{}
// yeni bir grpc server oluşturulur
grpcServer := grpc.NewServer(grpcOptions...)
// Bir PlayerService tipi oluşturulur
playerServiceType := &PlayerServiceServer{}
// servis sunucu ile birlikte kayıt edilir
playerpb.RegisterPlayerServiceServer(grpcServer, playerServiceType)
// mongoDB bağlantı işlemleri
fmt.Println("MongoDB sunucusuna bağlanılıyor")
mongoContext = context.Background()
// bağlantı deneniyor
db, err = mongo.Connect(mongoContext, options.Client().ApplyURI("mongodb://localhost:27017"))
// olası bir bağlantı hatası varsa
if err != nil {
fmt.Println(err)
}
// Klasik ping metodunu çağırıyoruz
err = db.Ping(mongoContext, nil)
if err != nil {
fmt.Println(err)
} else {
// çağrı başarılı olursa bağlandık demektir
fmt.Println("MongoDB ile bağlantı sağlandı")
}
// nba isimli veritabanındaki player koleksiyonuna ait bir nesne örnekliyoruz
// veritabanı ve koleksiyon yoksa oluşturulacaktır
playerCollection = db.Database("nba").Collection("player")
// gRPC sunucusunu aktif olan TCP sunucusu içerisinde bir child routine olarak başlatıyoruz
go func() {
if err := grpcServer.Serve(server); err != nil {
fmt.Println(err)
}
}()
fmt.Println("Sunucu 5555 nolu porttan gPRC tabanlı iletişime hazır.\nDurdurmak için CTRL+C.")
// CTRL+C ile başlayan kapatma operasyonu
cnl := make(chan os.Signal) // işletim sisteminde sinyal alabilmek için bir kanal oluşturduk
signal.Notify(cnl, os.Interrupt) // CTRL+C mesajı gelene kadar ana rutin açık kalacak
<-cnl
fmt.Println("Sunucu kapatılıyor...")
grpcServer.Stop() // gRPC sunucusunu durdur
server.Close() // TCP dinleyicisini kapat
fmt.Println("GoodBye Crow")
}
/* Protobuf mesajlarında taşınan serileşmiş içeriği nesnel olarak ele alacağımı struct */
type Player struct {
ID primitive.ObjectID `bson:"_id,omitempty"` // MongoDB tarafındaki ObjectId değerini taşır
PlayerID string `bson:"player_id"`
Fullname string `bson:"fullname"`
Position string `bson:"position"`
Bio string `bson:"bio"`
}
/* PlayerServiceServer'ın uygulanması gereken metodlarını. Yani servis sözleşmesinin tüm operasyonları
*/
// Yeni bir oyuncu eklemek için kullanacağımız fonksiyon
func (srv *PlayerServiceServer) AddPlayer(ctx context.Context, req *playerpb.AddPlayerReq) (*playerpb.AddPlayerRes, error) {
payload := req.GetPlyr() // GetPlyr (GetPlayer değil o servis metodumuz) fonksiyonu ile request üzerinden gelen player içeriği çekilir
// İçerik ile gelen alan değerleri player struct nesnesini oluşturmak için kullanılır
player := Player{
PlayerID: payload.GetPlayerId(),
Fullname: payload.GetFullname(),
Position: payload.GetPosition(),
Bio: payload.GetBio(),
}
// player nesnesi mongodb veritabanındaki koleksiyona kayıt edilir
result, err := playerCollection.InsertOne(mongoContext, player)
// bir problem oluştuysa
if err != nil {
// gRPC hatası döndürülür
return nil, status.Errorf(
codes.Internal,
fmt.Sprintf("Bir hata oluştu : %v", err),
)
}
// Hata oluşmadıysa koleksiyona eklenen yeni doküman
// üretilen ObjectID değeri de atanarak geri döndürülür
objectID := result.InsertedID.(primitive.ObjectID)
payload.Id = objectID.Hex()
return &playerpb.AddPlayerRes{Plyr: payload}, nil
}
func (srv *PlayerServiceServer) EditPlayer(ctx context.Context, req *playerpb.EditPlayerReq) (*playerpb.EditPlayerRes, error) {
return nil, nil
}
func (srv *PlayerServiceServer) RemovePlayer(ctx context.Context, req *playerpb.RemovePlayerReq) (*playerpb.RemovePlayerRes, error) {
// önce silinmek istenen playerId bilgisi alınır
id := strings.Trim(req.GetPlayerId(), "\t \n")
fmt.Println(id)
// DeleteOne metodu ile silme operasyonu gerçekleştirilir
_, err := playerCollection.DeleteOne(ctx, bson.M{"player_id": id})
// hata kontrolü yapılıyor
if err != nil {
return nil, status.Errorf(codes.NotFound, fmt.Sprintf("Silinmek istenen oyuncu bulunamadı. %s", err))
}
// hata yoksa işlemin başarılı olduğuna dair sonuç dönülür
return &playerpb.RemovePlayerRes{
Removed: true,
}, nil
}
// MongoDB'deki ID bazlı olarak oyuncu verisi döndüren metodumuz
func (srv *PlayerServiceServer) GetPlayer(ctx context.Context, req *playerpb.GetPlayerReq) (*playerpb.GetPlayerRes, error) {
// request ile gelen player_id bilgisini alıyoruz
// Trim işlemi önemli. İstemci terminalden değer girdiğinde alt satıra geçme işlemi söz konusu.
// Veri bu şekilde gelirse kayıt bulunamaz. Dolayısıyla bir Trim işlemi yapıyoruz
id := strings.Trim(req.GetPlayerId(), "\t \n")
// bson.M metoduna ilgili sorguyu ekleyerek oyuncuyu koleksiyonda arıyoruz
result := playerCollection.FindOne(ctx, bson.M{"player_id": id})
player := Player{}
// bulunan oyuncu decode metodu ile ters serileştirilip player değişkenine alınır
if err := result.Decode(&player); err != nil {
return nil, status.Errorf(codes.InvalidArgument, fmt.Sprintf("Sanırım aranan oyuncu bulunamadı %v", err))
}
// Decode işlemi başarılı olur ve koleksiyondan bulunan içerik player isimli değişkene ters serileşebilirse
// artık dönecek response nesne içeriğini hazırlayabiliriz
res := &playerpb.GetPlayerRes{
Plyr: &playerpb.Player{
Id: player.ID.Hex(),
PlayerId: player.PlayerID,
Fullname: player.Fullname,
Position: player.Position,
Bio: player.Bio,
},
}
return res, nil
}
// Tüm oyuncu listesini stream olarak dönen metod
func (srv *PlayerServiceServer) GetPlayerList(req *playerpb.GetPlayerListReq, stream playerpb.PlayerService_GetPlayerListServer) error {
currentPlayer := &Player{}
// Find metodu veri üzerinden hareket edebileceğimiz bir Cursor nesnesi döndürür
// bu cursor nesnesi sayesinde istemciye tüm oyuncu listesini bir seferde göndermek yerine
// birer birer gönderme şansına sahip olacağız
// Bu nedenle sunucu bazlı bir streamin stratejimiz var
cursor, err := playerCollection.Find(context.Background(), bson.M{})
if err != nil {
return status.Errorf(codes.Internal, fmt.Sprint("Bilinmeyen hata oluştu"))
}
// metod işleyişini tamamladığında cursor nesnesini kapatacak çağrıyı tanımlıyoruz
defer cursor.Close(context.Background())
// iterasyona başlanır ve Next true döndüğü sürece devam eder
// yani okunacak mongodb dokümana kalmayana dek
for cursor.Next(context.Background()) {
// cursor verisini currentPlayer nesnesine açıyoruz
cursor.Decode(currentPlayer)
// istemciye mongodb'den gelen güncel oyuncu bilgisinden yararlanarak cevap dönüyoruz
stream.Send(&playerpb.GetPlayerListRes{
Plyr: &playerpb.Player{
Id: currentPlayer.ID.Hex(),
PlayerId: currentPlayer.PlayerID,
Fullname: currentPlayer.Fullname,
Position: currentPlayer.Position,
Bio: currentPlayer.Bio,
},
})
}
return nil
}
Sunucu tarafındaki kodlama tamamlandıktan sonra istemci tarafı için clientapp altında tester.go isimli bir başka dosya oluşturarak ilerleyelim. Burada komut satırından temel CRUD operasyonlarını icra edeceğiz. Yeni bir oyuncunun eklenmesi, bir oyuncu bilgisinin çekilmesi, tüm oyuncuların listesinin alınması vb
package main
import (
"bufio"
"context"
"fmt"
"io"
"os"
"strings"
playerpb "gRPC-sample/proto"
"google.golang.org/grpc"
)
var client playerpb.PlayerServiceClient
var reqOptions grpc.DialOption
func main() {
// HTTPS ayarları ile uğraşmak istemedim
reqOptions = grpc.WithInsecure()
// gRPC servisi ile el sıkışmaya çalışıyoruz
connection, err := grpc.Dial("localhost:5555", reqOptions)
if err != nil {
fmt.Println(err)
return
}
// proxy nesnesini ilgili bağlantıyı kullanacak şekilde örnekliyoruz
client = playerpb.NewPlayerServiceClient(connection)
// Oyuncu ekleyelim
insertPlayer()
// tüm oyuncu listesini çekelim
getAllPlayerList()
// sembolik olarak ID bazlı 3 oyuncu aratalım
for i := 0; i < 3; i++ {
reader := bufio.NewReader(os.Stdin)
fmt.Println("Oyuncu IDsini gir.")
playerID, _ := reader.ReadString('\n')
getByPlayerID(playerID)
}
// Silme operasyonunu deniyoruz
reader := bufio.NewReader(os.Stdin)
fmt.Println("Silmek istediğiniz oyuncunun IDsini girin.")
playerID, _ := reader.ReadString('\n')
removePlayerByID(playerID)
// tüm oyuncu listesini çekelim
getAllPlayerList()
}
func insertPlayer() {
// Yeni oyuncu eklenmesi için deneme kodu
// Veri ihlalleri örneğin basitliği açısından göz ardı edilmiştir
reader := bufio.NewReader(os.Stdin)
fmt.Println("Yeni oyuncu girişi")
fmt.Println("Id->")
id, _ := reader.ReadString('\n')
id = strings.Replace(id, "\n", "", -1)
fmt.Println("Adı->")
fullname, _ := reader.ReadString('\n')
fullname = strings.Replace(fullname, "\n", "", -1)
fmt.Println("Pozisyon->")
position, _ := reader.ReadString('\n')
position = strings.Replace(position, "\n", "", -1)
fmt.Println("Kısa biografisi->")
bio, _ := reader.ReadString('\n')
bio = strings.Replace(bio, "\n", "", -1)
// protobuf dosyasındaki şemayı kullanarak örnek bir oyuncu nesnesi örnekliyoruz
newPlayer := &playerpb.Player{
PlayerId: id,
Fullname: fullname,
Position: position,
Bio: bio,
}
// servisin AddPlayer metodunu o anki context üzerinden çalıştırıp
// request payload içerisinde yeni oluşturduğumuz nesneyi gönderiyoruz
res, err := client.AddPlayer(
context.TODO(),
&playerpb.AddPlayerReq{
Plyr: newPlayer,
},
)
if err != nil {
fmt.Println(err)
return
}
// Eğer bir hata oluşmamışsa MongoDB tarafından üretilen ID değerini ekranda görmemiz lazım
fmt.Printf("%s ile yeni oyuncu eklendi \n", res.Plyr.Id)
}
// Tüm oyuncu listesini çektiğimiz metod
func getAllPlayerList() {
// önce request oluşturulur
req := &playerpb.GetPlayerListReq{}
// proxy nesnesi üzerinden servis metodu çağrılır
s, err := client.GetPlayerList(context.Background(), req)
if err != nil {
fmt.Println(err)
return
}
// sunucu tarafından stream bazlı dönüş söz konusu
// yani kaç tane oyuncu varsa herbirisi için sunucudan istemciye
// cevap dönecek
for {
res, err := s.Recv() // Recv metodu player.pb.go içerisine otomatik üretilmiştir. İnceleyin ;)
if err != io.EOF { // döngü sonlanmadığı sürece gelen cevaptaki oyuncu bilgisini ekrana yazdırır
fmt.Printf("[%s] %s - %s \n\n", res.Plyr.PlayerId, res.Plyr.Fullname, res.Plyr.Bio)
} else {
break
}
}
}
// Oyuncuyu PlayerID değerinden bulan metodumuz
func getByPlayerID(playerID string) {
// parametre olarak gelen playerID değerinden bir request oluşturulur
req := &playerpb.GetPlayerReq{
PlayerId: playerID,
}
// GetPlayer servis metoduna talep gönderilir
res, err := client.GetPlayer(context.Background(), req)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(res.Plyr.Fullname)
}
// Oyuncu silme fonksiyonumuz
func removePlayerByID(playerID string) {
// RemovePlayer servis çağrısı için gerekli Request tipi hazırlanır
req := &playerpb.RemovePlayerReq{
PlayerId: playerID,
}
// servisi çağrısı yapılıp sonucu kontrol edilir
_, err := client.RemovePlayer(context.Background(), req)
if err != nil {
fmt.Println(err)
return
}
fmt.Println("Oyuncu silindi")
}
Piuvvv!!! Uzun bir yol oldu. Öyleyse çalışma zamanı sonuçlarımıza bakalım mı?
Çalışma Zamanı
İlk gün çalışmasının meyveleri pek fena değil. server ve client tarafa ait go dosyalarını kendi klasörlerinde aşağıdaki terminal komutları ile derledikten sonra
go build main.go
go build tester.go
önce sunucu ardından istemci programlarını çalıştırıp kodlaması ilk önce biten AddPlayer fonksiyonunu deneme şansı buldum. Birkaç oyuncu verisini girdikten sonra mongodb container'ına ait shell'e bağlanıp gerçekten de yeni dokümanların player koleksiyonuna eklenip eklenmediğine baktım. Sonuç tebessüm ettiriciydi :) İstemci uygulama gRPC üzerinden sunucuya mesaj göndermiş, sunucuya gelen içerik docker container üzerinde duran mongodb veritabanına yazılmıştı.
İkinci gün tüm oyuncu listesini gRPC üzerinden istemciye döndüren süreci yazmaya çalıştım. İlk başta yaptığım bir hata nedeniyle epey vakit kaybettim. GetPlayerList metodunu protobuffer dosyasında stream döndürecek şekilde tasarlamamıştım. Büyük bir veri kümesini filtresiz çektiğimizde bu ağ trafiğinin sağlıklı çalışması açısından sorun olabilir. Oyuncuları sunucudan istemciye doğru bir stream üzerinden tek tek göndermek çok daha mantıklı(Burada REST ile gRPC arasındaki farkları hatırlayalım) Sonunda servis sözleşmesini değiştirip gerekli düzenlemeleri yaptıktan sonra aşağıdaki ekran görüntüsünde yer alan mutlu sona ulaşmayı başardım.
Devam eden gün bir öncekine göre daha zorlu geçti. FindOne metodunu player_id değerine göre çalıştırmayı bir türlü başaramadım. Neredeyse 4 pomodoro periyodu uğraştım. Hatta pomodoro süreci bittikten sonra farkında olmadan saatlerce bilgisayar başında kaldım. Sorunu araştırırken vakit nasıl geçti anlamamışım. Sonuçta işe 3 saatlik uykuyla gittim. Ertesi gün Ahch-To'nun tuşuna bile basmadım. Bir günlük ara, problemi çözmem için beni sakinleştirmeye yeterdi. Nihayetinde sorunu buldum. İstemci aradığı ID değerini girip sunucuya çağrı yaptığında, servis metoduna gelen ID bilgisinin sonunda boşluk ve alt satıra geçme karakterleri de geliyordu. Trim fonksiyonu ile bu durumun oluşmasını engelledikten sonra silme operasyonunu da işin içerisine dahil ettim ve güncelleme operasyonu hariç komple bir test yaptım. Sonuçlar ekran görüntüsünde olduğu gibi tatmin ediciydi.
Silme operasyonuna ilişkin çalışmaya ait örnek bir ekran görüntüsü de aşağıdaki gibi.
Neler Öğrendim?
Elbette SkyNet'te geçirdiğim bugünün de bana öğrettiği bir sürü şey oldu. Bunları aşağıda yer alan maddelerle ifade etmeye çalıştım.
- Bir protobuf dosyası nasıl hazırlanır ve Go tarafında kullanılabilmesi için nasıl derlenir,
- Go tarafından MongoDB ile nasıl haberleşilir,
- MongoDB docker container'ına ait shell üstünde nasıl çalışılır,
- Temel mongodb komutları nelerdir,
- Sunucudan istemciye stream açarak tek tek mongo db dokümanı nasıl döndürülür(main.go'daki GetPlayerList metoduna bakın)
Eksikliği Hissedilen Konular
Her ne kadar pomodoro tekniği ile çalışmalarımı olabildiğince verimli hale getirsem de ister istemez yaşlı zihnim yoruluyor. Dolayısıyla şunları da yapabilsem iyi olurdu dediğim şeyler var. Bunları da şu iki madde ile sıralayabilirim.
- İstemci tarafını Go tabanlı bir web client olarak geliştirmeyi deneyebiliriz. Terminalden hallice daha iyidir. En azından çalışma sırasında yaşadığım Trim ihlali oluşmaz.
- Bir çok sunucu metodunda hata kontrolü var ancak bunların çalışıp çalışmadığı test etmek gerekiyor. Yani Code Coverage değerimizi neredeyse 0. Yazıyla sıfır :) Bir Go uygulamasındaki fonksiyonlar için Unit Test'ler nasıl yazılır öğrenmem lazım.
Görev Listeniz
Ve tabii kabul ederseniz sizin için iki güzel görevim var :)
- Select * from players where fullname like 'A%' gibi bir sorguya karşılık gelecek mongodb fonksiyonunu geliştirip uygulamaya ekleyin.
- Güncelleme fonksiyonunu tamamlayın.
Böylece geldik SkyNet'te bir günün daha sonuna. Sonraki çalışmada Wails paketini kullanarak Go ile yazılmış bir masaüstü programı geliştirmek niyetindeyim. O zaman dek hepinize mutlu günler dilerim.