scroogeを使ってみる

概要

下調べのため、Scalaでthriftを使ってみる。 Apache Thrift公式でScalaはサポートされてないので、twitter/scroogeを使うのが一般的らしい。

環境

Max OSX 10.10.5 Yosemite sbt 0.13.13

下準備

sbtのインストール

$ brew install sbt

sbtでプロジェクトを作る

`$ sbt new sbt/scala-seed.g8

build.sbtにscroogeの依存関係を書く

Scalaのバージョンとライブラリのバージョンについては The Central Repository Search Engine で調べる。 Scala2.12を使いたければ、 scrooge-coreは4.12.0以降、finagle-thriftは6.41.0以降にしないといけないが 2.12だとコンパイルが通らないので、2.11を使う。scrooge-coreとfinagle-thriftの最新版を使うと下のような エラーが出るので、バージョンはscroogeのドキュメントに合わせておく。

[error] /tmp/thrift2/target/scala-2.11/src_managed/main/tutorial/Operation.scala:50: class EnumUnknownOperation needs to be abstract, since method originalName in trait ThriftEnum of type=> String is not defined
[error]   case class EnumUnknownOperation(value: Int) extends tutorial.Operation {

scroogeの公式のドキュメントではlibthriftのversionが0.9.2になってるけど、sbt compileを実行すると evictedのwarningが出るので0.5.0-1にしている。

lazy val root = (project in file(".")).
  settings(
    inThisBuild(List(
      organization := "com.example",
      scalaVersion := "2.11.1",
      version      := "0.1.0-SNAPSHOT"
    )),
    name := "Hello",
    libraryDependencies ++= Seq(
      "org.apache.thrift" % "libthrift" % "0.5.0-1",
      "com.twitter" %% "scrooge-core" % "4.6.0" exclude("com.twitter", "libthrift"),
      "com.twitter" %% "finagle-thrift" % "6.34.0" exclude("com.twitter", "libthrift")
    )
  )
project/plugins.sbtの編集

project/plugins.sbtを作る maven-centralやmaven.twttr.comにscrooge-sbt-pluginの4.5.0はないように見えるけど どこから持ってきてるのだろう…

resolvers += "Twitter Repository" at "http://maven.twttr.com"
addSbtPlugin("com.twitter" % "scrooge-sbt-plugin" % "4.5.0")
thriftファイルをsrc/main/thriftにおく

サンプルのthriftファイルは Apache Thrift - Index of tutorial/tutorial.thriftshared.thriftを使う。

thriftからScalaのコードを生成

sbt compileを実行すると、target/scala-2.11/main/の下にコードが生成される。

thriftを使ったコードを書いていく

クライアント側

src/main/scala/example/Hello.scalaができてると思うのでクライアントのコードに書き換えていく thriftでクライアントを作る時はThrift.client.newIfaceを使うらしいので、クライアントを作る。 あとはthriftで設定したメソッドが使えるのでそれを使う。

package example

import com.twitter.finagle.Thrift
import com.twitter.util.{Await, Duration}
import tutorial.Calculator
import tutorial.Calculator.Calculate


object Hello extends Greeting with App {
  println(greeting)
  val client = Thrift.client.newIface[Calculator.FutureIface]("localhost:9090")
  client.ping()
  print(Await.result(client.add(1, 2), Duration.fromSeconds(3)))
}

trait Greeting {
  lazy val greeting: String = "hello"
}
サーバー側

自動生成されたCalculatorを継承して、CalculatorImplに実装を作っていく 実装はテキトーでとりあえずpingadd以外はちゃんと実装してない。

クライアントとサーバーは別で起動するため、Appを継承してmain部分を作っておく thriftからサーバを作るにはThrift.server.serveIfaceを使って、引数に実装したCalculatorImplを渡してあげればよい。

package example

import java.net.InetSocketAddress

import com.twitter.finagle.Thrift
import com.twitter.util.{Await, Duration, Future}
import shared.SharedStruct
import tutorial.{Calculator, Work}

class CalculatorImpl extends Calculator[Future] {
  /**
    * A method definition looks like C code. It has a return type, arguments,
    * and optionally a list of exceptions that it may throw. Note that argument
    * lists and exception lists are specified using the exact same syntax as
    * field lists in struct or exception definitions.
    */
  override def ping(): Future[Unit] = {
    println("ping")
    Future.value("ping")
  }

  override def add(num1: Int, num2: Int): Future[Int] = {
    Future.value(num1 + num2)
  }

  override def calculate(logid: Int, w: Work): Future[Int] = {
    Future.value(0)
  }

  /**
    * This method has a oneway modifier. That means the client only makes
    * a request and does not listen for any response at all. Oneway methods
    * must be void.
    */
  override def zip(): Future[Unit] = {
    Future.value(null)
  }

  override def getStruct(key: Int): Future[SharedStruct] = {
    Future.value(null)
  }
}

object CalculatorServer extends App {
  val server = Thrift.server.serveIface("localhost:9090", new CalculatorImpl)
  Await.ready(server)
}

実装が終わったら忘れずにコンパイル sbt compile

実行

sbt runMainで実行していく。

sbt 'runMain example.CalculatorServer'
sbt 'runMain example.Hello'