GitBucket 4.10をリリースしました

Scalaで実装されたオープンソースのGitサーバ、GitBucket 4.10をリリースしました。

https://github.com/takezoe/gitbucket/releases/tag/4.10

Scala 2.12、Scalatra 2.5、Slick 3.2へのバージョンアップ

Scalaのバージョンを2.12にアップデートしました。これに伴ってフレームワークもScalatra 2.5、Slick3(3.2.0-RC1)にバージョンアップされています。

特にSlickに関してはこれまで使用していたSlick2.xからの変更が大きすぎるため、blocking-slickというSlick3上でSlick2互換のAPIを利用できるようにするライブラリを開発し、これを利用する形になっています。

これに伴って既存のほぼ全てのプラグインはGitBucket 4.10では動作しなくなってしまうものと思われます。

Slickを使用していないプラグインであればプラグインのリコンパイルだけで済むはずですが、Slickを使用しているプラグインは若干コードを書き換える必要があります。プラグイン開発者の方はこちらのプルリクエストを参考にGitBucketのバージョンアップへの対応をお願いできればと思います。

リポジトリビューアでファイルサイズを表示

リポジトリビューアのファイルの内容を表示するビューにファイルサイズも表示するようになりました。

f:id:takezoe:20170222235502p:plain

この他にも様々な改善やバグフィックスを行っています。詳細についてはIssueの一覧をご覧ください。

Playでいい感じにZipkinするライブラリを公開しました

PlayでZipkinでトレースするライブラリには以下のようなものがあるのですが、どれも用途が限られていたり、Playとのインテグレーションが不十分だったりします。

そんな背景もあり、同僚がPlayでいい感じにZipkinを利用できるようにするためのライブラリを社内向けに作成していたので、これをエンハンスしてOSSとして公開してみました。

github.com

まずはbuild.sbtに以下の依存関係を追加します。

libraryDependencies ++= Seq(
  "jp.co.bizreach" %% "play-zipkin-tracing-play25" % "1.0.0"
)

さらにapplication.confに以下のような設定を追加します。

play.http.filters=filters.Filters

trace {
  service-name = "zipkin-api-sample"

  zipkin {
    host = "localhost"
    port = 9410
    sample-rate = 0.1
  }
}

zipkin-trace-context {
  fork-join-executor {
    parallelism-factor = 20.0
    parallelism-max = 200
  }
}

play.modules.enabled  += "jp.co.bizreach.trace.play25.module.ZipkinModule"

filter.FiltersZipkinTraceFilterを追加します。

package filters

import javax.inject.Inject
import jp.co.bizreach.trace.play25.filter.ZipkinTraceFilter
import play.api.http.DefaultHttpFilters

class Filters @Inject() (
  zipkinTraceFilter: ZipkinTraceFilter
) extends DefaultHttpFilters(zipkinTraceFilter)

ここまでで準備完了です。コントローラーでは以下のようにWSClientの代わりにTraceWSClientをDIして使用することでAPI呼び出しのトレースを行うことができます。

package controllers

import play.api.mvc.{Action, Controller}
import play.api.libs.json.Json
import jp.co.bizreach.trace.play25.{TraceWSClient, ZipkinTraceService}
import jp.co.bizreach.trace.play25.implicits.ZipkinTraceImplicits
import scala.concurrent.ExecutionContext
import javax.inject.Inject

class ApiController @Inject() (ws: TraceWSClient)
  (implicit val tracer: ZipkinTraceServiceLike, val ec: ExecutionContext)
    extends Controller with ZipkinTraceImplicits {

  def test = Action.async { implicit request =>
    ws.url("hello-api-call", "http://localhost:9992/api/hello")
      .get().map { res => Ok(res.json) }
  }

}

また、ZipkinTraceServiceLikeのメソッドを使用してAPI呼び出し以外の処理のトレース情報を取得することも可能です。

// 同期処理のトレース
def test1 = Action { implicit request =>
  tracer.trace("sync"){
    println("Hello World!")
    Ok(Json.obj("result" -> "ok"))
  }
}

// Futureによる非同期処理のトレース
def test2 = Action.async { implicit request =>
  tracer.traceFuture("async"){
    Future {
      println("Hello World!")
      Ok(Json.obj("result" -> "ok"))
    }
  }
}

なお、このライブラリは諸事情によりPlay 2.3〜2.5に対応しています。バージョンのよって利用方法が少々異なりますので詳細については各バージョン向けのREADMEを参照してください。

