Dependency Injection

Bu bölümün bütün kodlarını burada bulabilirsiniz

Bunun için interfaceler hakkında biraz bilgi sahibi olmanız gerekeceğinden, structler bölümünü daha önce okuduğunuz varsayılmaktadır.

Programlama topluluklarında dependency injection konusunda birçok yanlış anlama var. Umarım, bu kılavuz size nasıl yapılacağını gösterecektir.

  • Framework'e ihtiyacınız yok

  • Tasarımınızı karmaşıklaştırmaz

  • Testi kolaylaştırır

  • Kullanışlı, harika fonksiyonlar yazmanıza izin verir.

hello-world bölümünde yaptığımız gibi, birini selamlayan bir fonksiyon yazmak istiyoruz ama bu sefer varolan çıktıyı (actual printing) test edeceğiz.

Özetlemek gerekirse, fonksiyon bu şekilde gözüküyor

func Greet(name string) {
    fmt.Printf("Hello, %s", name)
}

Fakat bunu nasıl test edeceğiz? fmt.Printf çıktılarını stdout'a çağırmak, test frameworkü kullanarak yapmamız çok zor.

Yapmamız gereken, yazdırmanın bağımlılığını inject etmektir (bu sadece içeri girmek için süslü bir kelime).

Yazdırma işleminin nerede veya nasıl olduğuna fonksiyonumuz önemsememeli, Bu yüzden concrete tip yerine interface kabul etmeliyiz.

Bunu yaparsak, uygulamayı kontrol ettiğimiz bir şeye yazdıracak şekilde değiştirebiliriz, böylece test edebiliriz."Gerçek hayatta" stdout'a yazan bir şey inject edersiniz.

Eğer fmt.Printf kaynak koduna bakarsanız bağlanabileceğimiz bir yol görebilirsiniz

// yazılan byteların sayısını ve gerçekleşmiş write error hatasını döner.
func Printf(format string, a ...interface{}) (n int, err error) {
    return Fprintf(os.Stdout, format, a...)
}

İlginç! Printf altında sadece Fprintf çağırıyor ve os.Stdout'u parametre olarak gönderiyor.

Tam olarak os.Stdout nedir? Fprintf, 1. argüman için kendisine ne iletilmesini bekliyor?

func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
    p := newPrinter()
    p.doPrintf(format, a)
    n, err = w.Write(p.buf)
    p.free()
    return
}

Bir io.Writer

type Writer interface {
    Write(p []byte) (n int, err error)
}

Buradan os.Stdout'un io.Writer implemente ettiğini sonucunu çıkarabiliriz; Printf, os.Stdout'u io.Writer bekleyen Fprintf'e gönderir.

Daha fazla Go kodu yazdıkça, bu arayüzün çok fazla ortaya çıktığını göreceksiniz çünkü "bu verileri bir yere koymak" için harika, kullanışlı bir interface.

Dolayısıyla, greetingi örtülü bir şekilde bir yere göndermek için nihayetinde Writer'ı kullandığımızı biliyoruz. Kodumuzu test edilebilir ve daha fazla yeniden kullanılabilir hale getirmek için bu mevcut soyutlamayı kullanalım.

İlk olarak test yaz

func TestGreet(t *testing.T) {
    buffer := bytes.Buffer{}
    Greet(&buffer, "Chris")

    got := buffer.String()
    want := "Hello, Chris"

    if got != want {
        t.Errorf("got %q want %q", got, want)
    }
}

bytes paketindeki Buffer tipi,Writer interfaceini implemente eder, çünkü Write(p []byte) (n int, err error) metoduna sahip.

Onu Writer olarak göndermek için testimizde kullanacağız Greet'i çağırdıktan sonra ona ne yazıldığını kontrol edebiliriz.

Dene ve testi çalıştır

Test derlenmeyecektir

./di_test.go:10:7: too many arguments in call to Greet
    have (*bytes.Buffer, string)
    want (string)

Testin çalışması için minimum kodu yaz ve başarısız test çıktılarını kontrol et

Derleyiciyi dinle ve problemi düzelt.

func Greet(writer *bytes.Buffer, name string) {
    fmt.Printf("Hello, %s", name)
}

Hello, Chris di_test.go:16: got '' want 'Hello, Chris'

Test başarısız oluyor. Adın yazdırıldığına dikkat edin, ancak stdout'a gidecek.

Testi geçecek kadar kod yaz

Testimizde, greetingi buffera göndermek için writerı kullan. fmt.Fprintf'in fmt.Printf gibi olduğunu hatırla ancak string göndermek için Writer'ı alır, oysa fmt.Printf varsayılan olarak stdouttur.

func Greet(writer *bytes.Buffer, name string) {
    fmt.Fprintf(writer, "Hello, %s", name)
}

Şimdi testten geçiyor.

Refactor

Daha önce derleyici bize 'bytes.Buffer' için pointer göndermemizi söyledi. Bu teknik olarak doğru ama çok kullanışlı değil.

Bunu göstermek için, Greet fonksiyonunu stdout'a yazdırmasını istediğimiz bir Go uygulamasına bağlamayı deneyin.

