Amazonのレビューでもやけに評判がいいし、AWS & ECS弱者なので読んでみました。感想としては例によってAWS使うの大変だなーという感じ…。ハンズオン形式ですが、一通り目を通すだけでもECSと周辺サービスを組み合わせてどんなことができるか掴めるのではないかと思います。
scala-cliでScalaによるスクリプティングとパッケージングを試してみる
最近VirtusLab社が突如として公開したScala用の汎用CLIツールscala-cliを試してみました。
基本的にはScalaでスクリプティングするためのツールのようで、便利ツールや教育用途での使用が想定されているようですが、スクリプトを実行可能jarやDockerイメージとしてパッケージングしたり、Scala.jsやScala Nativeでのコンパイルにも対応しているようです。MacであればHomebrewでインストールが可能なのですが、手元の環境ではHomebrewのバージョンが古いせいかインストールできなかったのでGitHubから最新のソースを取得して自分でビルドしたものを使っています。
スクリプトの実行
まずは簡単なScalaプログラム(HelloWorld.scala
)を実行してみます。
@main def hello() = println("Hello, world")
以下のようにして実行します。run
はデフォルトのサブコマンドなので省略することもできるようです。デフォルトでScala 3が使われるようですね。
$ scala-cli run HelloWorld.scala Compiling project (Scala 3.0.2, JVM) Compiled project (Scala 3.0.2, JVM) Hello, world
使用するScalaのバージョン、ライブラリなどはscala-cliコマンドのオプションのほか、ソースコード中にコメントで特殊なディレクティブを記述することでも指定できます。Scala 2.12は大丈夫だったのですが、Scala 2.13を指定してみたところコンパイルが失敗してしまいました(Compilation failedとだけ表示される)。これは最新のソースからビルドしたものを使っているせいかもしれません。
// using scala 3 // using lib org.wvlet.airframe::airframe-json:21.10.0 import wvlet.airframe.json.JSON @main def hello = { val jsonValue = JSON.parse("""{"id":1, "name":"leo"}""") println(jsonValue) }
Ammoniteでサポートされている拡張子が*.sc
のスクリプトにも対応しています。こちらではmainメソッドを記述する必要がないほか、Ammonite形式の記述でライブラリのインポートなどが可能です。AmmoniteスクリプトはMetalsやIntelliJでもサポートされているのでスクリプトの記述時にIDEの支援機能を利用できるのは嬉しいですね。
import $ivy.`org.wvlet.airframe::airframe-json:21.10.0` import wvlet.airframe.json.JSON val jsonValue = JSON.parse("""{"id":1, "name":"leo"}""") println(jsonValue)
mainメソッド付きの*.scala
ファイルと同じように実行できます。
$ scala-cli run hello.sc
パッケージング
スクリプトをパッケージングしてみます。
$ scala-cli package hello.sc Wrote hello, run it with ./hello $ ./hello Hello, world!
hello
というコマンドが生成されます。このコマンドは軽量なランチャーで、必要な依存関係などは初回実行時に自動的にダウンロードするようです。実行にはJavaがインストールされている必要があります。依存関係も含めてパッケージングしたい場合は--assembly
オプションを指定するとUber JARが生成されます。
$ scala-cli package hello.sc --assembly Wrote hello.jar, run it with ./hello.jar $ java -jar hello.jar Hello, world!
--js
オプションでJavaScriptを生成できます。生成されたJavaScriptはNode.jsで実行可能です。
$ scala-cli package hello.sc --js Wrote hello.js, run it with node ./hello.js $ node hello.js Hello, world!
--native
オプションでScala Nativeを使用したバイナリを生成できます。こちらはScala 2.12もしくは2.13を使用する必要があるようです。*.sc
のサポートはScala 2.13のみのようなのですが、手元で試したところではScala 2.13では依存関係の解決に失敗してビルドが通りませんでした。前述の通り、Scala 2.13では通常の実行でもコンパイルに失敗するので最新のソースではScala 2.13の扱いに問題があるのかもしれません。
$ scala-cli package HelloWorld.scala --native -S 2.12 Linking (1432 ms) Discovered 971 classes and 8420 methods Generating intermediate code (2108 ms) Produced 8 files Compiling to native code (676 ms) Linking native code (immix gc, none lto) (148 ms) Total (4510 ms) Wrote HelloWorld, run it with ./HelloWorld $ ./HelloWorld Hello, world!
--docker
オプションでDockerイメージを生成できます。ただし、デフォルトではJava 8ベースのベースイメージが使用されるようなので、手元の環境もJava 8でコンパイルが行われるようにしておくか、パッケージング時に--docker-from
オプションでより新しいバージョンのJavaを使用可能なベースイメージを指定する必要があります。
$ scala-cli package --docker hello.sc --docker-image-repository hello-docker --docker-from openjdk:11-jre-slim Started building docker image with your application, it would take some time Built docker image, run it with docker run hello-docker:latest $ docker run hello-docker:latest Hello, world!
今回は試していないのですが、--docker
オプションを--js
や--native
オプションと組み合わせることでJavaScriptやネイティブバイナリで動作するDockerイメージを生成することもできるようです。
この他にもREPLの起動(オプションでAmmoniteを使用することも可能)や、scalafmtを使用したコードのフォーマット、sbtやmillの形式でのプロジェクトとしてエクスポートなど様々な機能がサポートされているようです。スクリプトを実行するだけであればAmmoniteだけで良さそうな気もしますが、作成したスクリプトを配布したり、スクリプトでプロトタイピングをして必要であればsbtプロジェクトとしてエクスポートして本格的に開発する、なんていう使い方も可能かもしれません。
Scalaプロジェクトでのバージョンバッジ
ライブラリ等をGitHubで開発する場合、最新バージョンを表示するためのバッジを表示することが一般的なプラクティスになっています。
バッジを表示するためのサービスは色々ありますが、JVM系のプロジェクトの場合、たとえば以下のような感じでMavenセントラルにデプロイされている最新のバージョンを表示できます(ScalaライブラリはScalaのメジャーバージョン毎に別のアーティファクトとしてデプロイされるのでURLでScalaのバージョンを指定する必要があります)。
![Maven Central](https://img.shields.io/maven-central/v/org.scalatra/scalatra_2.12)
ここで使っているshields.ioは他にも様々なバッジに対応しています。
Scalaライブラリの場合、ScaladexというScalaライブラリの検索サービスがバッジを生成する機能を備えており、これを使うとScalaのバージョンを指定しなくてもライブラリが対応しているScalaバージョンも含めたバッジを表示できます。Scala.jsやScala Nativeにも対応しているようです。
バッジ表示用のMarkdownはScaladex上に表示されているバッジから取得できます。
実際の表示はこんな感じになります。
[![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)
バッジのURLにScalaのバージョンを含める必要もありませんし、Scalaのバージョンごとに対応する最新バージョンも一目でわかるのでなかなか便利です。
TrinoでOpenLineageを試してみる
OpenLineageはデータリネージの業界標準の仕様となることを目指しているプロジェクトで、JSONベースのリネージイベントスキーマ定義、JavaやPython向けのリネージイベント構築用ライブラリ、Apache Airflow、Apache Spark、dbt用のインテグレーションなどを提供しています。また、別プロジェクトとしてMarquezというOpenLineageに対応したメタデータリポジトリの参照実装が開発されています(Marquezは元々WeWorkで開発されたものがOSS化されたもののようです)。
OpenLineageはAirflowやSparkのようなワークフロー/ETLツールから使うことが想定されているようですが、今回は学習を兼ねて普段仕事でも使っているTrinoへのOpenLineageのインテグレーションにトライしてみました。以下が成果物です。
このTrinoプラグインはクエリのinput/outputをOpenLineageのイベントとしてメタデータリポジトリに送信します。Marquezで動作確認をしています。1クエリ=1ジョブとして扱っているのですが、この辺はちょっとOpenLineageの概念とは合わないかもしれません。
OpenLineageは仕様的には拡張性もあり、様々なユースケースに対応できそうな印象ですが、個人的にはMarquezのUIはちょっとシンプルすぎるというか、実際のユースケースでフロントエンドとして使うにはかなり機能不足な感じがしました。OpenLineageを使うのであればUIは自前で作り込む必要があるかもしれません。
また、OpenLineageが本当に業界標準になれるかどうかも現時点ではかなり謎な感じがしています。OpenLineageはデータリネージ関連のサービスを提供するDatakin社によって立ち上げられたようですが、普及するには幅広いエコシステムとコミュニティを構築することができるかどうかにかかっていそうです。
Scalatra 2.8をリリースしました
ScalaベースのWebフレームワークScalatraの新バージョン2.8をリリースしました。
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のメンバーが中心に行われているようです。
WDLの基本的な記述方法については以下のドキュメントが参考になります。
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のエクステンションの開発も進められているようです。あまりアクティブに開発されている感じではないっぽいですが…。
*1:cromwellではWDL以外にCDL(Common Workflow Language)もサポートされています。