このライブラリはBrave 4(Java向けのZipkinクライアントライブラリ)を使用して実装されています。Brave 4はBrave 3からAPIが大幅に変更されているので、このライブラリはBrave 4の使い方の参考のもなるのではないかと思います。

JDBCレイヤでDBのシャーディングを行うsharding-jdbcを試してみた

DBのデータ量が増えてきた場合の対策の1つとしてユーザIDなどをキーにデータベースを分割するシャーディングと呼ばれる手法があります。これをJDBCのレイヤで実現してしまうsharding-jdbcというライブラリを見つけました。

github.com

sharding-jdbcは中国のdangdang(当当)というEC大手企業が開発したOSSで、SQLをパースし、SQLに含まれるシャードキーを抽出して接続先のデータベースや、参照するテーブルを切り替えてくれるというものです。

使ってみる

まずはpom.xmlに以下の依存関係を追加します。

<dependency>
    <groupId>com.dangdang</groupId>
    <artifactId>sharding-jdbc-core</artifactId>
    <version>1.4.1</version>
</dependency>

今回は簡単な例ということで、COMPANY_IDというカラムをシャードキーに接続するデータベースを切り替えてみます。以下のような感じでデータソースを準備します。

// 接続先のデータソースを作成
Map<String, DataSource> dataSources = new HashMap<>();
dataSources.put("ds_0", createDataSource(...));
dataSources.put("ds_1", createDataSource(...));
DataSourceRule dataSourceRule = new DataSourceRule(dataSources);

// テーブルごとのルールを作成
TableRule companyTableRule = TableRule.builder("COMPANY")
  .dataSourceRule(dataSourceRule)
  .build();

// シャーディングのルールを作成
ShardingRule shardingRule = ShardingRule.builder()
  .dataSourceRule(dataSourceRule)
  .tableRules(Arrays.asList(companyTableRule, deptTableRule))
  .databaseShardingStrategy(new DatabaseShardingStrategy("COMPANY_ID", new CompanyIdShardingAlgorithm()))
  .build();

DataSource dataSource = ShardingDataSourceFactory.createDataSource(shardingRule);

CompanyIdShardingAlgorithmが接続先データベースを決定するクラスになります。シャードキーに使用するカラムは=BETWEENINでの検索しか行うことができません。ShardingAlgorithmではこれらの検索方法に対応したメソッドを実装する必要があるのですが、今回は=での検索しか行わないのでdoEqualSharding()メソッドのみ実装しています。

public class CompanyIdShardingAlgorithm implements SingleKeyDatabaseShardingAlgorithm<String> {

  @Override
  public String doEqualSharding(Collection<String> availableTargetNames, ShardingValue<String> shardingValue) {
    switch(shardingValue.getValue()){
        case "bizreach":
          return "ds_0";
        default:
          return "ds_1";
    }
  }

  @Override
  public Collection<String> doInSharding(Collection<String> availableTargetNames, ShardingValue<String> shardingValue) {
    throw new UnsupportedOperationException();
  }

  @Override
  public Collection<String> doBetweenSharding(Collection<String> availableTargetNames, ShardingValue<String> shardingValue) {
    throw new UnsupportedOperationException();
  }
}

あとは作成したデータソースからgetConnection()メソッドを取得してSQLを実行すればシャードキーに応じたDBに対してSQLが発行されます。

その他の機能

sharding-jdbcにはこの他にも以下のような機能があります。

  • シャードキーで、接続するデータベースではなく参照するテーブルを切り替える機能(SQLがリライトされて実行されます)
  • 一意なIDを発行する機能(シャードをまたぐ自動採番のカラムに値を入れるのに使用します)
  • クエリが複数のシャードにまたがる場合にORDER BYGROUP BYを処理する機能(マージ処理と呼ぶようです)

一方で前述の通りシャードキーに対する検索条件に制限がある、異なるシャードをUNIONできないなどの制約もあります。正常にルーティングが行われないSQLの場合でも特にエラーにはならず、SQL中で最初に検出されたシャードキーを使ってルーティングされているように見えましたが、できればエラーになってくれると嬉しいですね。

まとめ

sharding-jdbcJDBCのレイヤでシャーディングを解決してくれるため、既存の様々なORMやデータベースアクセスライブラリをそのまま利用できるという大きなメリットがあります。利用可能なSQLに制限はあるものの、そもそもシャードキーに対してリッチな検索を行う必要はないでしょうし、複雑なレポーティングが必要といったケースでなければさほど困ることもなさそうです。