func main() {
    Greet(os.Stdout, "Elodie")
}

./di.go:14:7: cannot use os.Stdout (type *os.File) as type *bytes.Buffer in argument to Greet

Daha önce tartışıldığı gibi, fmt.Fprintf, hem os.Stdout hem de bytes.Buffer implementationını bildiğimiz bir io.Writer göndermenize izin verir.

Eğer kodumuzu daha kullanışlı bir interface çevirirsek onu hem testlerde hem de uygulamada kullanabilirizi..

package main

import (
    "fmt"
    "os"
    "io"
)

func Greet(writer io.Writer, name string) {
    fmt.Fprintf(writer, "Hello, %s", name)
}

func main() {
    Greet(os.Stdout, "Elodie")
}

io.Writer hakkında daha fazla

io.Writer kullanarak başka hangi yerlere veri yazabiliriz? Greet fonksiyonumuz ne kadar kullanışlı?

İnternet

Aşağıdakileri çalıştırın

package main

import (
    "fmt"
    "io"
    "log"
    "net/http"
)

func Greet(writer io.Writer, name string) {
    fmt.Fprintf(writer, "Hello, %s", name)
}

func MyGreeterHandler(w http.ResponseWriter, r *http.Request) {
    Greet(w, "world")
}

func main() {
    log.Fatal(http.ListenAndServe(":5000", http.HandlerFunc(MyGreeterHandler)))
}

Programı çalıştırın ve http://localhost:5000 adresine gidin. Greeting fonksiyonunun kullanıldığını göreceksiniz.

HTTP serverlar sonraki konularda bahsedilecek o yüzden detaylar hakkında çok fazla endişe etmeyin.

HTTP handler yazdığınızda, size http.ResponseWriter istek (request) yapmak için kullanılan http.Request verilir. Sunucuyu implemente ettiğinizde, writer kullanarak cevabınızı yazarsınız.

http.ResponseWriter'ın io.Writer'ı implemente ettiğini tahmin ediyorsunuzdur, bu sayede handlerımızda Greet fonksiyonumuzu kullanabiliriz.

Özetlersek

Kodumuzun ilk halini test etmek kolay değildi çünkü kontrol edemediğimiz bir yere veri yazıyordu.

Testlerimizden motive olarak kodu refactor ettik, böylece verilerin bir bağımlılık inject ederek nerede yazıldığını kontrol edebildik, bu da bize şunları yapmamızı sağladı:

  • *Kodu test etme Eğer bir fonksiyonu kolayca test edemiyorsanız, bunun nedeni genellikle fonksiyona veya global bir duruma bağımlılıklarıdır. Örneğin, bir tür hizmet katmanı tarafından kullanılan global bir veritabanı bağlantı havuzunuz varsa, test edilmesi muhtemelen zor olacak ve çalıştırması yavaş olacaktır. DI sizi, testlerinizi kontrol edebileceğiniz, mocklayabileceğiniz veritabanı bağımlılığı (bir interface araclığı ile) inject etmeye motive eder.

  • Separate our concerns (Bağlantıların ayırma), verilerin nasıl oluştuğu ve nereden nereye gittiği ayrılması.Bir fonksiyonun/metodun çok fazla sorumluluğu olduğunu düşünüyorsanız (veri oluşturma ve db'ye yazma? HTTP isteklerini handle etme ve domain seviyesinde mantık?) DI ihtiyaç duyacağınız araçtır.

  • Kodumuzun farklı contextlerde yeniden kullanılması Kodumuzun kullanabileceği ilk "yeni" context, testlerin içindedir. Biri sizin fonksiyonlarınıza yeni bir şey denemek isterse, kendi bağımlılıklarını inject edebilir.

Mocking'e ne dersin? DI için buna ihtiyacın olduğunu ve aynı zamanda bunun kötü olduğunu duydum

Mocking detaylıca ileride ele alınacaktır (ayrıca kötü değil). Inject ettiğiniz gerçek şeyleri, testlerinizde kontrol edebileceğiniz ve inceleyebileceğiniz taklit bir sürümle değiştirmek için mockingi kullanırsınız. Bizim durumumuzda, standart kütüphanenin bizim için kullanıma hazır bir şeyi vardı.

Go standard kütüphanesi gerçekten faydalı, üzerinde çalışmak için zaman ayır

io.Writer interfaceine aşina oldukça, testimizde bytes.BufferWriter olarak kullanabileceğiz ve fonksiyonumuzu komut satırı uygulamasında veya web sunucusunda kullanmak için standart kütüphaneden diğer Writerları kullanabiliriz.

Standart kitaplığa ne kadar aşina olursanız, yazılımınızı çeşitli contextlerinde yeniden kullanılabilir hale getirmek için kendi kodunuzda yeniden kullanabileceğiniz bu kullanışlı interfaceleri o kadar çok görürsünüz.

Bu örnek, The Go Programming language kitabından oldukça etkilenmiştir, eğer beğendiyseniz satın alın!

Bu sayfa @bariscanyilmaz tarafından çevrildi.

Last updated