Scalaプロジェクトでのバージョンバッジ

ライブラリ等をGitHubで開発する場合、最新バージョンを表示するためのバッジを表示することが一般的なプラクティスになっています。

バッジを表示するためのサービスは色々ありますが、JVM系のプロジェクトの場合、たとえば以下のような感じでMavenセントラルにデプロイされている最新のバージョンを表示できます(ScalaライブラリはScalaのメジャーバージョン毎に別のアーティファクトとしてデプロイされるのでURLでScalaのバージョンを指定する必要があります)。

![Maven Central](https://img.shields.io/maven-central/v/org.scalatra/scalatra_2.12)

Maven Central

ここで使っているshields.ioは他にも様々なバッジに対応しています。

shields.io

Scalaライブラリの場合、ScaladexというScalaライブラリの検索サービスがバッジを生成する機能を備えており、これを使うとScalaのバージョンを指定しなくてもライブラリが対応しているScalaバージョンも含めたバッジを表示できます。Scala.jsやScala Nativeにも対応しているようです。

github.com

バッジ表示用のMarkdownはScaladex上に表示されているバッジから取得できます。

f:id:takezoe:20211009102159p:plain

実際の表示はこんな感じになります。

[![scalatra Scala version support](https://index.scala-lang.org/scalatra/scalatra/scalatra/latest-by-scala-version.svg)](https://index.scala-lang.org/scalatra/scalatra/scalatra)

scalatra Scala version support

バッジのURLにScalaのバージョンを含める必要もありませんし、Scalaのバージョンごとに対応する最新バージョンも一目でわかるのでなかなか便利です。

TrinoでOpenLineageを試してみる

OpenLineageはデータリネージの業界標準の仕様となることを目指しているプロジェクトで、JSONベースのリネージイベントスキーマ定義、JavaPython向けのリネージイベント構築用ライブラリ、Apache Airflow、Apache Spark、dbt用のインテグレーションなどを提供しています。また、別プロジェクトとしてMarquezというOpenLineageに対応したメタデータリポジトリの参照実装が開発されています(Marquezは元々WeWorkで開発されたものがOSS化されたもののようです)。

OpenLineageはAirflowやSparkのようなワークフロー/ETLツールから使うことが想定されているようですが、今回は学習を兼ねて普段仕事でも使っているTrinoへのOpenLineageのインテグレーションにトライしてみました。以下が成果物です。

github.com

このTrinoプラグインはクエリのinput/outputをOpenLineageのイベントとしてメタデータリポジトリに送信します。Marquezで動作確認をしています。1クエリ=1ジョブとして扱っているのですが、この辺はちょっとOpenLineageの概念とは合わないかもしれません。

f:id:takezoe:20211002203615p:plain

OpenLineageは仕様的には拡張性もあり、様々なユースケースに対応できそうな印象ですが、個人的にはMarquezのUIはちょっとシンプルすぎるというか、実際のユースケースでフロントエンドとして使うにはかなり機能不足な感じがしました。OpenLineageを使うのであればUIは自前で作り込む必要があるかもしれません。

また、OpenLineageが本当に業界標準になれるかどうかも現時点ではかなり謎な感じがしています。OpenLineageはデータリネージ関連のサービスを提供するDatakin社によって立ち上げられたようですが、普及するには幅広いエコシステムとコミュニティを構築することができるかどうかにかかっていそうです。

Scalatra 2.8をリリースしました

ScalaベースのWebフレームワークScalatraの新バージョン2.8をリリースしました。

github.com

Scalatra 2.8.0はだいぶ前にMaven Centralにpublishしてあったのですが、ライブラリの依存関係に一部問題があり、事実上標準のテンプレートエンジンであるTwirlと組み合わせる際にsbtの設定にワークアラウンドを入れる必要があるという状態だったのでアナウンスを控えていました。今回依存関係に修正を入れた2.8.2をリリースしてワークアラウンドなしで使用できるようになったのでサンプルやWebサイトも更新し、正式にアナウンスを行いました。

今回のリリースはScala 3対応に向けた機能やライブラリ依存関係の整理が目的で、特に大きな機能はありません。今回のバージョンで非推奨となった機能(WebSocketのサポートなど)はScalatra 2.9で削除され、Scalatra 3.0でScala 3に対応する予定です。

詳説 データベース ―ストレージエンジンと分散データシステムの仕組み

購入してからだいぶ時間がかかってしまいましたがようやく一周読み終わりました。元々は原著であるDatabase Internalsを読んでいたのですが、邦訳が出るという話を聞いて読み進める気力を失ってしまっていたのでした。

内容についてですが、データベースといってもクエリ言語やクエリの最適化といった製品依存度の高いトピックについては触れられておらず、データベースの種類を問わず普遍的なトピックであるストレージおよび分散システムに関するかなり硬派な内容になっています。

邦訳になっていることで確かに原著よりも早い速度で読むことはできるのですが、特に後半に関しては取り扱っている内容自体の難易度もあり、日本語で読んだから理解しやすいかというとそういうわけでもない感じでした。知見のあるトピックであれば理解できるのですが、そうでないトピックに関してはこの本だけではなかなか理解が難しい部分もあり、日本語で読んでも意味がわからないので原著と比べると確かに原著でもそう書いてあるんだけど、英語でそのまま読んだ方が理解しやすいかもと感じるケースもありました。

とはいえデータ指向アプリケーションデザインと同じく、自分で調べるきっかけを与えてくれるという意味ではとても貴重な教材だと思いますし、まずは興味のある部分だけを読んでいくというような読み方もありなのではないかと思います。アプリケーションの設計・開発にも応用の効くデータ指向アプリケーションデザインと比べるとデータベースに特化した書籍なので対象読者は絞られると思いますが(内容的には被る部分もありますが)、同様にじっくり理解を深めながら繰り返し読みたい一冊ですね。

データ指向アプリケーションデザインももう一度読みなさなくては…。

WDL(Workflow Description Language)について

Scalaベースのワークフローエンジンcromwellを試してみるの続きです。

cromwellではワークフローをWDL(Workflow Description Language)という言語で記述するようで*1、cromwellはWDLの参照実装という位置付けでもあるようです。WDLの仕様策定もcromwellを開発しているBroad Instituteのメンバーが中心に行われているようです。

github.com

WDLの基本的な記述方法については以下のドキュメントが参考になります。

github.com

support.terra.bio

WDLのサンプルGitHubリポジトリに含まれている簡単なワークフローを見てみます。

workflow SayItTwice {
   input {
     String name
   }
   call WriteGreeting {
      input:
         name = name
   }
   call ReadItBackToMe {
     input:
        written_name = WriteGreeting.output_name
  }
  output {
     File outfile = ReadItBackToMe.repeated_name
  }
}

task WriteGreeting {
  input {
     String name
  }

  command {
     echo "${name}"
  }
  output {
     File output_name = stdout()
  }
}

task ReadItBackToMe {
  input {
     File written_name
     String original_name = read_string(written_name)
  }

  command {
     echo "${original_name} to you too"
  }
  output {
     File repeated_name = stdout()
  }
}

taskでタスクを定義し、callで呼び出すという感じ。処理はcommandに外部コマンドを記述、タスクのouputで宣言した出力を別のタスクのinputで指定することでタスク間の依存関係を定義が定義されます。ワークフロー自体の入力は以下のようなJSONファイルで与えます。

{
  "SayItTwice.name": "Saying the same thing twice...is nice"
}

CLIで実行する場合、-iオプションでこのJSONファイルを指定します。

$ java -jar cromwell-<version>.jar run linear.wdl -i linear.json

このワークフローでは使われていませんが、runtimeを指定することでdockerコンテナで実行したり、scatter/gatherで集合を並列処理したりもできるようです。

WDL編集用にVSCodeのエクステンションの開発も進められているようです。あまりアクティブに開発されている感じではないっぽいですが…。

marketplace.visualstudio.com

*1:cromwellではWDL以外にCDL(Common Workflow Language)もサポートされています。

Scalaベースのワークフローエンジンcromwellを試してみる

ひょんなことからcromwellというScalaベースのワークフローエンジンの存在を知ったので軽く試してみました。依存関係を見るとAkka、Akka HTTP、Cats、Slickなどが使われているようです。

github.com

まずはGitHubのリリースページからcromwell-<version>.jarをダウンロードし、以下のようなシンプルなワークフローファイルを適当なファイル名(ここではhelloworld.wdlとします)で作成しておきます。

workflow myWorkflow {
    call myTask
}

task myTask {
    command {
        echo "hello world"
    }
    output {
        String out = read_string(stdout())
    }
}

CLIからワークフローを実行してみます。

$ java -jar cromwell-<version>.jar run helloworld.wdl

するとこんな出力が得られます。

{
  "outputs": {
    "myWorkflow.myTask.out": "hello world"
  },
  "id": "8904c84f-933d-4228-ba4e-7eef23a8b307"
}

ログを見るとデフォルトではhsqldbのインメモリモードで動作しているようです。設定ファイルのサンプルを見るとMySQLPostgreSQLも使えるようです。

続いでサーバーモードで実行してみます。以下のようにしてサーバーを起動します。

$ java -jar cromwell-<version>.jar server

ブラウザで http://localhost:8080 にアクセスするとSwaggerのUIを参照でき、このUIからファイルを指定してワークフローを実行したり、結果を確認したりできます。

f:id:takezoe:20210918221052p:plain

先ほどと同じワークフローをcurlコマンドで送信してみます。

$ curl -X POST http://localhost:8000/api/workflows/v1 -H "Content-Type: multipart/form-data" -F "workflowSource=@helloworld.wdl"

するとこんなJSONレスポンスが返ってきます。

{"id":"603775aa-d711-4df1-8bbb-1adad1ad7de4","status":"Submitted"}

ステータスを確認してみます。

$ curl http://localhost:8000/api/workflows/v1/603775aa-d711-4df1-8bbb-1adad1ad7de4/status
{"status":"Succeeded","id":"603775aa-d711-4df1-8bbb-1adad1ad7de4"}

成功しているようなので実行結果を参照してみます。

$ curl http://localhost:8000/api/workflows/v1/603775aa-d711-4df1-8bbb-1adad1ad7de4/outputs
{"outputs":{"myWorkflow.myTask.out":"hello world"},"id":"603775aa-d711-4df1-8bbb-1adad1ad7de4"}

CLIで実行した場合と同じ結果が得られました。

cromwellには様々な機能がありそうですし、WDLを使ったワークフローの記述などについてもエントリを改めてもう少し詳しく見ていきたいと思います。

GitHub Actionsでテストのエラーをわかりやすく表示する

CircleCIにはJUnit形式のテストレポートを食わせてテスト結果をわかりやすく表示してくれる機能があるのですが、GitHub Actionsでも同じようなことができないかなと思って調べてみたところ、以下のアクションを使えばできそうなので試してみました。

github.com

使い方は簡単で、テスト実行後にこんな感じの設定を追加するだけ。

- name: Publish Test Report
  uses: mikepenz/action-junit-report@v2
  if: always()
  with:
    report_paths: '**/build/test-results/test/TEST-*.xml'

Scalaプロジェクトの場合、sbtがtarget/test-reportsディレクトリ配下にJUnit形式のテストレポートを出力してくれるのでreport_pathsを以下のような感じにしておけばOKです。

report_paths: '**/target/test-reports/TEST-*.xml'

テストが失敗した場合、以下のように表示されます。失敗したテストケースとエラー内容を簡単に把握することができます。

f:id:takezoe:20210912122137p:plain

CIでテストが失敗した場合、どのテストがどういうエラーで失敗したのかをコンソールに出力されるログからすぐに見つけることが難しかったりするので、特にテストケースが大量にあるプロジェクトではこのアクションはなかなか便利そうです。