ScalaDays 2018 NewYorkの気になるセッション

ScalaDays 2018 NewYorkのタイムテーブルが公開されていましたので、完全に個人的な趣味で気になるセッションを列挙してみたいと思います

na.scaladays.org

  • Type-driven Development (Workshop) カンファレンスの前日と前々日行われるワークショップでは「Type-driven development」というワークショップがあります。これは…と思ったのですが、どうやらIdrisのワークショップというわけではないようですw 残念。
  • Networks and Types — the Future of Akka 今楽努さんことKonradさんは先日西新宿で開催されたReactive System Meetupでも発表されていたAkka Typedの話をされるようです。日本で先行して聞けたのはラッキーでした。
  • Slinky: a modern toolkit for modern apps ScalaDays常連スピーカーのShadajさんによるSlinkyというScala.js用のReactインターフェースの紹介。これまでもScala.js用のReact用ライブラリはいくつかありましたが、これは結構本気度を感じますがどうでしょうか。JSX相当の部分は独自のタグAPIを使うみたいなのでデザイナとの共同作業は難しそうですが…。
  • How we built tools that scale to millions of lines of code マクロでお馴染みEugene Burmakoさんですが、今回はTwitter社内でコードレビュー、コードブラウジングなどを行うツールを開発する際にScalaのセマンティックツールを大規模なコードベースに対応させるためにどのようにスケールさせたかという話。ちょっと想像がつかないのですが興味深いです。
  • Serverless Scala.js iOS/Android/Web apps from one code base Scala.jsとScala-Nativeを使って1つのScalaコードベースでWeb、iOSAndroid用のUIアプリを開発した経験を話すらしい。仕事でこんなことしたら確実に怒られが発生しそうですが、人の話を聞く分には楽しい気がします。
  • Meet bloop and get more productive with Scala Scala Centerの方々によるBloopのセッションもあります。以前このブログでも紹介したのですが、非常に微妙な立ち位置のツールのような気がするのですが、本気でやるつもりがあるのかどうか気になるところです。
  • Compiling Scala Faster with GraalVM Oracle社のChristianさんのセッションで、Graalを使ってScalaコンパイルを速くするという話らしい。
  • sbt 1 Yokotaさんとdwijnandさんによるsbt1のセッション。自分は参加できなかったのですが、YokotaさんはScalaMatsuriでもsbt1について話されていましたね。KonradさんのAkka Typedの話もそうですが、日本でも同じ話を聞ける機会があるというのは有難いことです。
  • ScalaQuest: the Scala adventure クラウドファンディングもしていたScala教育用ゲーム「ScalaQuest」の進捗報告とのこと。Scala Warriorと似たコンセプトのアプリケーションなので技術的なところに興味があります。
  • sttp: the Scala HTTP client that you always wanted! SoftwareMillのAdamさんによるセッションで、Scala業界にまた新たに爆誕してしまったHTTPクライアントsttpを使ったライブコーディングだそうです。いろいろ爆誕しがちなのはScala業界らしくていいと思います。ScalaSphereでビルドツール対決とかやって欲しい。
  • Twitter's quest for a wholly Graal runtime Twitter社におけるGraalの話。Graalで良いコードを生成することでコストを削減するという、Scalaで大規模な分散システムを運用しているTwitter社ならではの発表ですね。

全体的な傾向として、ことScalaDaysに関してはFreeモナドブームは完全に去った感じがあります。Graalのセッションが2つあったり、ここでは取り上げませんでしたがSparkのセッションが2つあったりと、Scalaエンタープライズ寄りな雰囲気が出てきたかなという気がしますが、一方でScala.jsやScala-Nativeも依然として注目度が高いようです。

最終兵器トラックポイント付きメカニカルキーボード「TEX Yoda II」レビュー

f:id:takezoe:20180312122502j:plain
最終兵器「TEX Yoda II」

初代を買い逃してから早数年、2代目が発表され入手できる日を心待ちにしていたのですが、先日ようやくゲットすることができました。数日使ってみてのレビューです。

Massdropで組み立てキットも販売されていましたが、面倒なので以下のサイトから赤軸、バックライトなしの組み立て済みモデルをオーダーしました。到着まで3週間ほどかかりました。また、関税がかかったため受け取り時に支払いが必要でした(送付先を会社にしていたので社で怒られが発生した)。

mechanicalkeyboards.com

