Go Gin Framework ile Tam Kapsamlı Mikroservis CRUD Uygulaması Yazalım.

Alper Reha YAZGAN
Turk Telekom Bulut Teknolojileri
10 min readFeb 14, 2022

Hepinize merhaba ben Alper, bugün sizlerle birlikte Go dilini kullanarak hızlı geliştirme yapabileceğiniz mini image boyutlu Stateless CRUD uygulaması yazacağız. Bu uygulamada normal bir servis için gerekli bütün üçüncü parti uygulamalarla bağlantısı yapılmış şekilde çalıştırmayı gerçekleştireceğiz ve bu sebeple makale birazcık uzun olacak. Makale sonunda oluşturmuş olduğunuz çalıştırılmaya hazır Container Image fonksiyonumuzu istediğiniz bütün bulut sağlayıcılarda (Google Cloud Functions , Amazon ECS, on-prem Docker vb.) kullanabilecek ve hızlıca deployment’lar yapabileceksiniz. Üstelik oluşturduğunuz imaj boyutu ise ≈35MB civarı gibi minimal boyutlarda olacak.🔥🔥🔥
Dilerseniz başlayalım.

(1–2–3–4 kısımlı başlıklarda Giriş — Kodlama —-Konfigurasyonlar — Image Oluşturma — Test işlemlerini gerçekleştiriyorum. Direkt olarak imajı elde etmek istiyorsanız 3.2 numaralı kısma giderek hepsini yapıp atmış olduğum DOCKER_HUB imajını çekip makaleye oradan devam edebilirsiniz. )

Gereksinimler:

-Docker
-Docker-compose (Redis,NATS, Minio ve Postgresql gereksinimlerimizi docker-compose.yaml üzerinden test için hızlıca kaldıracağız.)
-Go 1.17 (1.17 ve sonrası herhangi bir versiyon)

Ne Yapacağız?

Bugün yapmayı planladığım mini uygulama sizlerin Go dilini kullanarak Web Programlama ile yapmayı isteyebileceğiniz her şeyi kapsaması (Redis, Postgres, NATS, S3) ve ilerde ihtiyacınız olduğunda direkt olarak kullanabileceğiniz kodların bulunabilmesi için biraz karmaşık ve main.go dosyamızın uzun olduğu bir örnek olacak. Ancak bütün gereksinimleri çalıştırmayı planladığımız bu örneğimizde dilediğiniz kısmı kaldırarak kendi kodunuzu yazabilirsiniz. Ben hepsinin birer hazır kodu olması için anlatarak yapacağım.

Ürün Yönetim Servisi: (Product Management Service)
(BÜTÜN_PROJE_KODLARI)

1- Uygulamamız 4 endpointten oluşacak:
GET /products: Postgresql veritabanı kullanılarak pagination ile veri çekilebilecek.

GET /cache/:cache_id: İçerisinde ürün_fotoğrafı yolunun bulunduğu cache_id’yi içeren değeri Redis üzerinden çekip sonra Minio’dan ilgili ürün_fotoğrafını getiren endpoint’imiz.

POST /products: Kullanıcı tarafından sağlanan ürün_adı ve ürün_fotografi bilgilerini alıp ürün_fotografi’ni S3 uyumlu Minio uzerine kaydeden ardından Minio tarafını işaret eden erişim linkiyle(URL) birlikte Postgresql üzerine ürün bilgilerini kaydeden ve ürün_fotoğrafına erişim için geçiçi bir anahtar oluşturup o anahtarı Redis üzerinde bellekte saklayan bütün işlemlerden sonra ise NATS üzerine “product.created” isimli event fırlatarak Pub&Sub mantığının gerçeklenmesini sağlayan endpoint’imiz.

DELETE /products/:product_id: Verilen product_id’ye sahip ürünü veritabanından, ürün fotoğrafını ise S3 uyumlu Minio üzerinden silip kullanıcıya geri dönüş sağlayan uç noktamız. (Makale daha da uzamaması için rol bazlı yetkilendirme ve kimlik doğrulama kısımlarını yazmadım.)

