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.thriftとshared.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に実装を作っていく
実装はテキトーでとりあえずping
とadd
以外はちゃんと実装してない。
クライアントとサーバーは別で起動するため、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'