触ってみてまず感じたのはトラックポイントの感度が良すぎるということです。スイッチの高さの分、本家ThinkPadキーボードより支柱が長いせいでしょうか。Mac側の設定でマウスカーソルの移動速度を最低にして使っています。ボタンが通常のキーと同じスイッチなのも最初はかなり違和感がありましたが、現行モデルのThinkPad USBキーボードと異なり、Macでも横スクロールが可能でした。また、センターボタンは実はFnキーなので、センターボタンでFnキーのコンビネーションを利用可能です。これは慣れると結構便利そうです。

キーボード部分については、筐体のサイズはHHKBと同じくらいなのですが、キー配列がHHKBと結構違うので最初はかなり戸惑いました(まあこれはHHKBがおかしいという説もあります)が、TEX Yoda IIはキー配列のカスタマイズの自由度が非常に高いのでHHKB風の配列に変更したところだいぶ快適になりました。キー配列の設定は以下のWebサイトで設定ファイルを作成し、それをTEX Yoda IIに書き込むという方法で行います。

yoda2.tex-design.com.tw

Fnキーとのコンビネーション含めてあらゆるキーを変更できるので自分の好みの配列にすることができます。ただ、HHKBとは物理的にキー配置が違うので完全に同じ配列にすることができなかったり、Fnキーとのコンビネーションを変更するとキートップの表記と紛らわしかったりするなどの問題があります。特にESCキーとbackquote、tildeが被っているのが不便なのでなんとかしたいのですが、キーの数が足りないのでどうするか迷い中です。

なお、一度生成したファイルを読み込んで編集することはできないのですが、サインインすると設定を保存しておくことができるので、キー配列を試行錯誤するのであればサインインしておくと便利です。

f:id:takezoe:20180312123206j:plain
HHKBとの比較 その1

f:id:takezoe:20180312123224j:plain
HHKBとの比較 その2

スイッチは赤軸と茶軸で迷って結局赤軸にしたのですが、軽いタッチで入力できていい感じです。ちょっとタッチが安っぽい感じもしますが、これはキーキャップの差によるところもあるかもしれません(バックライト付きのモデルはキーキャップが違うはずなので微妙にタッチが異なる可能性もあります)。ケーブル接続はUSB-Cで、付属のケーブルはL字型なので奥行きのない場所にも設置できます。ただ、筐体の重量がかなりあるので尊師スタイルで使うにはやや不安もあります。チルトスタンドはネジ式の足を取り付けるという漢らしい仕様。ゴム足もついていますし前述の通り重量もあるので安定感は抜群です。

f:id:takezoe:20180312123321j:plain
チルトスタンドはネジで取り付ける方式

f:id:takezoe:20180312123338j:plain
L字型のUSBケーブルが付属

トラックポイントについて少し補足ですが、キャップはクラシックドーム、ソフトリム、ソフトドームの三種類が付属していました。ThinkPad純正のものと同じ形状のようなので交換も容易そうです。最初からついているものだとちょっと高さがあってキー入力時に指に引っかかってしまうことがあるのでロープロファイルのキャップに交換してもいいかもしれません。

Lenovo ThinkPlus トラックポイント・キャップ・コレクション [73P2698]

Lenovo ThinkPlus トラックポイント・キャップ・コレクション [73P2698]

欠点を挙げるとすれば、コンパクトキーボードではあるものの、持ち運びにはまったく適していないという点でしょうか。重量もありますし、トラックポイントもキャップをつけたままカバンの中に入れたりすると引っかかって支柱が折れてしまうのではないかと不安になります。持ち運び時は注意した方がよさそうです。

あれこれ書きましたが、トラックポイント付きメカニカルキーボードという点では唯一無二の存在なのでみんな買うといいと思います。

GitBucket 4.22.0をリリースしました

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

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

プルリクエストのマージ方法の設定

リポジトリ毎にデフォルトのマージ方法と、選択可能なマージ方法を設定できるようになりました。

f:id:takezoe:20180303100516p:plain

イシューでのアクションの記録を強化

イシュー、プルリクエストで以下の操作も履歴として記録されるようになりました。

f:id:takezoe:20180303100702p:plain

空コミットでリポジトリを作成可能に

リポジトリ作成時に空コミットで初期化されたリポジトリを作成できるようになりました。

f:id:takezoe:20180303100757p:plain

データベースビューアの改善

データベースビューアにいくつかの機能が追加されました。

  • 左側のツリーでカラムを選択した場合にそのカラムを検索するSQLが生成されるようになりました。
  • "Auto query"チェックボックスが追加されました。チェックしておくと左側のツリー項目を選択した際に自動的にSQLが実行されます。

f:id:takezoe:20180303101136p:plain

maven-repository-pluginのアップデート