現時点でシャーディングが不要だとしても、将来的にデータの増加に伴ってDBのシャーディングを検討する可能性があるようであれば、最初からこれを入れておくという手も考えられるのではないでしょうか。

JGitへのコントリビュートとGerritを使ってみた感想

GitBucketではバックエンドの実装にJGitを使っているのですが、gitリポジトリをzipファイルとしてダウンロードした際に、同じコミットでもダウンロードしたzipファイルのハッシュが同じ値にならないというレポートがあり、調べたところJGitのarchiveコマンド部分の実装に問題があることがわかったため*1、JGitにフィードバックを行ってみました。

こちらがGitBucketのユーザさんからいただいたレポートです。

github.com

JGitへのコントリビューション方法

さて、JGitはオープンソースプロジェクトではありますが、Eclipse Foundationで開発されているものなので、Apache Software Foundationと同じくコントリビューションは面倒そうな予感がします。GitHubリポジトリがあるのですがどうやらこちらはミラーのようで、プルリクエストを送っても受け付けてもらえないようです。

github.com

調べてみたところ、コントリビューター向けのガイドがありました。どうやらGerritでコードレビューの依頼をする必要があるようです。もちろん事前にアカウントを作成してCLAへの同意などを行っておく必要があります。*2

Gerritによるコードレビュー

Gerritは1コミット=1プルリクエストのような感じでレビューを行います。以下のようにrefs/for/masterにpushすることでレビュー依頼が登録されます。

git push ssh://username@git.eclipse.org:29418/jgit/jgit.git HEAD:refs/for/master

レビューを受けて修正する場合は元のコミットにamendするなどして新しいコミットをpushするわけですが、以前のコミットと紐づけるためにコミットメッセージにChange-Idを含めておく必要があります。また、修正者を明示するためにアカウントの情報をSigned-off-byに含めておく必要もあります。JGitのGerritではこれらのフィールドが含まれていないとpushがリジェクトされました。

実際にpushしたコメントはこんな感じです。

Set commit time to ZipArchiveEntry

Archived zip files for a same commit have different MD5 hash because
mdate and mdate in the header of zip entries are not specified. In
this case, Commons Compress sets an archived time.

In the original git implementation, it's set a commit time:
https://github.com/git/git/blob/e2b2d6a172b76d44cb7b1ddb12ea5bfac9613a44/archive.c#L378

By this fix, archive command sets the commit time to ZipArchiveEntry
when RevCommit is given as an archiving target.

Change-Id: I30dd8710e910cdf42d57742f8709e9803930a123
Signed-off-by: Naoki Takezoe <takezoe@gmail.com>

Change-Idはコミットフックで自動的に付与することができます。フックスクリプトは通常Gerritサーバで提供されており、JGitの場合は以下のようにしてローカルリポジトリにインストールすることができました。

scp -p -P 29418 username@git.eclipse.org:hooks/commit-msg .git/hooks/

これで修正をローカルリポジトリにコミットした時点でコミットメッセージにChange-Idが自動的に追加されます(修正時など、すでにChange-Idが付与されている場合は何も行いません)。

レビューで指摘があった場合は修正して再度pushすると新しいpatch setが作成されます。前述の通り、Gerritは1コミットが1プルリクエストに相当するのですが、patch setによって1コミット内のレビューや変更の履歴を管理することができます。もちろん途中のpatch setからやり直したりということもできます。

ちなみに以下が今回フィードバックした修正のレビューです。JGit 4.7でマージしていただけるようです。

https://git.eclipse.org/r/#/c/91116/

f:id:takezoe:20170218214029p:plain

感想

JGitは目立たないライブラリではありますが、JVMベースの様々な開発者向けツールで使用されており、もちろんGitBucketでも活用させていただいているので、ちょっとした修正ではありますが貢献できて嬉しいところです。

また、Gerritは初めて使ったのですが、綺麗なコミット履歴を保ちつつ、その過程も管理できるという点はメリットだと感じました。ただ、Gitの操作が複雑なだけでなく*3、Web UIも非常にわかりづらく初見だと何をしていいのかさっぱりわからないという敷居の高さがあります。

最近は企業が主体となって開発しているOSSや、ASFのプロダクトなどでもGitHubでのプルリクエストを受け付けてくれるものが増えてきていますが、golangなどGerritを使ってレビューを行っているプロジェクトもあります。今回の作業はGerritの使い方も含め勉強になりました。