Yukarıdaki 4 endpoint ile birlikte aslında bir uygulamanın bütün gereksinimlerinin karşılanabileceği bir mikroservis yazabilir hale geleceksiniz.

Gerçekleyeceğimiz Uygulama Mimarimiz:

Bütün aksiyonları içerisinde barındıran CRUD Servisimizin mimari görünümü
  • Redis: Uygulamada Cache işlemlerimiz için,
  • NATS: Uygulamamızda Queue ve Pub&Sub işlemlerini yönetmek ve göndermek için,
  • Postgresql: CRUD operasyonlarını veritabanı üzerinde gerçeklemek için,
  • Minio(S3): Dosya yükleme işlemleri sonucu dosya ve içerikleri ortak bir merkezde barındırmak için, (Minio Nedir ilgili yazıma ulaşmak için BU_LINK ile okuyabilirsiniz. )
  • Go Gin CRUD App: Basit anlamıyla gönderi oluşturma, getirme ve silme işlemlerini içeren Go dili ile yazılmış mini servisimizin dili için.

Yukarıdaki mimaride gördüğünüz gibi CRUD uygulamamızı bütün fonksiyonları (Dosya yükleme ve getirme, veritabanına yazma ve okuma, önbelleğe yazma ve okuma, yapılan işlemleri mikroservis mimarisi kapsamında üçüncü parti uygulamalara olay fırlatabilmesini -PubSub-) yapabilir haliyle gerçekleştirmek istiyoruz. Kodlamaya başlarken de öncelikle NATS, Redis, Postgresql ve Minio uygulamalarını ayağa kaldırıp ardından Go ile kodlamaya başlayacağız. Dilerseniz sırasıyla bu işlemlere geçelim.

1.Proje Dizini ve Uygulamaların Çalıştırılması:

1.1- İlk olarak kendimize bir proje dizini oluşturuyoruz ve Go projemizi gerekli kütüphaneler ile birlikte başlangıcı ve kurulumunu yapıyoruz. (Bütün hepsini şimdi kuracağımız için biraz uzun olacak 😃)

# Cmd (Windows) veya Shell (Linux) ekranında iken
mkdir go-crud-boilerplate
cd go-crud-boilerplatego mod init github.com/AlperRehaYAZGAN/go-crud-boilerplate go get github.com/gin-gonic/gin
go get github.com/joho/godotenv
go get github.com/aws/aws-sdk-go/aws
go get github.com/aws/aws-sdk-go/aws/credentials
go get github.com/aws/aws-sdk-go/aws/session
go get github.com/aws/aws-sdk-go/service/s3
go get github.com/go-playground/validator/v10
go get gorm.io/driver/postgres
go get gorm.io/gorm
go get github.com/go-redis/redis/v8
go get github.com/nats-io/nats.go

1.2- Go dilimiz için bütün bağımlılıkları indirdik şimdi ise Docker kullanarak Go uygulamamızın bağlanacağı bütün uygulamaları(Postgres, Redis,NATS,Minio) tek bir docker-compose.yaml dosyası içerisine yazıp tek satır kod ile ayağa kaldırıyoruz. (Kaldıracağımız uygulamalar dosyalarını saklayabilmesi için -örneğin veritabanı- bir dosyaya bağlanması -volume mount- gerekmektedir. Bu sebeple docker-compose.yaml dosyamızı oluşturmadan önce proje dizinimizde “volumes” adında bir dizin oluşturuyoruz ve ardından docker-compose.yaml dosyamızı oluşturuyoruz.)

# Proje dizinindeyken Cmd ekranı veya Shell ekranı ile 
mkdir volumes

Şimdi docker-compose.yaml dosyamızı oluşturabiliriz. (docker-compose.yaml dosyamız.)

1.3- Bu dosyamızı oluşturduktan sonra bütün uygulamaları ayağa kaldırıyoruz.

docker-compose up -d
docker-compose up -d # komutu sonucu ekran görüntüsü (Bu bütün 4 uygulamamızın memory boyutu ≈150 MB tuttu. Harika 🔥)