非公式プラグインですが、maven-repository-pluginがアップデートされました。

管理画面で任意のリポジトリを追加できるようになり、リポジトリ毎にアーティファクトの上書きが可能かどうか、全員に後悔するかどうかを設定できます。非公開リポジトリはアクセスするためのGitBucketアカウントでのBASIC認証が必要です。

f:id:takezoe:20180303101336p:plain

また、管理者限定でブラウザからリポジトリ内のファイル、ディレクトリを選択して削除できるようになりました。これは上書き不可能なリポジトリでも利用可能です。

f:id:takezoe:20180303101434p:plain

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

GitBucketに見る長期的なメンテナンスの必要なScalaアプリケーションにおいてScalaのバージョンアップのために留意しておくべきこと

Scalaのバージョンアップはなぜ大変なのか?

GitBucketはもうかれこれ5年近く開発を継続しており、Scalaのメジャーバージョンアップも何度か経験してきました。ScalaのバージョンアップはJavaと比べるとかなり苦労することが多いのですが、それはScalaのバージョンアップに伴うアプリケーションコードの修正よりも、むしろ以下の2点による部分が大きいのではないかと思います。

  1. フレームワークやライブラリの仕様がアグレッシブに変わる
  2. Scalaのメジャーバージョン間でバイナリ互換性が保証されていない

まず前者についてですが、これまでPlay FrameworkやSlickなど標準的なフレームワークやライブラリもかなり活発に仕様変更が行われてきました。フレームワークの開発が活発に行われているという状況はメリットも多い反面、後方互換性のない変更が多いとバージョンアップがつらいというデメリットもあります(とはいえ個人的にはフレームワークの開発が停滞しているよりは活発な方が好ましいと思いますし、最近は主要なフレームワークやライブラリについてはある程度落ち着いてきた感があります)。

後者については、Scalaはメジャーバージョンアップでのバイナリ互換性が保証されていないため、フレームワークやライブラリはScalaのメジャーバージョン毎にビルドする必要があります。このため、まずはすべての依存ライブラリが新しいバージョンのScalaに対応してからでないとScalaのバージョンをあげることができないという問題が発生します。中にはメンテの止まってしまっているライブラリが存在する可能性もありますので、それらへの対処も必要になります。

GitBucketの場合

GitBucketではWebフレームワークとしてScalatra、DBアクセスライブラリにSlickを使っています。他にも様々なライブラリを使用していますが、Javaライブラリが多く、Scalaのバージョンアップには影響していません。つまり、ScalatraとSlickというフレームワークについて上記の2点が主な課題となってきます。

Scalatra

Scalatraについてはすでに枯れているフレームワークであり、GitBucketで使い始めてから大きな仕様変更はありません。開発はさほどアクティブとは言えませんが、自分がコミッタをやっているのでScalaのバージョンアップ対応など必要な作業については自分で行なうことができるという安心感があります。

とはいえ、Scalatra自体様々なサブモジュールがあり、依存関係もそれなりに複雑です。Scalaのバージョンアップの際にはこれらの依存ライブラリにプルリクエストを出して回り、それらがリリースされてからScalatraをバージョンアップするということを行なっており、毎回数ヶ月がかりの作業になります。そのため、最近はメンテナンスコストを減らすため重要度の低いサブモジュールや依存ライブラリをなるべく減らす(小さなものでライセンス上問題なければScalatraのソースツリーに取り込んでしまうということも)という取り組みを行なっています。

Slick

Slickについては元々Typesafe社謹製のライブラリでScala業界ではデファクトスタンダードとして利用されていることもあり、メンテナンスを自分で行わないといけないというような状況には至っていません。しかし、Slick2から3へのバージョンアップでAPIがIOモナドを使った非同期方式に変更され、アプリケーションコードを大幅に書き換える必要がありました。また、当時Slick2はScala 2.12向けにはリリースされていなかったため、Scala 2.12にバージョンアップするにはSlick3に移行する必要がありました(その後有志の手によってSlick2もScala 2.12対応が行われ、Slick 2.1.0のScala 2.12版がリリースされています)。

GitBucketでは本体のコードの修正量もさることながら、IOモナドや非同期処理を導入することでプラグイン開発の難易度が上がってしまうのは避けたい事態でした。さらにGitBucketはWebフレームワークとしてScalatraを使っており、従来のサーブレットによる同期処理が基本となっているためDBアクセスを非同期に行うメリットが薄いという事情もありました。ScalikeJDBCやquillなど別のDBアクセスライブラリへの移行も検討したのですが、様々な事情から最終的にはblocking-slickという、Slick3上でSlick2互換の同期APIを提供するライブラリを自作し、これを用いてSlick3に移行するという選択をしました。