Gerritの使い方を手取り足取り教えてくれた弊社ターミナル部部長である@tanacasinoに感謝の意を表したいと思います。ありがとうありがとう。

*1:zipファイルのエントリにファイルのタイムスタンプを設定していなかったため、zipファイル作成時のタイムスタンプが使用されてしまっていたのが原因でした。

*2:GerritはGoogleが開発したGitベースのコードレビューシステムで、JGitを使用して実装されています。

*3:通常の操作を行うだけでもそれなりのGit力を要求されますし、patch setの操作や複数のレビューが相互に関連する大きな修正の場合はかなり複雑な操作が必要になりそうです。

GitHubをパトロールするためのWebアプリを作ってみた

GitHubのトップページではウォッチしているリポジトリのアクティビティがタイムラインのように表示されるのですが、ウォッチしているリポジトリ数が多いと見落としてしまいがちです。そこで、自分がウォッチしているリポジトリの中から選択したリポジトリのアクティビティのみ表示するタイムラインリーダーのようなものを作ってみました。

f:id:takezoe:20170218005503j:plain

こんな感じで選択したリポジトリのアクティビティを一覧表示し、未読管理も行うことができます。キーボードショートカットも装備しているのでRSSリーダーのような感じで使うことができます。

GitHubのタイムラインはRSSフィードも提供されているのですが、リポジトリごとにRSSリーダーに登録するのは面倒ですし、GitHubAPIから取得できる情報と比べるとRSSフィードに含まれる情報量は多くはありません。このアプリケーションではウォッチしているリポジトリの中から選択するだけで購読できたり、イシューのMarkdownをHTMLに変換して表示するなどGitHubに最適化されています。

今のところコードを公開する予定はないのですが、このアプリケーションはもちろんScalaで書いており、サーバサイドはRestyを使っています。データは最初はElasticsearchに入れていたのですが、低スペックなVPSだとElasticsearchを動かすのが厳しいこと、個人で使うものなのでそれほどのデータ量にならないだろう(&データが増えたら消せばよい)ということで途中からPostgreSQLに移行しました。

フロントはVue.jsを使っているのですが、HTML1枚なのでwebpackなどは使わずベタ書き+WebJarsで済ませています。機能が増えてきたらコンポーネント化のためにwebpackを導入する必要が出てくるかもしれません。Vue.jsはシンプルなAngular1という感じでちょっとしたものを作るには非常に便利でいいですね。趣味で作っているものなのでフロントをScala.jsで書いてみるのも面白いかもしれません。

まだ不足している機能も多いので少しずつ手を入れていこうと思いますが、ひとまずこれで気になるリポジトリのアクティビティを見逃すことはなくなりそうです!

