All Articles

Spring ve Dependency Injection

Not: Bu yazı 2018 yılında medium’da yayınladığım aynı başlıklı yazıdan kopyalanmıştır.

dependency injection

Dependency Injection, yani Türkçede Bağımlılık Enjeksiyonu olarak ifade edebileceğimiz kavram, kodumuzu yazarken kullanmaya ihtiyaç duyulan (bağımlı olduğumuz) diğer kodları, ihtiyaç duyan kodun içerisinde kullanmak (enjekte etmek) olarak tanımlanabilir.

Örneklerle tekrar ifade edecek olursak; elimizde, array üzerinde binary search uygulayarak istediğimiz elemanın indexini bulmayı sağlayan basit bir servis olduğunu düşünelim. Bir Array üzerinde binary search uygulayabilmemiz için gerekli olan koşul, Array elemanlarının sıralı şekilde tutuluyor olmasıdır. Buradan hareketle, binary search servisimizi, array elemanlarını sıralama işlemi gerçekleştiren başka bir servise veya sınıfa bağımlı şekilde oluşturmamız gerektiğini söyleyebiliriz.

BinarySearchService.java

public class BinarySearchService {
    private SortAlgorithm sortAlgorithm;
    
    public BinarySearchService() {
        sortAlgorithm = new BubbleSortAlgorithm(); 
    }
    public int search(int[] arrayToSearch, int target) { 
        // Bagimlilik olarak tanimlanan sortAlgorithm kullanilarak 
        // array'in siralanmasi
        int[] sortedArray = sortAlgorithm.sort(arrayToSearch);
        // BinarySearch implementasyonu ve aranan elemanın
        // indeksinin bulunmasi
           ...
        return index;
    }
}

Main.java

public class Application() {
    
    public static void main(String[] args) {
    
        ...
        BinarySearchService service = new BinarySearchService();
        int targetIndex = service.search(arrayToSort, 5);
        ...
    }
}

Servisimizin constructor’ında, bağımlı olduğumız sınıftan nesne yarattık ve sıralama yani sort işlemini bu nesne üzerinden yürüttük. Başka bir deyişle, sıralama işlemini gerçekleştiren BubbleSortAlgorithm sınıfımızı, BinarySearch servisine bağımlılık olarak tanımlamış olduk.

Dependency Injection alternatifi olan daha ilkel yöntem ise şuydu: Sort işlemini BinarySearchService’in içerisinde yapmak. Peki ama ileride sort işlemini Bubble Sort değil de başka bir algoritma; örneğin Quick Sort kullanarak yapmak isteseydik ?

Bu senaryoda BinarySearchService’in içerisindeki kodu tekrar düzenlememiz gerekecekti. Peki bu ne gibi bir sorun yaratıyor ?

  • Birincisi, bu servisi bir yerlerde kullanan tüm kodların bu durumdan etkilenmesi söz konusu. Örneğin servis methodunun parametrelerini değiştirmiş olmamız durumunda, methodu çağırdığımız her yerde düzenleme yapmamız gerekir. Büyük sistemlerde bunun imkansız olduğu malum.
  • İkinci problem ise, bu yöntem dinamik bir yapı kurmayı engelliyor. İleride servisi kullandığımız yerlerde duruma göre Bubble Sort, Quick Sort veya Insertion Sort kullanmak isteyebiliriz ama bu yöntemle mümkün değil.

Aslında Dependency Injection kullanmadığımızda ortaya çıkacak sorunlardan bahsederken, Dependency Injection’ın sağladığı faydaları da görmüş olduk. Faydaları bu ikisiyle sınırlı olmasa da, Seperation of Concerns (Farklı işleri yapan birimlerin birbirinden ayrılması) prensibine uygun ve dinamik bir yapı kurmamızı sağladı.