Scalaのバージョンアップのために留意しておくべきこと

冒頭でも書いたように、Scalaのバージョンアップの難しさは、アプリケーション本体のコードよりも、使用しているフレームワークや依存ライブラリによる影響の方が大きいというのがGitBucketを開発を通じての感想です。

Javaの場合、古いライブラリでも基本的に新しいバージョンのJavaでそのまま動作しますが、Scalaライブラリの場合、メンテナンスが止まってしまうとScalaのバージョンアップ時にblockerになってしまうため、長期的にメンテナンスする必要のあるアプリケーションでマイナーなフレームワークやライブラリを選択するのは大きなリスクになる可能性があります。マイナーなものを選択する場合は最悪自分でメンテナンスを行う覚悟をするか、別のものへの移行を常に視野に入れておく必要があります。

また、依存ライブラリについては必要最低限に留め、小さなものであれば敢えて自前で書いてしまうのも1つの手です。同じ機能を提供するJavaライブラリが存在するのであればそちらを利用したり、自前でライブラリを書く場合でもScala固有の機能が不要なものであればJavaで書いたものをScalaから使うのも有効な手段です。実際にGitBucketではMarkdownパーサやマイグレーションツールなどはJavaで書いた独自ライブラリをScalaから使用しています。

とはいえ、Scala界隈も黎明期の混沌とした状況からは脱して標準的なフレームワークやライブラリはだいぶ安定・収束しつつあり、メンテナンス性を考慮した選択も数年前と比べるとかなり容易になっています。Scalaのバージョンアップは新しい機能が利用できるようになったり、コンパイルが速くなったりなど様々なメリットがあるので、特に長期的にメンテナンスを行う必要のあるアプリケーションでは継続的に追従できる体制を整えておきたいところです。

akka-http-sessionによるセッション管理

Akka HTTPはプリミティブなHTTPツールキットであり、標準ではセッション管理の機能を持っていません。Akka HTTPにセッション管理機能を追加するためのライブラリとしてSoftwareMill社が開発しているakka-http-sessionというものがあります。

github.com

このライブラリを使うとAkka HTTPでセッション管理のためのDSLを利用できるようになります。セッション情報はカスタムHTTPヘッダまたはクッキーでクライアントに送信されます。設定で暗号化することができる他、オプションのモジュールを使用することでJWTを使用することもできます。また、CSRFプロテクション(クッキーの場合のみ)や長期間セッションを維持するためのリフレッシュトークンもサポートされています。

ここでは簡単な例を紹介します。まずはbuild.sbtに以下の依存関係を追加します。

"com.softwaremill.akka-http-session" %% "core" % "0.5.3"

セッションに格納するクラスを作っておきます。

case class Session(userId)

実際のコードは以下のような感じになります。Akka HTTP標準のDSLと同じ感じで記述することができます。特に難しいところはないと思います。SessionConfigの作成時にSessionUtil.randomServerSecret()でランダムなsecretを与えていますが、サーバを複数台立てる場合や再起動してもセッションが継続するようにしたい場合は設定ファイルに切り出して固定のsecretを与えるようにするとよいでしょう。

import com.softwaremill.session.SessionDirectives._
import com.softwaremill.session.SessionOptions._
import com.softwaremill.session.SessionResult._
import com.softwaremill.session._

...

val sessionConfig = SessionConfig.default(SessionUtil.randomServerSecret())
implicit val sessionManager = new SessionManager[Session](sessionConfig)

// ログイン
path ("login") {
  post {
    formFields("userId", "password"){ case (userId, password) =>
      if(userId == "admin" && password == "admin"){
        setSession(oneOff, usingCookies, Session(userId)) {
          complete(StatusCodes.OK)
        }
      } else {
        complete(StatusCodes.Unauthorized)
      }
    }
  }
} ~
// ログアウト
path ("logout") {
  post {
    invalidateSession(oneOff, usingCookies){
      complete(StatusCodes.OK)
    }
  }
} ~
// 要ログインなエンドポイント
path ("required_login"){ case (account) =>
  get {
    requiredSession(oneOff, usingCookies) { session =>
      complete(StatusCodes.OK)
    }
  }
}

設定はデフォルトではapplication.confで行います。デフォルトの設定はこんな感じになっているようです。

セッション情報をHTTPヘッダでやり取りするので巨大なデータの管理には向いていませんが、手軽に使えるのでちょっとした認証をかけたりするのに便利そうです。

