ScalatraのWebサイトをGitHub Pagesに移行しました

ScalatraのWebサイトはこれまでIvanが管理している自前サーバ(?)で運用されていたので時折ダウンしていたり、Jenkinsが落ちているとサイトのビルドができなったりとトラブルが絶えませんでした。

ここ数ヶ月もScalatraの最新版である2.5のドキュメントがサイトに反映できていなかったり、サイトが長期間ダウンしていたりと、さすがにこの状況はいかがなものかということで以前から話には上がりつつ一向に進んでいなかったGitHub Pagesで運用する作戦を決行することにしました。

というか自分が手をつけようと思ったのは吉田さんのこのツイートがきっかけだったのですがw

これまでのサイトはJekyllで構築されていたのですが、まず、ブランチで作業されていたhugo版に自分の方で手を入れてテーマやレイアウト、リンク切れなどを修正し、サイトとして最低限公開できる状態に持って行きました。hugoのビルドやScaladocの生成はコミッタの一人である@dozedTravis職人を買って出てくれ、GitHub Pagesにデプロイできるようになり、最終的に@rossabakerDNSを切り替えてもらいscalatra.orgをGitHub Pagesに向くようにするところまで、作業を開始してから約1週間というところでした。

海外の方とOSSで共同開発をしていてもリアルタイムに協力して作業をするということはなかなかないのですが(時差の関係もありますが…)、日々gitterでやり取りしながら作業が進んでいくのは面白い体験でした。Scalatraのコミッタ間のやり取りは以前はIRCが使われていたのですが、gitterを使い始めてから随分敷居が低くなりましたし、こういったやり取りもしやすくなりましたね。

なにはともあれこれでしょっちゅうサイトが落ちてるなんてことはなくなるはずです。今回hugoを触るのも初めてだったのですが、サイトの復旧優先で作業したためまだいろいろおかしなところがあると思います。今後少しずつ直していこうと思います。

Re-engineering Legacy Software

クリスさんがManningで執筆された書籍です。少し前に購入していたのですが、きちんと読めていなかったので英語の本を読む練習を兼ねて読み直してみました。

Re-engineering Legacy Software

Re-engineering Legacy Software

レガシーコードをいかに改善するかという、クリスさんの経験に基づく(?)現場感溢れる内容で、読んでいると勇気を与えられる反面、こちらの心まで痛くなってくる部分が多々ありますw

もちろん全てのシチュエーションに当てはまるものでもないとは思いますが、リファクタリングの価値を説明する際のテクニックや、リファクタリングするかリライトするかの判断基準や実際の進め方、モノリシックなアプリケーションを分割する際のパターンやメリット・デメリットなど、頭ではなんとなくわかってはいるのだけど自分の中できちんと説明できる言葉を持っていなかった部分が言語化されており、今後の判断や説明の際に1つの拠り所になるのではないかと感じました。

コードは書いた瞬間からレガシーになるとも言われますが、そう考えると多くのソフトウェアエンジニアにとって、程度の差こそあれ日常的な業務の大部分はレガシーコードとの戦いということになるのではないでしょうか。レガシーコードと戦っているソフトウェアエンジニアの皆さんには是非読んでみていただきたいです。英語も非常に読みやすいですし、何より書かれてある内容が「あるある」だったり、「なるほど」と思うことばかりで飽きずに読み進めることができました。

ちなみに日本語版も出ています。原著も読みやすいとはいえ、コード主体の技術書と違って摘み読みしづらいということもありますし、やはり英語だとどうしても読むのに時間がかかってしまうのではないかと思います。せっかく日本語版が出ていますので、時間のない方はこちらがオススメです。

レガシーソフトウェア改善ガイド

レガシーソフトウェア改善ガイド

ScalaMatsuri 2017に参加しました

今年もお台場で開催されたScalaMatsuri 2017ですが、今回はスポンサーとしてだけではなく、個人でスピーカーとしても参加させていただき、Scala Warriorについてお話しさせていただきました。

www.slideshare.net

前がScala.js作者のセバスチャンさんの発表で、最近の機能の紹介として取り上げたCommonJSモジュールのあたりなどは丸かぶりしてしまっており、もう少し違った切り口の発表にすればよかったかなと若干反省しています。

また、セバスチャンさんから質問をいただいたり、セッションの後で直接ご挨拶をさせていただいたのですが、Dotty.jsについて質問したところいろいろ話してくださったのですが、2/3くらい何を言ってるのかわからず、相変わらず英語力の不足を感じました。

ScalaMatsuri全体としては、昨年はセッションにやや偏りがあるなぁと感じたのですが、今年はバランスもよく、初日に聴講したセッションはどれも面白いものばかりでした。また、スピーカーとして来日されていたEPFLやLightbendの方々をはじめ、国外からの参加者も多く、ScalaMatsuriが名実共にヨーロッパやアメリカで開催されるカンファレンスに劣らない国際的なカンファレンスに育ちつつあると感じました。

二日目はお昼過ぎから参加したのですが、疲労もあり、セッションは聴講せずいろんな方々とお話ししていました。特にクリスさん(昨年NYでのScala Daysでもお会いしました!)からは面白い話をたくさん伺うことができました。以下の本をおすすめされたので読んでみようと思いますw

Type-driven Development With Idris

Type-driven Development With Idris

土日2日間の開催は体力的に厳しいところもありますが、セッションを聴講するだけでなく、久しぶりにお会いする方々とゆっくり話したり、アンカンファレンスで小ネタを披露したりと、様々な楽しみ方ができるのはScalaMatsuriならではのスタイルといえるかもしれません。

スタッフの皆様、スピーカーの皆様、スポンサーの皆様、そして参加者の皆様、お疲れ様でした。来年のScalaMatsuriまでまたScalaを書いていきましょう。

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の操作や複数のレビューが相互に関連する大きな修正の場合はかなり複雑な操作が必要になりそうです。