Dinamik yapı demişken, şimdi yazdığımız koda geri dönelim. Bir sorun göze çarpıyor. Her ne kadar basit haliyle Dependency Injection uygulamış olsak da, dinamik bir yapı kurmuş gibi görünmüyoruz. Çünkü yazdığımız servis Constructor’ında BubbleSort sınıfından nesne yaratılıyor ve her halükarda bu sınıf kullanılıyor. Yani Servis sınıfımız ve BubbleSort sınıfımız tightly coupled (sıkı sıkıya bağlı). Sistemi daha dinamik hale getirmek için bu sınıflar loosely coupled (gevşekçe bağlı) olmalı.

QuickSortAlgorithm.java

public class QuickSortAlgorithm extends SortAlgorithm {

    @Override
    public int[] sort(int[] arrayToSort) {
       
        // QuickSort
        ...
    }

}

BinarySearchService.java

public class BinarySearchService {
    private SortAlgorithm sortAlgorithm;
    
    public BinarySearchService(SortAlgorithm sortAlgorithm) {
        this.sortAlgorithm = sortAlgorithm;
    }
    public int binarySearch(int[] arrayToSearch, int target) {
        sortAlgorithm.sort(arrayToSearch);
    }
}

Application.java

public class Application() {
    public static void main(String[] args) {
        
        .....
        // Üzerinden servisi kullanacağımız nesneyi yaratırken 
        // parametre olarak istediğimiz sort sınıfını verdik.
        BinarySearchService binarySearchService = new 
                     BinarySearchService(new QuickSortAlgorithm());
        int targetIndex = binarySearchService.sort(arrayTosort, 5);
        .....
    }
}

Bu örnekte polymorphismden yararlanarak servis ve diğer kullandığımız sınıfları loosely coupled olacak şekilde düzenledik. Artık kullanmak istediğimiz sort algoritması çeşidini, servis constructor’ına parametre olarak veriyoruz.

Spring ve Dependency Injection İlişkisi

Testing, Data Access, MVC Web gibi çeşitli işlevsellik ve büyüklüklerde birçok farklı modülden meydana gelen Spring, temel konseptlerinden biri Dependency Injection olan ve Inversion of Control Container’ı görevi gören bir application framework’üdür.

spring framework routine

Basitçe anlatacak olursak, Spring’e uygulamada kullanacağımız farklı işlevler ve katmanlardaki sınıfları (service, controller, entity vs.) ve bu sınıfların bağımlılıklarını belirtiriz. Spring de bizim yerimize implementasyonları gerçekleştirir, yani başka bir deyişle gerekli nesneleri, bu nesnelere gerekli bağımlılıkları enjekte ederek yaratır.

BinarySearchService.java

@Service
public class BinarySearchService {

    @Autowired
    private SortAlgorithm sortAlgorithm;

    public int search(int[] ar, int target) {
        ...
    }
}

BubbleSortAlgorithm.java

@Component
public class BubbleSortAlgorithm implements SortAlgorithm {
    @Override
    public int[] sort(int[] ar) {
        ...
    }
}

SpringApplication.java

@SpringBootApplication
public class SpringApplication {
    public static void main(String[] args) {
        SpringApplication.run(SecurityappApplication.class, args)
        BinarySearchService service = new BinarySearchService()
        int target = service.search(arrayToSearch, 5);
    }
}

Annotation’ları kullanarak Beanleri tanımladık ve yine Autowired annotation’ı kullanarak bağımlılığımızı belirttik. Spring geri kalan işi arka planda kendi halletti. Dikkat edilirse service sınıfında constructor yazmamıza dahi gerek kalmadı. Çünkü Spring arka planda setter methodu kulanarak dependency’i inject etti. Servis nesnesini herhangi bir sort algorithm parametresi göndermeden yarattık ve direkt olarak kullanıma hazır hale geldi.

Annotationlar hakkında daha fazla bilgi için: https://springframework.guru/spring-framework-annotations/

Published Oct 10, 2020

Software engineer currently based in Tallinn, Estonia.