Bu işlem sonucunda:
- 5432 portunda Postgresql,
- 6379 portunda Redis,
- 4222 portunda NATS,
- 9000 portunda Minio çalışacaktır.

Şimdi bu uygulamalara bağlanabilmek için isim ve şifreler yukarıdaki docker-compose.yaml içerisinde belirtildi. Ancak uygulamaya bu veriler verilirken direkt olarak açık bir şekilde yazıyla verilmesi güvenlik sorunları oluşturmaktadır. Bu sebeple proje dizinimizde .env isminde bir dosya oluşturup içerisine bu değişkenleri aşağıdaki gibi veriyoruz.

Aşağıdaki .env dosyasını farkettiyseniz localhost diyerek değil ip üzerinden erişimleri verdik. Çünkü şu an geliştirme yapıyoruz ve Go uygulamamızı ana makine üzerinden kaldırdığım için (“go run main.go”) otomatik olarak localhost diyerek erişebiliyoruz. Ancak uygulamayı Container Image oluşturup test ederken (makale son kısımlarında) bütün konteynırların erişebileceği bir ip vermemiz gerekecektir ki Go uygulamamız konteynır içerisindeyken veritabanına localhost üzerinden değil gerçek erişilebilir ip’si üzerinden erişerek işlemlerini gerçekleştirebilsin yoksa “Error Connection” hatası atacaktır.

# .env dosyamız 
# cmd'den ifconfig veya ipconfig ile aldığım ip bilgime göre
# localhost »» 192.168.1.103 bilgisayarımın adresi olmaktadır.
# siz de test için kendi local ip adresinizi yazmalısınız.
DBCONNSTR=postgres://alyafnuser:alyafnuserpassword@192.168.1.103:5432/alyagofndev
REDISCONNSTR=192.168.1.103:6379
NATS_URL=nats://192.168.1.103:4222
S3_ENDPOINT=http://192.168.1.103:9000
S3_REGION=eu-east-1
S3_BUCKET=public
S3_ACCESS_KEY=minio
S3_SECRET_KEY=minio123

Şu anda işlemimiz bitti ancak docker-compose.yaml üzerinde Minio uygulamasını ayağa kaldırınca varsayılan olarak hiçbir bucket oluşturulmadan geliyor. Bu sebeple Minio web browser’a girip ROOT_USERNAME ve ROOT_PASSWORD ile giriş yapıp “public” adında bir bucket oluşturacağız.Böylece uygulamamız yukarıda S3_BUCKET şeklinde verilen bucket’ı bulabilecek ve artık uygulamamızı kodlamaya son sürat başlayacağız.