シンプルなScala用ビルドツール「cbt (Chris's Build Tool)」を試してみる

Slickのコミッタとして有名なJan Christopher Vogtさんが作っているcbt (Chris’s Build Tool) というScala用のビルドツールがあります。*1

github.com

昨年参加したScala Days NewYork 2017でもセッションがあり、興味本位で参加したのですが、sbtのdisなどもありなかなか楽しい発表でした。*2

www.youtube.com

cbtの特徴

Scalaにはsbtという標準的なビルドツールが存在するわけですが、非常に難解なsbtと異なり、cbtは既存のScalaの知識だけでカスタマイズができるという大きな特徴があります。

たとえばコンパイルの前後に処理を挟む場合、compileメソッドをオーバーライドして前後に独自のScalaコードを記述し、super.compileを呼び出せばよいだけです。タスクはただのメソッドなので別のタスクを呼ぶ場合もメソッドを呼び出すだけですし、以下のようにメソッドを定義するだけで独自タスクも簡単に定義することができます。

class Build(val context: Context) extends BaseBuild{
  ...
  def hello = println("Hello CBT!!")
}

cbtを使ってみる

まずはGitHubリポジトリをクローンし、リポジトリのルートディレクトリに環境変数PATHを通します。*3

$ cd ~/
$ git clone https://github.com/cvogt/cbt.git
$ export PATH=$PATH:~/cbt

続いて適当なディレクトリを掘って動作を確認してみます。cbt toolsはファイルの雛形などを生成する便利ツールです。

$ mkdir my-project
$ cd my-project
$ cbt tools createMain

以下のような内容でMain.scalaが生成されます。

object Main{
  def main( args: Array[String] ): Unit = {
    println( Console.GREEN ++ "Hello World" ++ Console.RESET )
  }
}

実行してみます。

$ cbt run
Compiling to /tmp/my-project/target/scala-2.11/classes
[warn] Pruning sources from previous analysis, due to incompatible CompileSetup.
[info] Compiling 1 Scala source to /tmp/my-project/target/scala-2.11/classes...
[info] Compile success at 2017/02/15 14:19:40 [0.457s]
Hello World

このようにコンパイルや実行だけであればビルドファイルを作らなくても実行することができます。

ビルドファイルの作成

ビルドの設定などを行いたい場合はビルドファイルを作成する必要があります。

$ cbt tools createBuild

以下のような内容でbuild/build.scalaが生成されます。コメントでライブラリやマルチプトジェクト構成の場合の設定方法などが説明されています。

import cbt._
class Build(val context: Context) extends BaseBuild{
  override def dependencies =
    super.dependencies ++ // don't forget super.dependencies here for scala-library, etc.
    Seq(
      // source dependency
      // DirectoryDependency( projectDirectory ++ "/subProject" )
    ) ++
    // pick resolvers explicitly for individual dependencies (and their transitive dependencies)
    Resolver( mavenCentral, sonatypeReleases ).bind(
      // CBT-style Scala dependencies
      // ScalaDependency( "com.lihaoyi", "ammonite-ops", "0.5.5" )
      // MavenDependency( "com.lihaoyi", "ammonite-ops_2.11", "0.5.5" )

      // SBT-style dependencies
      // "com.lihaoyi" %% "ammonite-ops" % "0.5.5"
      // "com.lihaoyi" % "ammonite-ops_2.11" % "0.5.5"
    )
}

デイレクトリレイアウトはsbtと同様で、デフォルトではsrc/main/scalasrc/test/scalaなどにソースコードを配置することができます。

jarファイルにパッケージングしたいのでベーストレイトをPackageJars変更します。namegroupIdversionという3つのメソッドを実装する必要があります。

import cbt._
class Build(val context: Context) extends PackageJars{
  override def name = "my-project"
  override def groupId = "com.github.takezoe"
  override def version = "1.0.0"
  ...
}

このようにしておくとsbt packageでjarファイルを作成することができます。

まとめ

実際にcbtを使ってみた感じ、sbtと比べるとカスタマイズしやすそうで好印象です。ただ、現実的にはやはり以下のような点がネックになってきます。

  • IDEのサポートが期待できない
  • 既存のsbtプラグインが利用できない

特に既存のsbtプラグインが利用できないというのは大きな欠点です。例えばサーブレットベースのWebアプリケーションを開発するためのxsbt-web-pluginやsbtプラグインでテンプレートをScalaコードにコンパイルするTwirlなども使えません。もちろん開発環境をがっつりsbtに依存しているPlayアプリケーションの開発を行うこともできません。

現時点ではネタ感を拭えないのが正直なところですが、開発は活発に続けられていますし*4、選択肢があることは重要だと思うので、Chrisさんには是非とも頑張っていただきたいところです。

*1:自分の名前をつけてしまうセンスはどうなのかという話はここでは置いておきます。

*2:動画だけでなくスライドも公開されています。 https://docs.google.com/presentation/d/1dZ7f6cETFecfjiqQvVsYtXdUQxZzan8gS_XJZoWbF7M/

*3:あわせてnailgunを入れておくと起動が速くなったり、gpgを入れておくとpublishSignedタスクを実行できるようになったりするようです。Macの場合はbrewで入れておくとよいでしょう。

*4:Chrisさんのモチベーションがいつまで続くのかという不安はありますが…。

Scala Days 2017のタイムテーブルが公開されました

ここ数年は毎年アメリカとヨーロッパで開催されているScala Days、一昨年はサンフランシスコとアムステルダム、昨年はニューヨークとベルリンでしたが今年はシカゴとコペンハーゲンで開催されるようです。

www.lightbend.com

以下、シカゴとコペンハーゲンそれぞれのタイムテーブルです。

タイムテーブルをざっと眺めて気になったセッションを挙げてみました。個人的な趣味だったり、内容より人で見ているところもあるのでだいぶ偏りがありますが…。

Scala.jsカウボーイLi HaoyiさんのAnatomy of a full-stack Scala/Scala.js Web Appも気になりますね…。Scala.jsどうなんですかね…。

なお、コペンハーゲンではScala Daysの日程に合わせてTypelevel Summitも開催されるようです。Scala Daysの翌日なのであわせて参加される方も多いのではないでしょうか。

typelevel.org