GitBucket 4.21.0をリリースしました

4.21.0には本体およびバンドルされたプラグインにいくつかの問題があったため、修正した4.21.2をリリース済みです。こちらをご利用ください。 https://github.com/gitbucket/gitbucket/releases/tag/4.21.2

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

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

リリースページ

タグの一覧画面がリリース機能で置き換えられました。それぞれのタグにリリースノートを記述したり、リリースファイルを添付することができるようになりました。

f:id:takezoe:20180127021047p:plain

OpenID Connectをサポート

OpenID Connectを使ったログインが可能になりました。

f:id:takezoe:20180127021156p:plain

Google Identity ProviderおよびKeycloak用の設定方法についてはOpenID Connect Settingsを参照してください。

新しいDBビューア

これまでGitBucketは管理者向けにH2データベースに付属のWebコンソールを同梱していましたが、DBのURLやID/パスワードを入力しなくてはならなかったり、独自の機能を追加しようにも手を入れられなかったりといった問題がありました。

このバージョンでは従来のH2コンソールが新しい独自実装のDBビューアに置き換えられています。

f:id:takezoe:20180127021618p:plain

なお、以前のH2コンソールを引き続き使用したいという方向けにgitbucket-h2console-pluginというプラグインを提供しています。

サブモジュールのリンク先の変更

従来、サブモジュールのリンクはGitリポジトリのURLになっていたのですが、これはブラウザ上では直接参照できないケースが多いという問題がありました。今回のバージョンではサブモジュールのGitリポジトリが特定のパターンに一致する場合、GitリポジトリではなくWebインターフェースへのリンクを出力するようになりました。

今のところ、サポートしているのは以下のサービスです。

これら以外のリポジトリは今まで通りGitリポジトリへのリンクになります。

Close/Reopenボタンの改善

イシューやプルリクエストのClose/Reopenボタンはキャンセルボタンと間違って押してしまうというケースが報告されていたため、これらのボタンをドロップダウンから選択する方式に変更しました。

f:id:takezoe:20180127022210p:plain

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

GitBucketで使っているSlick用のちょっとした便利機能

GitBucketではずっとSlickを使っているのですが、クエリを簡単に記述するためにちょっとした便利機能を追加しています。

1つめはこんな感じのエクストラクタ。以前吉田さんにTwitterかなにかで教えてもらったもの。

object ~ {
  def unapply[A, B](t: (A, B)): Option[(A, B)] = Some(t)
}

Slickだとjoinとかmapするときに以下のようにネストしたタプルを多用するのですが、

PullRequests
  .join(Issues).on { case (t1, t2) => 
    t1.issueId === t2.issueId)
  }
  .join(Accounts).on { case ((t1, t2), t3) => 
    t1.userName === t3.userName
  }
  .join(Accounts).on { case (((t1, t2), t3), t4) => 
    t2.userName === t4.userName
  }
  .joinLeft(Milestones).on { case ((((t1, t2), t3), t4), t5) =>
    t2.milestoneId === t5.milestoneId
  }

これを以下のようにすっきり記述することができます。テーブルを追加するときもカッコの数をあわせたりする必要がないので楽です。

PullRequests
  .join(Issues).on { case t1 ~ t2 => 
    t1.issueId === t2.issueId)
  }
  .join(Accounts).on { case t1 ~ t2 ~ t3 => 
    t1.userName === t3.userName
  }
  .join(Accounts).on { case t1 ~ t2 ~ t3 ~ t4 => 
    t2.userName === t4.userName
  }
  .joinLeft(Milestones).on { case t1 ~ t2 ~ t3 ~ t4 ~ t5 =>
    t2.milestoneId === t5.milestoneId
  }

2つめはRep[Boolean]に対するimplicitクラス。これは確か同僚が発明したものだったような気がします。

implicit class RichColumn(c1: Rep[Boolean]){
  def &&(c2: => Rep[Boolean], guard: => Boolean): Rep[Boolean] =
    if(guard) c1 && c2 else c1
}

これは条件によってfilterの内容を変更したい場合に使います。こんな感じ。

Accounts filter { t =>
  (1.bind         === 1.bind) &&
  (t.groupAccount === false.bind, !includeGroups) &&
  (t.removed      === false.bind, !includeRemoved)
} sortBy(_.userName)

第二引数(!includeGroupsとか!includeRemovedのところ)がtrueになる場合のみ条件が有効になります。検索フォームとか作るときに便利です。

ちょっとしたものですが、Slickでクエリ書くときに面倒に感じる部分なのでこれで少しは記述が楽になっているかなという感じです。