3# Go Programlama Dili — Paketler

Nailcan Küçük
5 min readJan 24, 2020

Go dilinde kod organizasyonu paketler ile sağlanır. Paket aslında disk üzerinde bulunan bir klasör içerisindeki dosyaları temsil etmektedir. Go dilinde de hem kod içerisinde kullandığımız kütüphaneler hem de ana kodumuz birer pakettir.

İsimlendirme

Paket isimleri verilirken, paketi kapsayan klasör ismi ile aynı olması beklenir, ancak zorunlu değildir.

Paket tanımlaması kaynak kod dosyasının en üstünde package keyword’ü ile yapılır.

Her kaynak kodu içerisinde paket tanımlaması yapılması zorunludur.

package sendMailimport {

..
..

Örnek olarak paketimizin bulunduğu dizin aşağıdaki gibi olsun:

$GOPATH/src/foo/bar

Bu paketi kod içerisinde aşağıdaki gibi import ederiz

import “foo/bar”

Eğer GitHub üzerinde foo isimli repository’de bulunan bar paketini import edeceksek de aşağıdaki şekilde import işlemini yapabiliriz:
import github.com/nailcankucuk/foo/bar

Import edilen paket kod içerisinde kullanılmak istendiğinde genelde son element üzerinden fonksiyonlara erişilerek kullanılır

package mainimport “github.com/nailcankucuk/foo/bar”func main() {
bar.sayHello()
}

Yürütülebilir (Executable) Paketler

Yürütülebilir programlar main olarak paket tanımlaması yapılmış ve içerisinde main() fonksiyonu bulunan bir kaynak koda sahip olmalıdırlar. main() fonksiyonu bir kere tanımlanabilir, herhangi bir parametre almaz ve bir geri dönüş tipi yoktur. Uygulamamız ilk olarak bu main fonksiyonundan çalışmaya başlar.

package mainfunc main() {
fmt.Println(“Naber?”)
}

Kapsam ve Erişilebilirlik

Go dilinde diğer dillerden alışık olduğumuz public, private, protected gibi erişim belirleyiciler yoktur. Bunun yerine Go dilinde isimlendirmede ilk harfin büyük/küçük olmasına göre erişim belirlenir.

Eğer tip, değişken, fonksiyon.. vs ismi büyük harf ile başlıyor ise paket dışında diğer paketlerden de erişilebilir olduğu anlamına geliyor. Ancak küçük harf ile başlıyor ise sadece aynı paket içerisinde erişilebilir oluyor.

// Paket dışından da erişilebilir
func Foo() {}
// Sadece paket içerisinde erişilebilir.
var bar string

Başka bir paket içerisindeki export edilmemiş alanlardan oluşan bir struct’ın bu alanlarına doğrudan erişemez ve atama yapamayız.

package mainimport (
“fmt”
“github.com/nailcankucuk/userops”
)
func main() {
user := userops.NewUser(“Nail”, “Küçük”, “12345”)
fmt.Printf(“%+v\n”, user)
//Beklenen çıktısı: {First:Nail Last:Küçük password:12345}
// Fakat aşağıdaki gibi direk erişmeyiz.
fmt.Println(user.password)
// ./main.go:17:18: user.password undefined (cannot refer to unexported field or method password)
//user.password = “new”
// ./main.go:18:6: user.password undefined (cannot refer to unexported field or method password)
}

Bir best-practise olarak dışarıdan erişimi olmayan tipte bir değeri dışarıdan erişimi olan bir fonksiyonun dönüş tipi olarak kullanmamak gerekiyor. Kod olarak yazılabilir bir hata almazsınız ancak doğru bir kullanım olmayacaktır.

Hatalı kullanıma örnek

type bar struct {}func Foo() bar {
return bar{}
}

Paket Organizasyonu

Kodu ilk yazmaya başladığınızda hangi paketler olması gerektiğini tam olarak görmek mümkün olmayabilir. Mimariyi oluştururken birçok yaklaşım benimsenebilir. Ancak hiç biri tüm durumlara uyum sağlayabilecek en iyi yaklaşım değildir. Zaten bir çok defa projenin en başında ilişkileri, uygulanması gerekli tasarım şablonlarını direk göremeyebiliriz. Bu nedenle ilk önce kodu yazmaya başlamak, kodumuz belirli bir olgunluğa eriştiğinde de refactor ederek paketleri oluşturmak bir yöntem olabilir. Tabi paketleri oluştururken dikkat etmemiz gereken bazı noktaları da gözden kaçırmamalıyız.

Bu noktalardan en önemlisi circular (döngü halinde) package import etme işlemine Go’nun izin vermemesidir. Yani:

A paketi B paketini import ediyorsa, B paketi A paketini import etmemeli.

Ya da A paketi B paketini, B paketi C paketini, C paketi de A paketini import ediyorsa yine döngü tamamlandığından circular package import yapmış oluruz.

Modül bazında kodumuzu paketlere ayırmak da bir yöntem olarak seçilebilir. Ancak bunun dezavantajı da yine circular referance’a müsait olmasıdır. Örnek olarak user ve department modüllerini ayırdığımızı düşünelim.

demo/
└── users
│ ├── user.go
└── department
└── department.go

Kodu ilk yazarken bu iki paket arasında karşılıklı import etme durumu olmasa bile ilerleyen aşamalarında user paketi içerisinde department paketini, department paketi içerisinde de user paketini kullanma ihtiyacı duymak mümkün olabilir bir durum olarak gözüküyor. Bu nedenle modül bazında ayrım yapmak da çok doğru gözükmemektedir.

Tavsiye edilen paket ayrıştırma yöntemlerinden biri olarak aşağıdaki 4 grupta paketleri oluşturabiliriz.

  • Domain Paketi
  • Implementation Paketleri
  • Mock Paketi
  • Binary Paketleri

Domain Paketi

Domain Package, uygulamaya özel tipleri içeren ve projenin merkezinde bulunan pakettir.

Veri modelleri (struct’lar), interface ve servisleri içerebilir. Ayrıca bu bu veri modelleri ve servisleri kullanan fonksiyonlar da bu paket içerisinde tanımlanabilir.

  • Veri Modelleri: Account, User structs
  • Servisler: AccountService, UserService interfaces
  • Diğerleri: SortUsers() fonksiyonu veya UserCache struct’ı.

Implementation Paketleri

Paket isimleri kullanılan teknolojilerden oluşmak üzere implement işlemine ait kaynak kodlar ilgili paket altında bulunur.

Örnek olarak Foo struct’ı ile ilgili Postgres DB üzerinde yaptığımız CRUD işlemlerine ait FooService kaynak kodumuz postgres paketi altında, yine Foo struct’ına ait Redis’de tutuacağımız cache işlemlerini içeren FooService’i de redis paketi altında tanımlayabiliriz. Böylece implementasyonları teknoloji bazında izole etmiş oluruz. Bize avantajı olarak da her katman için ayrı mock veriler oluşturabilir, ayrı ve bağımsız testler yazabilir, kısaca katmanları birbirinden ayırmış oluruz.

demo/
└── foo.go
└── postgres
│ ├── foo.go
└── redis
└── foo.go

Mock Paketi

Özellikle test için kullanacağımız mock verileri bu paket altında toplayabiliriz. Tek paket altında tüm diğer servisler için gerekli mock verileri toplayabiliriz.

demo/
└── mock
└── foo.go

Binary Paketleri

Proje içerisinde kullandığımızı paketlerin derlenmiş binary kodlarını “”cmd” paketi altında alt paketler oluşturarak tutabiliriz.

demo/
└── cmd
├── demoserver
│ └── main.go
└── democlient
└── main.go

Genel görünüm olarak örnek bir paket yapısı aşağıdaki gibi olabilr.

demo/
├── cmd
│ ├── demoserver
│ │ └── main.go
│ └── democlient
│ └── main.go
├── mock
│ └── foo.go
├── postgres
│ └── foo.go
├── redis
│ └── foo.go
└── foo.go

Eğer büyük boyutlu bir projede çalışıyorsanız projenizi alt domainlere bölmeniz gerekecektir. Ana dizinde yine tüm alt domainleri ilgilendiren ortak tip ve interface tanımlamalarını yazabilirsiniz. Ancak farklı domainlere sahip kodları birbirinden izole ederek ayrı paketler halinde yazmak daha doğru bir yaklaşım olacaktır. Bu yaklaşımın faydalarını kısaca toparlayacak olursak:

  • Uygulama katmanları arasındaki iletişimin, arayüzler üzerinden doğru bir şekilde yapılabilmesi sağlanır.
  • Birbirinden ayrılmış her paket kendi içerisinde ayrı ayrı test edilebilir.
  • İç içe (circular) bağımlılık oluşmasının önüne geçilmiş olur.
  • Uygulamanın sağlıklı ve temiz bir şekilde büyümesi sağlanmış olur. Uygulama büyürken çıkabilecek bir sorun sadece ilgili paket içerisinde olur ya da yeni bir geliştirme esnasında diğer paketlerin iç yapısını etkileyecek bir durum oluşmaz.

Dışarıdan Paket Yükleme ve Güncelleme

Bir çok dilde olduğu gibi Go dilinde de başka geliştiricilerin oluşturduğu paketleri kendi geliştirme ortamımızda kullanabiliriz. Dışarıdan bir paketi geliştirme ortamımıza almamız için de ilk önce Go dilinin kendi araçlarından get fonksiyonunu kullanmanız gerekir. Bu fonksiyon ile paketleri ilk önce lokal geliştirme ortamımıza indiririz. İndirme işlemi GOPATH sistem değişkeninin tanımlı olduğu dizine yapılır. GOPATH dizinini görmek için aşağıdaki satırı terminalde çalıştırabilirsiniz.

$ go env GOPATH
/Users/nailcankucuk/go

foo örnek paketini lokal makinemize indirebilmek için paketin bulunduğu git reposunun adresi ile aşağıdaki komut satırını çalıştırırız.

$ go get github.com/nailcankucuk/foo

Bu komut sonrası aşağıdaki dizine paket indirilecektir.

$GOPATH/src/github.com/nailcankucuk/foo

Bu sayede artık bu paketi de kendi kodumuz içerisinde import keyword’ü ile aktararak kullanabiliriz.

Eğer lokalimizde bulunan paket kodlarını güncellemek istersek get sonrası -u yazarak paketin en güncel halini lokalimize almış oluruz. Tabi eğer paket lokalimizde yoksa da en son halini indirmiş oluruz.

$ go get -u github.com/nailcankucuk/foo

Go Programlama Dili paket yapılarını kısaca yazmaya çalıştım. Umarım sizler için de faydalı olmuştur :)

İletişimde kalmak isterseniz linkedin üzerinden haberleşebiliriz.

--

--