Sbt #3 : Dependency Yonetimi

 Bu postumuzda sbt ile dependency'ler nasil manage edilir (konustugumz dile bak) konusunu isleyecegiz. Sub-projelerin birbirine depend etmesi soz konusu oldugu gibi 3rd party library kullanimina da deginecegiz. 

3rd Party Library
Daha once sayisal loto tahmin uygulamasi yapmistik. Simdi buna bir ek yaparak, doviz kurlarini da ekleyecegiz. Bunun icin http request yapmamizi saglayacak scala-request kutuphanesini projeye ekliyoruz. buid.sbt dosyasindaki proje tanimlamasina soyle bir ek yapacagiz:

name := "hello_sbt"
version := "0.1"
scalaVersion := "2.13.4"

val sansOyunlari = project.settings(
    libraryDependencies ++= Seq(
        "com.lihaoyi" %% "requests" % "0.6.5",
        "org.scala-lang.modules" %% "scala-xml" % "1.2.0",
    )
)

Sbt projesini reload ediyoruz. Gidip IntelliJ sbt plugin sekmesine bakarsak su sekilde dependency'lerin olsuturuldugunu gorebiliriz:

Simdi requests calisiyor mu calismiyor mu kontrol edebilmek icin bu sansOyunlari (bu arada onceki posttaki sayisalLoto projesinin adini sansOyunlari olarak degistirdim, malum son asamada at yarisi da eklemistik) uzerinde console calistiriyoruz:

sbt:hello_sbt> sansOyunlari/console
[info] Starting scala interpreter...
Welcome to Scala 2.12.12 (Java HotSpot(TM) Client VM, Java 1.8.0_251).
Type in expressions for evaluation. Or try :help.
scala>

Simdi gidip hemen guncel doviz kurlari sayfasina bir request yapalim:

scala> val r = requests.get("https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml")
scala> r.headers("content-type")
res0: Seq[String] = Buffer(text/xml)

Bu sekilde bir XML response elde ediyoruz. Bunu parse etmek icin de scala-xml kutuphanesini import edelim.

scala> val xmlResponse = XML.loadString(r.text)
scala> val ulkeKodlari = (xmlResponse \\ "@currency").map(_.text) 
scala> val euroCarpanlari = (xmlResponse \\ "@rate").map(_.text.toDouble)
scala> val kurlar = (ulkeKodlari zip euroCarpanlari).toMap

Seklinde her bir ulke kodu icin 1 euro'nun ne kadar yaptigini tutan bir dictionary olusturmus olduk. Mesele lirayi kurlar("TRL") seklinde elde edebliiriz. Denemeyi yaptigimiza gore bu kodu yeni api isimli yeni bir sub-proje olsutrara, oraya aliyoruz. Ayrica buradaki requests-scala ve xml dependency'lerini de api projesine kaydiriyoruz. 

build.sbt'nin son hali:

name := "hello_sbt"
version := "0.1"
scalaVersion := "2.13.4"

val sansOyunlari = project

val api = project.settings(
  libraryDependencies ++= Seq(
    "com.lihaoyi" %% "requests" % "0.6.5",
    "org.scala-lang.modules" %% "scala-xml" % "1.2.0",
    "org.scalatest" %% "scalatest" % "3.0.5" % Test
  )
)

Bu arada ekledigimiz bagimlilik tanimlasinda ilk kisim organizasyon, ikinci kisim kutuphane adi ucuncu kisim versiyon ve var ise son kisim (scala-test icin Test) configuration. Yani bu kutuphane sadece Test config'inde olacak ve ornek olarak assembly ile projemiz icin fatjar uretirsek onda yer almayacak. npm'deki dev tanimlamasi gibi dusunulebilir. prodda yer almayacak bir bagimlilik. 

api/src/main/scala/DovizKur.scala seklinde doviz kuru class'imizi da ekleyelim: 

import scala.xml.XML

object DovizKur extends App {

  def dovizurlariDownload(): Map[String, Double] = {
    val r = requests.get("https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml")
    val xmlResponse = XML.loadString(r.text)
    val ulkeKodlari = (xmlResponse \\ "@currency").map(_.text)
    val euroCarpanlari = (xmlResponse \\ "@rate").map(_.text.toDouble)
    (ulkeKodlari zip euroCarpanlari).toMap
  }

  override def main(args: Array[String]): Unit = {
    println(dovizurlariDownload())
  }
}

Scala Test
Scala-test, scala kodunu test etmke icin kullanilan en populer kutuphanelerden bir tanesi. JUnit, testNG ve maven gibi populer testing toollari ile integrasyon saglamaktadir.  Bircok testing style destekliyor ancak biz FlatSpec kullanacagiz. Sadece indirilen doviz kuru sayisini kontrol eden basi bir test yazalim ve api/test/scala/DovizKurSpec.scala seklinde kaydedelim:

import org.scalatest.{FlatSpec, Matchers}

