読者です 読者をやめる 読者になる 読者になる

冥冥乃志

ソフトウェア開発会社でチームマネージャをしているエンジニアの雑記。アウトプットは少なめです。

follow us in feedly

Twitter4S 2.1.0をリリースしました

また半年位開いてしまいましたが、いつの間にか3.0.5までTwitter4Jのバージョンが上がっていたこともあり、*1Twitter4Sのバージョン2.1.0をリリースしました。今回はTwitter4Jのバージョンアップ追随以外にもいくつかトピックがあるので、そちらもあわせてご紹介します。

リポジトリはこちら -> https://github.com/Shinsuke-Abe/twitter4s

リリースノート

Version 2.1.0での対応は以下です。

Twitter4J 3.0.5対応

Twitter4J 3.0.4および3.0.5で追加になったAPIメソッドを追加しました。Twitter4Jのインタフェースに大きな変更がないので、Twitter4Sのインタフェースにも大きな変更はありません。
ちょっと便利なメソッド追加としては、Twitter4J 3.0.4で追加されたリソースインタフェースへのアクセスメソッドがあります。これは、Twitterクラスに定義されたtimeliensやtweetsといったメソッドで、各APIメソッドを実装しているインタフェースとして自分のクラスを返します。Twitter4JのTwitterクラス(Twitter4Sも同じ作りですが)って全てのAPIメソッドのインタフェースを実装しているので、IDEとかでメソッドのリストが一杯出てきて目的のメソッドを探すのがしんどいので、地味ですが非常に嬉しい機能です。
こんなに嬉しい機能なので、Twitter4Sにも実装しない手はありません。Twitter4JではTwitterクラスにAPIメソッドのインタフェースを実装していますが、Twitter4Sでは実装traitとTwitterクラスをなるべく分離した状態を維持したかったため*2、以下のような実装にしています。

// Twitter.scala
case class Twitter(twitter4jObj: twitter4j.Twitter) extends TwitterBase {
  // 省略
  def timelines = TimelinesResourcesBinder(this)
}

// ResourcesBinder.scala
trait ResourcesBinder[ApiResourcesInterface] {
  def apply(self: Twitter)(implicit ct: ClassTag[ApiResourcesInterface]): ApiResourcesInterface =
    if (ct.runtimeClass.isInstance(self)) self.asInstanceOf[ApiResourcesInterface]
    else bind(self)

  val bind: (Twitter) => ApiResourcesInterface
}

object TimelinesResourcesBinder extends ResourcesBinder[TimelinesResources]{
  val bind = (self: Twitter) => new Twitter(self.twitter4jObj) with TimelinesResourcesImpl
} 

リソースAPIを実装しているtraitをTwitterクラスにバインドするためのオブジェクトを作って、すでにmixinされていればキャストして返し、mixinされていなければmixinしたTwitterインスタンスを生成して返します*3
もう少しスマートな実装がないかな、と思っているところでそれは今後の課題にしたいと思っています。

(オレオレ)DSL機能追加

前回のリリース時に予告したDSLの実装を開始しました。まだ、ちょっとスマートなAPIレベルですが、これから機能追加をして充実させていきます。DSLの使い方についての詳細はGithubのREADMEをお読みください。
DSLを実装するにあたって、ライブラリとして考えたことは以下の2点です。

  • Twitter4Jと同じアクセス方法は残す
  • DSLと今までと同じアクセス方式が混在できるようにする

それと、自分の実装の興味としてScala 2.10系の機能でDSLを作るならどうするか、というものを考えてみたいと言うのもありました。実践プログラミングDSLではScalaのバージョンが2.9系だったため、サンプルに2.10で追加された機能は使われていません。2.10系のString ContextがDSLの実装と表現の面で面白い効果を発揮しないかなあ、という考えの元で実装しています。その結果、Twitter4Sの実装は、String Contextを中心とした組み立てになっています。個人的には、文字列に対する文脈を強固にするので、DSLとしては良い効果が出ているのではないかと思っています。

それから、本当ならパッケージオブジェクトに実装しているメソッド(getやsendなど)も括弧を省略したかったのですが、Scalaはレシーバを書かない形式だと括弧省略の形で書けないので諦めています。主語になるオブジェクトを返してレシーバを無理矢理作るということも考えましたが、DSLの主目的は自然言語にあわせることではないですし、本末転倒な感じがしてやめました。

バイナリをbintrayにpublish

ついにマッチョ仕様を脱しました。bintrayというdistributionのためのサービスを使ってライブラリを公開し、sbtのライブラリ依存性を解決できるようにしています。
sbtのライブラリ依存性を使う場合は、build.sbtに以下を追加して下さい。

resolvers += “bintray” at “http://dl.bintray.com/shinsuke-abe/maven”

libraryDependencies += “com.github.Shinsuke-Abe” %% “twitter4s” % “2.1.0"

ちなみに、Githubアカウントで連携することができて、bintrayは500MBまで公開可能です。まだ公式のドキュメントを良く読んでいませんが、maven centralへの連携も可能なようです。sonatypeへの英語メールなどが面倒な方は自作ライブラリの公開などにこちらを利用しても良いのではないでしょうか?

Bintray - Download Center Automation & Distribution w. Private Repositories

(内部のトピック)テストのmock化

前回リリース時にテストのmock化を進めたいという話をしていたかと思いますが、今回のリリースに向けた対応の中で、実装traitのメソッドは全てmockを使用したテストに変更しました。これで、どんなタイミングでも全てのテストを流すことができるようになりました。

(余談)bintrayへのpublishでハマったこと

本来ならハマる要素があまりないところだったのですが、ググって参考にした情報のターゲットが微妙に間違っていたことと、プラグインの併用時の注意事項と、bintrayの謎仕様にやられた感じです。

  • sbt-release-plubinとbintray-sbtプラグインをプロジェクトに追加
  • releaseタスクを動かすも、初回publish時に聴かれるcredential情報のプロンプトが出ずにpublish失敗
    • その他のタスクは上手く行っているので、新バージョン情報がSNAPSHOTで作成される(これが後々影響)
  • ググった結果、sbtプラグイン用のbintray publish設定に当たってしまい、sbtPlugin設定をtrueにしてしまう
  • publishタスクだけ実行してcredential情報を登録する
    • ただしsbtPlugin設定のせいで失敗する
  • sbtPluginはsbtプラグイン開発のための設定らしく、デフォルトでユーザとリポジトリが決まってしまうことが発覚
  • sbtPlugin設定を削除してpublishタスクを実行するものの、失敗
  • bintrayはSNAPSHOTバージョンをpublishできないという謎仕様があるらしい
  • SNAPSHOTを削除して再publish

何でしょうね、この薄氷ばかり選んで踏み抜きながら歩いている感じは。とりあえず、bintrayへのpublishが難しくないことはよくわかったので、次のpublishではこんなことは起こらないでしょう。

今後の展開

とりあえずDSLを表現力をもっと高めていきたいと思っています。Twitter4SがDSL中心に利用できるようにして機能を網羅していく、というのが当座の展開です。

*1:Twitter4S 2.0.0の時はTwitter4J 3.0.3対応

*2:コンパニオンオブジェクト経由でインスタンスを生成した場合は全ての実装traitがmixinされた状態です

*3:今思うと、メソッドではなくフィールドの方が良かったかもしれません