( eski minio imajları için http://localhost:9000/ , yeni minio imajlarını kullanıyorsanız ise http://localhost:9001/ adresine gidiyoruz. isim ve şifre ile giriş yapıyoruz sonra aşağıdaki resimdeki gibi “public” adında bir bucket oluşturuyoruz.)
ACCESSKEY=minio
SECRETKEY=minio123
(Minio Meraklısına Not: Yukarıdaki bilgiler isim ve şifre bilgileridir eski Minio versiyonlarında ana kullanıcı isim ve şifre access_key, secret_key olarak geçerken yeni Minio projelerinde isim değişikliğine gidilerek Root_Username, Root_Password credentials olarak geçmektedir. Biz bu projede eskisini kullanmaktayız.)

"public" isminde bucket oluşturup dizine gidiyoruz.

2. Uygulamamızı Kodlayalım.

2.1- Uygulamamız için proje dizinimizde main.go isminde bir klasör oluşturuyoruz. Sonrasında bütün yatay uygulamalar ile birlikte çalışabilmesi için sırasıyla aşağıdaki gibi yatay birimler ile bağlıyoruz. (main.go)

Bütün Yatay Birimlerin Bağlanması ve Konfigürasyonu

Yukarıdaki koda baktığınızda veritabanı ve diğer bağlantılar için global bir değişken oluşturduğumu farketmişsinizdir. Go dili kütüphanelerinin çoğu sizin direkt olarak connection-pool açmanıza gerek kalmadan kendisi connection-pool işlemlerini yöneterek hata durumunda tanımlanmış panic() fonksiyonlarını çağırmaktadır. Böylece Node.js veya diğer dillerden hatırlayabileceğiniz her veritabanı işlemine veya bu örnekteki diğer uygulama bağlantılarını try-catch içerisine koymanıza gerek yoktur.

2.2- Şimdi ise öncelikle veritabanımızda “Product” isminde tablo oluşturalım. Sonrasında yukarıdaki bahsedilmiş 4 adet endpoint için uygulamamızı yazalım.

2.2.0- Product Gorm Class ve Main Fonksiyonumuz:

Öncelikle bütün veritabanı işlemlerini Gorm kütüphanesine bırakıyoruz. Bu sebeple biz modelimizi tanımlayacağız. Modeli bilgilerini bir önceki kısımda verdiğimiz veritabanına bağlanıp kendisi otomatik oluşturması için main fonksiyonu içerisinde AutoMigrate diyoruz.

Go Gin uygulamamızın iskeletini barındıran ve başlamasını sağlayan Main fonksiyonumuz

2.2.1- GET /products:

Kullanıcıdan basit bir şekilde pagination verilerini alıyoruz ve ona göre veritabanından veri çekiyoruz.

GET /products endpoint’imiz

2.2.2- POST /products:

Kullanıcıdan ürün ismi ve ürün resmini alan uç noktamız. Bu uç nokta ürün fotoğrafını harici Minio üzerine yazıp yüklenen fotoğrafın erişim URL’i ve ürün adını veritabanında ekleme işlemi yapan ve sonrasında geçici bir anahtar ile bellek üzerine yazarak sonrasında bütün servislere “post.created” şeklinde bir event fırlatan uç nokta olarak kodlamasını yapacağız.
(ürün fotoğrafı -> “product_photo” multipart/form-data
ürün adı -> “name” multipart/form-data olacak şekilde okunmaktadır.)

POST /products : Ürün ekleme işlemini gerçekleştiren en uzun endpointimiz 😇

2.2.3- GET /cache/:cache_id :

Ürün kaydedildikten sonra Redis belleğe bir anahtar ile fotoğrafa erişim linki verilmektedir. Bu sebeple önce Redis üzerinden anahtara karşılık gelen değeri okuyup sonrasında fotoğrafı Minio üzerinden çekerek kullanıcıya dönmemiz gerekmektedir.

GET /cache/:cache_id : Verilen anahtara sahip ürün fotoğrafını getiren fonksiyon

2.2.4- DELETE /products/:id :

Verilen ürün id’sini veritabanı üzerinden silen uç noktamız. (Yetkilendirme işlemlerini bu makaleyi uzatmak için eklemedim. İsterseniz yalnızca bir middleware yazıp ekleyebilirsiniz.)

DELETE /product/:id : Verilen id’ye sahip ürünü silen endpoint’imiz 🗑

Yukarıdaki kodları incelediğinizde global olarak oluşturduğumuz pool variable değişkenler ile ilgili endpointler içerisinde üçüncü parti servislere istek atıp cevap yönetimi yapılarak bir mikroservisin genel ihtiyacı olan konuların barındırıldığı bir uygulama gerçekleştirmiş olduk.

3. Uygulamamızı Dağıtıma Hazır Container Image Haline Getirelim.

Uygulama kodlarımız hazır ve paketlenmeyi bekliyor. 🎉🥳

3.1- Şimdi ise paketlenme bilgilerini içeren ve aşama aşama paketi oluşturması için ne yapması gerektiğini anlattığımız Dockerfile isminde bir dosyamızı projemizin ana dizininde oluşturuyoruz. (Dockerfile dosyamız.)

Yukarıda aşama aşama belirttik şimdi ise gelin dağıtıma hazır olacak paketimizi oluşturması için Docker’ı çağıralım.

# imaj olustur
docker build -t alperreha/go-crud-boilerplate:1.0.0 .
....
# Imaj oluşturmasını bekliyoruz.
# Image oluştuğu zamanda "docker images" diyerek imajı kontrol ediniz.
# Image boyutu ≈33MB civarında... 🔥
....
Container Image’ımız oluştu.

Başarıyla imajımız oluştu. 🎉
Artık çalışmaya hazır bir image elde etmiş olduk. Şimdi ise çalışıp çalışmadığını kontrol etmek için lokalimizde test edelim. Birazdan “docker run” diyerek çalıştıracağımız uygulamamız tamamen bağımsız ve kendi ip’si, dosya sistemi ve container işletim sistemi ne sahip olan bir image olacaktır. Bu sebeple çalıştırırken ona diğer Redis, NATS, Postgresql gibi servislerin ip’sini doğru bir şekilde .env dosyasında tanımlamamız gerekmektedir ki başarıyla bağlanabilsin. Bu sebeple cmd ekranımızı açıp “ipconfig” veya “ifconfig” yazarak kendi local area’nızda gerçek ip’nizi bulunuz. Ben yaptığımda kendi ip’mi 192.168.1.103 olarak buluyorum. Sizler de ip’nizi bulup proje dizinindeki .env dosyasını aşağıdaki şemada verilen şekliyle MAIN_MACHINE_IP kısmına o ip adresini yazarak güncelleyin.

(.env dosyamız — MAIN_MACHINE_IP »» 192.168.1.103)

DBCONNSTR=postgres://alyafnuser:alyafnuserpassword@MAIN_MACHINE_IP:5432/alyagofndev
REDISCONNSTR=MAIN_MACHINE_IP:6379
NATS_URL=nats://MAIN_MACHINE_IP:4222
S3_ENDPOINT=http://MAIN_MACHINE_IP:9000
S3_REGION=eu-east-1
S3_BUCKET=public
S3_ACCESS_KEY=minio
S3_SECRET_KEY=minio123

Artık imajımızı ayağa kaldırabiliriz. Öncelikle konteynırımıza .env dosyasının yolunu volume olarak verip uygulamamızı aşağıdaki gibi çalıştırıyoruz.

# .env dosya yolunuzu bulup aşağıda volume'u kendinize göre  değiştiriniz!docker run -d --name alya-go-crud-app \
-p 9090:9090 \
-v /Users/alperreha/Desktop/alper/workspace/go/go-crud-boilerplate/.env:/app/.env \
alperreha/go-crud-boilerplate:1.0.0
docker run komutu ve sonrası

3.2- Sizler için buraya kadar olanları içeren (kodlama,imaj haline getirme kısımlarını) kendi Dockerhub_Repo üzerinde paylaştım. İsterseniz hiç bunlarla uğraşmayıp direkt olarak oradan çekebilirsiniz:

docker pull alperreha/go-crud-boilerplate:1.0.0

Bu kodu çalıştırarak bu makalede yukarıdaki yapmış olduğunuz örneği çekip direkt olarak yukarıdaki “docker run” komutuyla çalıştırabilirisiniz. Yani platform ve bilgisayar bağımsız Container oluşturmanın tadını çıkartın. 😃

4.Postman ile API’ımızı Deneyelim.

Şimdi ise bu uygulamayı test etmek için sırasıyla şu dört şeyi yapıyoruz.

  • Ürün_adı ve ürün_fotoğrafı oluşturarak istek atıp ürün oluşturacağız.
  • Oluşturulan ürün için dönen Redis anahtarını kullanarak ürün fotoğrafını getiren linki çağıracağız.
  • Eklenen ürünleri listelemek için /products uç noktasına GET isteği atacağız.
  • En son eklediğimiz ürünün ID’sini alıp o ürünü sileceğiz.
  • Mutlu Son 🔥

4.1- POST /products uç noktasına istek atarak yeni ürün oluşturuyoruz.

Ürün oluşturuyoruz

4.2- GET /cache/:cache_id uç noktasına bir önceki istekten gelen cache_id ile istek atıyoruz.

Yüklenen fotoğrafı elde ediyoruz.

4.3- GET /products uç noktasına istek atarak bütün ürünleri görüyoruz.

Bütün ürünlere bakıyoruz.

4.4- DELETE /products/:id uç noktasına istek atarak az önceki oluşturduğumuz ürünü siliyoruz.

Az önceki oluşturduğumuz ürünü siliyoruz.

Tebrikler. Başarıyla çalışan bir web servis ayağa kaldırmış olduk…

🥳🎉👏🏻

Sonuç:

Bu makale ile birlikte Go dili Gin Framework’ü kullanarak oturum bilgisi veya dahili bilgi tutmayan (STATELESS) ve yatayda birbirinden bağımsız şekilde replikası alınarak büyümesi sağlanabilen ayrıca kodlama sonucu ortaya çıkan uygulamamızın da güncel Bulut Bilişim platformlarında sektörde çokça kullanılan Docker Image ve Container’ları oluşturarak platform bağımsız deployment yapabilmenize olanak sağlayacak şekilde bir senaryo gerçekleştirmeyi amaçladım. Bu uygulama içerisinde iki yıldır içerisinde bulunduğum ve tasarım kalıplarının uygulanması kapsamında bilgiler edinerek sizlere aktarmayı amaçladığım yapıları belli başlı özetleyecek olursam:

  • Uygulama bağlantı, isim-şifre vb. bilgileri uygulamadan soyutlayarak .env dosyası gibi Environment Variables olarak uygulamaya aktardık.
  • Uygulamamızı bellek işlemleri için üçüncü parti yazılım olan Redis bellek bağlantısı,ekleme,getirme işlemlerini gerçekleştirdik.
  • Uygulamamızın veri saklama ve getirme gibi işlemlerinin halledildiği Veritabanı(Postgresql) bağlantı,ekleme,getirme,silme işlemlerini gerçekleştirdik
  • Mikroservis mimarisinin en temel kavramlarından olan birbirinden bağımsız birimlerin birbirlerine üçüncü parti event-broker ile haberleri publish etmesi ve subscribe ile bu olayın diğer servisler tarafından bilinmesini sağlayan subscribe mantığını (Pub&Sub)uygulamamızda NATS ile gerçekleştirdik.
  • Dosya yükleme mikroservislerinde gerçekleşen ve dosyaların merkezi bir Object Storage Server (S3) uygulamasında toplanmasını ve yönetilmesini öngören yapıyı S3 uyumlu Minio ile ekleme,silme,getirme işlemleriyle gerçekleştirdik. (Amazon S3 kullanacak olursanız yapmanız gereken tek şey .env dosyasında Amazon tarafından verilen bilgileri kullanmanız ve uygulamayı çalıştırmanız. Bu kadar basit 🙃. Bkz. Minio Nedir?)
  • Uygulamanın bütün gereksinimlerinin bulunduğu üçüncü parti çözümlerin (Redis,NATS,Postgres vb.) tek satır kod ile “docker-compose up -d” ayağa kaldırarak geliştirme ortamınızı çok hızlandıran konfigürasyon dosyasını da birlikte gerçekleştirmiş olduk.
  • Uygulamamızı az önce oluşturduğumuz Container Image vasıtasıyla bütün platformlarda (Amazon ECS, Google Cloud Functions) kullanılabilir ve deploy edilebilir hale getirmeyi gerçekleştirdik. (İlgili linklere giderek deployment’lara başlayabilirsiniz.)

Makalede yazılan bütün kodları BU_LINK üzerinden inceleyebilir. Kod ile ilgili güncelleme, hata vb. gibi durumları yine aynı Github linkinden bildirebilirsiniz.
Okuduğunuz için teşekkürler.

Diğer yazılarımda görüşmek üzere, sağlıcakla kalın…

Alper Reha Yazgan.

--

--

Alper Reha YAZGAN
Turk Telekom Bulut Teknolojileri

Yıldız Teknik Üniversitesinde Bilgisayar Mühendisliği öğrencisi, yeni nesil teknoloji ve yeniliklere karşı araştırmacı ve meraklı birisiyim.