class DovizKurSpec extends FlatSpec with Matchers {
  "DovizKuru api" should "32 farkli para birimini indirir" in {
    val kurlar = DovizKur.dovizurlariDownload()
    kurlar.size should be (32)
  }
}

simdi sbt shell'de iken test komutu ile testleri calistirabiliriz.

sbt:hello_sbt> test
[info] DovizKurSpec:
[info] DovizKuru api
[info] - should 32 farkli para birimini indirir
[info] Run completed in 964 milliseconds.
[info] Total number of tests run: 1
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pen
ding 0
[info] All tests passed.

Harika. Son olarak SansOyunlari projesindeki onceki amator testi FlatSpec kullanacak sekilde guncelliyorum ve AtYarisi icin de bir test ekliyorum. 

sansOyunlari/test/scala/AtYarisiSpec.scala:

import org.scalatest.{FlatSpec, Matchers}

class AtYarisiTahminSpec extends FlatSpec with Matchers {
  "AtYarisiTahmin" should "verilen atlardan birisini secmeli" in {
    val atlar = Array("at1", "at2")
    val at = AtYarisiTahmin.tahminEt(atlar)

    atlar.contains(at) should be (true)
  }
}

sansOyunlari/test/scala/SayisalTahminSpec.scala:

import org.scalatest.{FlatSpec, Matchers}

class SayisalTahminSpec extends FlatSpec with Matchers {
  "SayisalTahmin" should "6 farkli sayi tahmin etmeli" in {
    SayisalTahmin.tahminEt().size should be (6)
  }
}

tekrar sbt shell'de test komutunu calistirirsak, (root projede iken) tum projelerin testleri calisacak. Neden? cunku default root proje bir aggregator ve verilen tasklari tum alt projelere broadcast ediyor. Bunu da hatirlayalim. Hatta sbt'de tum testlerin paralel olarak calistirildigini da unutmayalim. Biz burada 4 test calistirdik ve bunlar paralel olarak calisti.

Ozellikle TDD yaparken, her bir test eklendikten sonra gidip test komutunu calistirmak biraz zor olabilir. Bu durumda tilda on eki ile sbt'nin her yeni test eklendiginde testleri calistirmasini saglayabiliyoruz. 

sbt:hello_sbt> ~test

seklinde. Buna sbt camiasinda triggered execution adi veriliyor ve mevcut olan butun tasklar icin bu sekil kullanim olasi, sadece test komutu degil. 

Dependency Organizasyonu
Simdi build.sbt dosyasinin son haline bakarsak hem sansOyunlari hem de api projesinin ayni scala-test dependency'sini kullandigini ve bizim bunun icin iki farkli hardcoded tanimlama yaptigimizi gorururz. Dependency'leri build.sbt doasyasi icerisinde tutmak bu anlamda bazi sikintilar doguruyoruz. Ama nerede tutacagiz o zaman? sbt, build.sbt icerisinde hicbir object ya da class tanimlanmasina izin vermez. Bu asamada project klasoru devreye giriyor. Daha once bu klasorde build ile alakali yardimci scala dosyarin barindigini soylemistik. project klasoru altina Dependencies.scala dosyasi olsuturuyoruz:

import sbt._

object Dependencies {

  val scalaTest = "org.scalatest" %% "scalatest" % "3.0.5"
  val requests = "com.lihaoyi" %% "requests" % "0.6.5"
  val scalaXml = "org.scala-lang.modules" %% "scala-xml" % "1.2.0"

  val commonDependencies: Seq[ModuleID] = Seq(scalaTest % Test)
  val apiDependencies: Seq[ModuleID] = Seq(requests, scalaXml
    ) ++ commonDependencies

  val sansOyunlariDependecies: Seq[ModuleID] = commonDependencies
}

Ve build.sbt'de bu dosyayi direk kullanabiliyoruz:

name := "hello_sbt"
version := "0.1"
scalaVersion := "2.13.4"

val sansOyunlari = project.settings(
  libraryDependencies ++= Dependencies.sansOyunlariDependecies
)

val api = project.settings(
  libraryDependencies ++= Dependencies.apiDependencies
)

Gayet seksi. build.sbt dosyasini sisirmeden, redundant tanimlamalar yapmadan bu sekilde build tanimlamalarini yapabiliriz. Hatta build.sbt daha da fazla sistikce, belirli kisimlari farkli dosyalara extract edip katmanli bir yapi ortaya cikarabiliriz.  

Proje Bagimliliklari
sansOyunlari projesinin api projesini kullanabilmesini saglamak gerekiyor. Bunun icin de build.sbt'de, proje tanimalasinda dependsOn metodunu kullaniyoruz:

lazy val sansOyunlari = project
  .dependsOn(api)
  .settings(
  libraryDependencies ++= Dependencies.sansOyunlariDependecies)

Ozet
    - Harici kutuphane ekleme
    - build.sbt'yi harici scala dosyalari ile genisletme
    - Proje bagimliliklari tanimlama
     



Yorumlar

Bu blogdaki popüler yayınlar

Python'da Multithreading ve Multiprocessing

Threat Modeling 1

Encoding / Decoding