Scala 3.0 enumの紹介 (前半) enumの利点と利用例

マーベリック株式会社、技術広報のリチャード 伊真岡です。今回はScala 3.0の主要機能の一つenumについて紹介します。

2019年12月現在Scalaの最新バージョンは2.13ですが、2020年中にScala 3.0のリリースが予定されています。正式リリースに向けてScala 3.0用コンパイラはDottyというプロジェクト名で開発されていて、多くのScala 3.0向け機能がすでにDottyから利用可能となっています。

そんなScala 3.0で導入されるenumは、Scalaの創始者であるEPFL(スイス連邦工科大学ローザンヌ校)のMartin Odersky教授が「Scala初心者に勧めたい3.0新機能ランキングの中で1位」と述べています。

enumを使いこなすことはScalaプログラマにとって、特にScala初心者にとって大きな力となるでしょう。enumに近い機能はScala 2.xでも利用可能でしたが、Scala 3.0のenumではより便利で簡潔な記述ができます。もしScala 3.0のenum「のみ」について知りたい方はDotty公式のenumのページを読んでください。この記事はenumの背景や利用例から説明するので少し回りくどくなっています。

また記事は前後半にわかれていて、前半である今回の記事はenumの利点や利用例を解説し、後日公開する後半ではenumの歴史を振り返り、なぜScala 3.0で新しくenumを実装する必要があったのかを説明します。Algebraic Data Typesに関する話題は後半の記事で触れます。

enumの利点

Scala 3.0のenumは昔からプログラミングの世界で利用されてた列挙体と呼ばれる概念を改めてScalaの中で実装したものです。列挙体は歴史あるプログラミング言語であるC言語やPascalでも古くから利用可能でした。多くの言語で利用可能な列挙体、そのScala 3.0実装であるenumはどんな場面で利用するのが効果的なのでしょうか?それは変数の取りうる値を制限したいときです。

例を挙げて説明してみましょう。いまWebアプリケーションの画面があってドロップダウンリストから属性を選び、その値をバックエンドであるScalaアプリケーション側で処理する機能を考えます。ドロップダウンリストから属性を選ぶので、属性は予め決められた少数の選択肢の中から選ぶことになります。つまりドロップダウンリストの選択肢以外は属性として不正な値です。

f:id:maverick-techblog:20191209044057p:plain

選択肢のデータをString型でこのように表現できます。

Webアプリケーション上での表示 Stringで表現したシステム内部での属性値
コート ”Coats”
ジャケット ”Jackets”
ニット(セーター) ”KnitWear”
シャツ ”Shirts”
パンツ ”Pants”

しかしString型を使ってしまうと、上記以外の不正なStringが属性値として間違って使われてしまう可能性があります。

// 属性をStringで表現すると、ありとあらゆる不正なStringが可能!
“”                               //空String
“Socks”                          //靴下は売っていない
“あqwせdrftgyふじこlp;” //全くの不正String

ソースコード上のあらゆる場所でこの属性を表すString変数があらわれるたびに「このデータは不正なStringになってないだろうか?」という心配がつきまといます。そのため例えば下記のisValid(shoppingCategory)のような 形で、属性値を表すStringであるshoppingCategoryが正しい属性値か不正な値かをチェックする必要があります。

def doSomething(shoppingCategory: String, ...): Result = {
  if(isValid(shoppingCategory))
    ... //Exceptionをthrowする?
    ... //あるいはEitherのLeftを戻り値として返す?
}

このdoSomethingはshoppingCategoryが不正な値であったときにExceptionをthrowするか、あるいはEitherのLeftを戻り地として返却する実装を持つとします。するとdoSomethingが呼ばれるすべての場所でtry-catchでExceptionをハンドルするかEitherのLeftをハンドルします。これは面倒ですし、ソースコードの見た目も読みづらくなります。

f:id:maverick-techblog:20191209044934p:plain

このようにあらゆる場所で不正な値のチェックを行うのは大変です。Web APIと外部の境界、データベースとScalaアプリケーションとの境界など、境界部分のみでStringのチェックを行い、それ以外の場所では不正なStringが入り込む余地を残さないのが理想的でしょう。しかしそういった理想的な状態のソースコードを保つことは難しく、いつしか不注意なソースコードの変更で不正なStringが混入する可能性があります。いったん不正なStringを混入させてしまったら、あらゆる場所でのチェックが必要になり、チェックを行った際エラー処理まで考えなくてはなりません。

こういった時にenumを使うとプログラムの安全性がたかまり、コンパイラの助けによって不正な値を型レベルで防いでくれます。以下のように選択可能な属性値を定義しておくと、ShoppingCategory型の変数は不正な値を取ることはありえません。

// Scala 3.0 または Dotty
enum ShoppingCategory {
  case Coats, Jackets, KnitWear, Shirts, Pants
}

Web APIからの入力やデータベースからの入力はScalaアプリケーションと外部の境界になるので、チェックを行いStringやIntなどの型からScalaのenumで表す型へと変換する必要があります。しかし、チェックが必要なごく一部の処理とその他大部分のShoppingCategory型に変換されたあとの安全な処理を明確に分けることができます。

f:id:maverick-techblog:20191209044948p:plain

またScalaのenumはパターンマッチと相性がよく、以下のように書くと網羅的に場合分けを記述できます。コンパイラがexhaustiveness checkを行って場合分けの漏れを防いでくれるので、enumを使ったパターンマッチ網羅性に起因するエラーを未然に防ぐことができます。

enum ShoppingCategory {
  case Coats, Jackets, KnitWear, Shirts, Pants
}

def doSomething(category: ShoppingCategory): Unit = category match {
  case ShoppingCategory.Coats => … 
  case ShoppingCategory.Jackets => …
  case ShoppingCategory.KnitWear => …
  case ShoppingCategory.Shirts => …
  case ShoppingCategory.Pants => …
}

パターンマッチ網羅性に起因するエラーを見るために、下記のようにShoppingCategoryにSocksを加えてみましょう。オンラインショッピングストアで新しく靴下(Socks)の取り扱いを始めた想定です。

// 最後にSocksを追加
enum ShoppingCategory {
  case Coats, Jackets, KnitWear, Shirts, Pants, Socks
}

//ここでコンパイルエラー!Socksのcaseが含まれていない
def doSomething(category: ShoppingCategory): Unit = category match {
  case ShoppingCategory.Coats => …
  case ShoppingCategory.Jackets => …
  case ShoppingCategory.KnitWear => …
  case ShoppingCategory.Shirts => …
  case ShoppingCategory.Pants => …
}

このとき、上記のコードはSocksに対するcaseが含まれていないので以下のようなコンパイラWarningが表示されます。コンパイラの設定によってこれをWarningからエラーに変え、より安全にできます。

// [warn] -- [E029] Pattern Match Exhaustivity Warning: 
// [warn] 12 |  def doSomething(category: ShoppingCategory): Unit = category match {
// [warn]    |                                                      ^^^^^^^^
// [warn]    |                                    match may not be exhaustive.
// [warn]    |
// [warn]    |                                    It would fail on pattern case: Socks
// [warn] one warning found

enumの利用例

その他のenumの利用例を見てみましょう。例えばenumで次のように曜日を定義できます。

enum Day {
 case Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday
}

Java標準のDayOfWeekですでに曜日の定義があるのでありがたみは薄いかもしれませんが、何らかの理由でDayOfWeekへの依存を避けたい場合、例えば階層型アーキテクチャを採用していて、ドメイン層にはすべて自前の型を使うルールがあるといった場合には利用できます。

この際enumでは自動的にordinalという0から始まるInt値が付与されるので、「月曜日は火曜日の前」といった比較を行うことができます。

val l = List(Day.Sunday, Day.Friday, Day.Tuesday, Day.Wednesday, Day.Saturday, Day.Monday, Day.Thursday)
    
println(l)
//List(Sunday, Friday, Tuesday, Wednesday, Saturday, Monday, Thursday)

println(l.sortWith{(day1, day2) => day1.ordinal < day2.ordinal})
//List(Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday)

さらにvaluesというメソッドも自動で付与され、当該のenumに属するすべての値をArrayにして取得できます。

Day.values.foreach(println) //Day.valuesはArray[Day]型
//Sunday
//Friday
//Monday
//Saturday
//Tuesday
//Wednesday
//Thursday

曜日だと7種類しかありませんが、もっと種類の多いもの、例えばHTTPのステータスコード200 Successや404 Not Found等をenumで表現できます。現在のScalaのWebフレームワークやライブラリではvalを多数並べて、ステータスコード自体はIntの値で表現している物が多いです。

実用上はvalとIntを使った定義で問題なく、不注意であったとしてもわざわざ不正なHTTPステータスコードのInt値を利用したコードを書く人は少ないでしょう。しかし、もしこれらのWebフレームワークやライブラリが誕生する前からScala 3.0が存在していたら、ステータスコードは型安全なenumを使って表現されていたかもしれません。

JSONのserialization, deserializationにおいてもenumを利用する機会は多いでしょう。Web APIを開発するとき、HTTPリクエストのボディにJSONを利用するとします。JSONのあるmemberが取りうる値を制限したいときjson-schemaであればこのようなschema定義をするでしょう。

{
  "type": "string",
  "enum": ["red", "amber", "green"]
}

Scala 3.0のenumを使えばScalaのコードで同様の制限を表現できます。Scalaで最もよく使われるJSONライブラリのひとつであるplay-jsonを使うとこう書けます。play-jsonの現行バージョンはまだDotty対応が完了していないので、下記のサンプルではimplicitを使っています。Dotty対応が完了したらgivenが使えるはずです。

enum Color {
  case Red, Amber, Green
}

object Color {
  //型レベルでこのパターンマッチがRed, Amber, Greenを網羅していると保証できない…
  def unapply(str: String): Option[Color] = str match {
    case "red" => Some(Red)
    case "amber" => Some(Amber)
    case "green" => Some(Green)
    case _ => None
  }
}

またunapplyメソッドのパターンマッチの右側がenumの値すべてを網羅しているか、コンパイラはチェックできないので、この記事の「enumの理論的側面」の項目で紹介しているようにテストコードと組み合わせて網羅性をチェックするとよいでしょう。

//play-jsonのDotty対応が完了すればimplicitではなくgivenを使えるはず
import scala.language.implicitConversions

implicit val readsColor: Reads[Color] = Reads[Color] {
  case JsString(str) => str match {
    case Color(color) => JsSuccess(color)
    case _ => JsError(str + " is not a valid color")
  }
  case json: JsValue => JsError(json.toString + " failed to convert to color")
}
case class Pen(
  owner: String,
  color: Color
)

implicit val reads: Reads[Pen] = (
  (JsPath \ "owner").read[String] and
  (JsPath \ "color").read[Color]
)(Pen.apply _)
val penJson = Json.obj("owner" -> "Alice", "color" -> "red")
println(penJson.validate[Pen])

こうして不正な値が入り込む可能性をWeb APIとその外部との境界部分に限定します。境界部分で上記のようなコードによってStringからenumへの変換を行ってしまえば、Web APIの内部では型安全なenumによって属性値を表現できます。

あるいはMySQLのようなリレーショナル・データベースではenum型をデータベース内で利用できます。

CREATE TABLE items (
    name VARCHAR(40),
    size ENUM('coats', 'jackets', 'knit_wear', 'shirts', 'pants')
);

このときScala側のenum ShoppingCategoryとデータベース側のenumの対応を付ければ、安全かつ自然な属性表現ができます。

(enumの利用例としてFinite State Machineの実装を紹介しようと思ったのですが、記事が長くなってきたので後半の記事で改めて紹介します。)

enumを利用しない方がよい例

enumは万能のツールではありません。enumを使うのが適切でない例として、利用可能な値が頻繁に追加・削除される用途が挙げられます。

例えばタスク管理ツールなどのラベルや、進捗管理ツールの進捗ステージ名などにenumを使うとしましょう。これらは、それぞれのツールの利用者が様々なラベル名、進捗ステージ名を自由につけて使うことが予想されるので、ツールの利用者全体では利用可能な値の数が膨大になりますし、高頻度でそのラベル名や進捗ステージ名が更新されます。

f:id:maverick-techblog:20191209221504p:plain

こういったときはenumによってソースコード内で利用可能な値を制限するのではなく、素直にデータベース内にラベルや進捗ステージの名前を保存し、高頻度な更新に備えるのが良いでしょう。

enumの理論的側面

「enumの利点」で述べたようにenumを使うメリットは、型レベルで不正な値を防ぐことによってプログラムの安全性が上がることです。英語の情報になりますが、Quoraでもその点に触れている質問があり

”Strings (or Ints to represent certain meaning) are prone to errors”

という解説がなされています。「変数やパラメタ、あるいは戻り値に不正な値が使われる可能性」はソースコード上においてエラーの主要な原因のひとつです。先に述べたようにソースコード上のあらゆる場所で不正な値のチェックをしなければならないとしたら、それは非常に頭を悩ます問題です。

この点をうまく理論的に説明している資料として、Scalaとは別の言語ですがElmのドキュメントがあります。不正な値を型レベルで防ぐという点について、身近な例をまじえつつ集合論や濃度といった抽象度の高い概念を用いて説明しています。関数型プログラミングが好きな人にはElmのドキュメントの該当部分をよむと思考が整理されスッキリとした気分になるかもしれません。

Elmにおけるプログラミングの中で最も重要なテクニックのひとつは、コード中で可能な値を現実世界での正当な値に完全に一致させることです。これにより不正なデータの入り込む余地がなくなるため、…

また、取りうる値の範囲が型によって決まっているというのは、テストの面でも有利です。型によって取りうる値が制限されているため、そもそもテストを書かなくてよい場面も出てくるでしょう。それでもテストを書く必要がある場合以下のような単純なfor comprehensionですべてのケースを網羅できます。

enum Day {
  case Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday
}  

enum ShoppingCategory {
  case Coats, Jackets, KnitWear, Shirts, Pants
}
for {
  day <- Day.values
  category <- ShoppingCategory.values
} doTest(doSomeMethod(day, category))

まとめ

今回の記事ではScala 3.0のenumを題材に、列挙体という概念の基本に立ち戻って使い方を紹介してきました。冒頭で言及したようにScala 2系でも列挙体として使うことのできる機能はあったので、この記事で紹介したことの多くはScala 2でも違った方法によって達成可能です。

そこで後半の内容である次回の記事では、Scala 3.0 enumが誕生した背景やScala 2系までで利用可能だった類似の機能との比較などを紹介します。

参考文献:

clickhouse-odbcインストールでぶつかったgccバージョン固定の壁をSCL (Software Collection)で乗り越えた手順

マーベリック株式会社、インフラエンジニアの小川です。

特に古めの枯れたOSを使ったサーバ上で新しめのミドルウェアを使いたいと思った時、OS標準のパッケージやライブラリが古くてすんなり入らない…といったケースは多々起きると思います。弊社では以前、ClickHouseというOSSのデータベース導入を試みたときに上記の問題に遭遇しました。そこで、弊社でどうやってそれを乗り越えたかを、SCL(Software Collections)の使い方とともに紹介します。

1年で数億件以上のレコードが追加される予測

弊社でClickHouse導入を決めたのはある広告系のプロダクトでした。プロダクト開発の初期段階でデータベースの検討を行っていたとき、ひとつのレポーティング系テーブルのサイズが非常に大きくなることが懸念されました。過去の別プロダクトのデータなどをもとに見積もると、1年間の運用で数億件のレコードが追加されていくとの予測でした。もしビジネスが予想以上に拡大すればレコード数の成長速度も更に上がります。困ったことにレポーティング系のテーブルなので基本的にデータが減ることはなく、時間の経過とともにレコード数は増える一方です。レポーティングは毎回その巨大なテーブルに対しクエリを走らせねばなりません。

f:id:maverick-techblog:20191202121802p:plain

ClickHouse

そこで目をつけたのがClickHouseです。ClickHouseは以下のような特徴を持ち、ClickHouseのデータ構造に適したケースでは行指向なデータベースより高い性能を期待できます。

  • 列指向(カラムナ)データベース管理システム
  • OLAP処理向きのデータ分析基盤
  • オープンソース
  • SQLクエリを使用して分析データレポートをリアルタイムで生成できる

社内で行ったベンチマークでは、テーブルの構成や検索条件でばらつきがありましたが、クエリを走らせたとき1秒あたり数千万~1億以上のレコードをスキャンできました。これで十分実用に耐えうる速度がでたのでClickHouseを本番導入することが決定し、以下のような構成で当時の弊社のEC2環境、Amazon Linux 1イメージのインスタンス群上でアプリケーションの構築を目指しました。

f:id:maverick-techblog:20191202122112p:plain

clickhouse-odbc

さて、自社開発アプリケーションからClickHouseに接続するにはいくつかの方法があります。このアプリケーションはHaskellで書かれていたため、ODBCドライバを経由してClickHouseを利用することにしました。

  • アプリから ClickHouse を扱うための ODBC ドライバ
    • Haskell 製アプリ → unixODBC → clickhouse-odbc → ClickHouse
  • RHEL7/CentOS7/Amazon Linux 2 用のドライバは .rpm が提供されているため、特に困らない(はず)
  • RHEL6/CentOS6/Amazon Linux 1 用は .rpm パッケージが提供されていない

f:id:maverick-techblog:20191202122233p:plain

さてこれでClickHouseのデータベースとドライバがあれば動くはずでしたが…。

gcc バージョン問題にぶち当たる

GitHub からソースを取得して、Amazon Linux 1環境のサーバ上でビルドを試みるも失敗してしまいました。 https://github.com/ClickHouse/clickhouse-odbc#installing-prerequisites-linux

You'll need to have installed:

- Fresh C compiler, which understands -std=c++14
- Static libraries
    - static libgcc
    - static libstdc++
    - static libodbc
- cmake >= 3

Fresh C compiler, which understands -std=c++14

理由としては要求されるビルド環境の条件が厳しかったことです。特に gcc の「C++14」が大きな壁で、当時弊社の環境でのEC2インスタンスのOSはAmazon Linux 1で、gcc は「C++11」なので、ビルドが通りませんでした。

何ということでしょう

OSのバージョンをAmazon Linux 2に上げずに上げずに何とかできないものか…? と探していたとき

ありました

SCL (Software Collection)

https://www.softwarecollections.org/

SCLは現環境を壊さずに、新しめのミドルウェアが使えるものです。 (Node.js における nvm, Python における PyEnv などに使用感が近いです)

SCL の使い方

scl enable devtoolset-7 bash

→ bash が新たに起動し、ライブラリは SCL 提供のものを使うようになります

使ってみた on Amazon Linux 1

$ cat /etc/system-release
Amazon Linux AMI release 2018.03
  • 通常時の GCC バージョン
$ g++ --version
g++ (GCC) 4.8.5 20150623 (Red Hat 4.8.5-28)
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  • SCL 切り替え後
$ scl enable devtoolset-7 bash
$ g++ --version
g++ (GCC) 7.3.1 20180303 (Red Hat 7.3.1-5)
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

これなら行けそうな気がしてきました!

clickhouse-odbc インストール手順

新しめの unixODBC をインストール

cd /opt/
wget http://www.unixodbc.org/unixODBC-2.3.7.tar.gz
tar zxvf unixODBC-2.3.7.tar.gz 
cd unixODBC-2.3.7
./configure && make && make install

Cmake, その他必要なものをインストール

yum -y install git cmake3 libtool-ltdl-devel --enablerepo=epel

CentOS SCL(Software Collections) インストール
元々のビルドツールやライブラリを壊すことなく、より新しいバージョンのツール/ライブラリを利用できる。 C++14 対応の gcc もこの中に含まれている。

cd /opt/
wget http://mirror.centos.org/centos/6/extras/x86_64/Packages/centos-release-scl-rh-2-4.el6.centos.noarch.rpm
wget http://mirror.centos.org/centos/6/extras/x86_64/Packages/centos-release-scl-7-4.el6.centos.noarch.rpm
wget http://vault.centos.org/6.7/SCL/x86_64/scl-utils/scl-utils-20120927-11.el6.centos.alt.x86_64.rpm
wget http://vault.centos.org/6.7/SCL/x86_64/scl-utils/scl-utils-build-20120927-11.el6.centos.alt.x86_64.rpm
yum -y install *.rpm
yum install -y devtoolset-7-gcc-c++ devtoolset-7-make devtoolset-7-build --enablerepo=epel
scl enable devtoolset-7 bash

clickhouse-odbc インストール

git clone --recursive https://github.com/yandex/clickhouse-odbc.git
cd clickhouse-odbc/
git checkout -b v0523 refs/tags/v1.0.0.20190523
mkdir -p build; cd build && cmake3 .. && make -j $(nproc || sysctl -n hw.ncpu || echo 4)
make install

無事ビルドが通れば、以下の場所に clickhouse-odbc の ODBC ドライバが出来上がる。

ls -la /usr/local/lib64/odbc/libclickhouseodbc.so 

ODBC への組み込み

ODBC で認識するように、ドライバの設定ファイルを用意する。 /etc/odbcinst.ini

[ClickHouse]
Driver=/usr/local/lib64/odbc/libclickhouseodbc.so

~/.odbc.ini

[ClickHouse]
Driver = /usr/local/lib64/odbc/libclickhouseodbc.so
url = https://localhost

動作確認

clickhouse-odbc ドライバが動作するか確認する。

isql -v ClickHouse

動作確認結果
成功すれば、ODBCのクライアントが起動する

# isql -v ClickHouse
+---------------------------------------+
| Connected!                            |
|                                       |
| sql-statement                         |
| help [tablename]                      |
| quit                                  |
|                                       |
+---------------------------------------+
SQL> 

失敗の場合は、 Segment Fault 等のエラーが出る

動作確認完了
確認できたら、クライアントから抜ける。

SQL> quit

まとめ

SCL について

冒頭で述べたように、ミドルウェアをインストールする際にOS標準のパッケージやライブラリが古くてすんなり入らないといったケースは、長年使い続けている環境だと起こりがちです。本来は素直にOSのバージョンを上げて最新に近いものを使うべきでしょうが、あらゆるしがらみでできないことがよくあります。特に GCC に手を加えるのは…正直避けて通りたいものですよね。

そういった時、今回利用した SCL で元々のOSライブラリに影響なく、より新しいバージョンのツールが使える手立てがあるのは本当に助かりました。多用するのは気が引けますが、いざ! どうしても! という時の選択肢として活用できれば良いかなと思います。

ClickHouse について

また、 ClickHouse はまだまだ知名度が低い感じがあるものの、テーブル単位でレプリケーションが設定できたりと、噛むほど面白そうなカラムナDBです。まだまだ運用して日が浅いですが、今後弊社内でも活躍の場が増えそうな予感がしています。興味あるかたはぜひチェックしてみて下さい。

参考文献

セプテーニ・オリジナルさん主催でEventStormingワークショップを行いました。

マーベリック株式会社、技術広報のリチャード 伊真岡です。11月16日土曜日にセプテーニ・オリジナルさん主催でEvent Stormingワークショップを行いました。私はそこでファシリテーター役を務めたので、当日の様子を私から見てブログに記します。ディスカッション慣れしていた参加者の皆さんに助けられ、実りの多いワークショップになりました。

readeffectiveakka.connpass.com

Event Stormingとは複数人で行うワークショップの一形態です。英語の公式サイトに情報が載っていますし、日本語の情報だとyoskhdiaさんのブログ記事に解説が載っています。

f:id:maverick-techblog:20191120013742g:plain

このGIF画像のように、ふせんをたくさん使って議論と整理を進めていきます。段々とカラフルなふせんが増えて途中を仕切る線も加えられているのがわかるでしょうか?ふせんを並べ替えたり追加したり、ふせんの集まりを区切ったりしながら、皆で議論して業務手順の理解を深めます。

今回行ったワークショップはEvent Stormingの中でもBig Pictureワークショップと呼ばれる方式で、Big Pictureという名の通り特定のビジネスの全体、業務手順どうしのつながりに対して理解を深めるためのワークショップです。その他のにはUI(ユーザインターフェイス)デザインのためのEvent Stormingワークショップや、よりソースコードでの実装を意識したワークショップがEvent Storming公式サイトで紹介されています。

今回のワークショップの題材

ワークショップではより参加者になじみがあるビジネスであろう居酒屋を題材に、具体性をもって議論ができるように以下の設定をしました。

今回の分析対象: 客席数150人~200人規模の居酒屋

やや大きめの居酒屋です。居酒屋だとメニューの豊富さやコースの有無、予約やクーポン、団体客など程よく複雑性があり、分析しがいがありそうです。

さて今回は店の注文・配膳および会計など飲食店の業務全体を支えるシステムを導入することにしました。システムとしてはこちらが参考になるかもしれません。もちろん導入すべきシステムが私達の要望を全て満たしてくれるかわからないので業務分析を行ってシステム導入のための要求を洗い出しましょう。

店の規模:
カウンター席、テーブル席、大小様々な個室があります。全部で150人〜200
人収容できます。

厨房:
厨房と接客フロアは別れています。厨房の人員が直接注文をうけることはありません。

接客:
フロアの接客担当が注文を伝票に記入していました。会計時にはフロア担当が店入り口のレジで会計します。大人数の団体客には席での会計を受け付けていました。

システム導入の目的:
店舗での業務の効率が悪く、またミスが多く高い廃棄率にもつながっていたためシステムによって業務をサポートする判断がなされました。また外国人従業員の採用も考え、できるだけ簡単に業務が覚えられることが望ましいです。

ワークショップで行った分析を紹介

まずは全体の手順、来客から精算までの全体的な業務手順をあらいだし、だんだんと部分的な手順からより詳細な手順を洗い出していきました。それらの詳細な部分的手順の例の一つとして「団体客の1stドリンク受注」を切り出してみましょう。居酒屋で客が来店したあと、一番最初に全員分のドリンクを注文するあの手順です。「ユーザーストーリー」という言葉に馴染みがあれば、これはひとつのユーザーストーリと言えそうです。

写真だと少し見づらいので改めて描画ソフトで図にしたのがこちら。私達ワークショップ参加メンバー渾身の分析結果です。

f:id:maverick-techblog:20191120014320p:plain

最初は「ドリンク注文はメニューを客に渡して、注文を受け付けるくらいじゃないのか?」と思っていました。しかし詳細にストーリーを検討すると「飲み放題コースがランク別に分かれている可能性」「遅れてくる客がいてなかなか全員揃わない」「客がドリンク注文を決めるまでやたらに時間がかかる」「ドリンクが売り切れる」など様々な場合分けでの対応が必要なことがわかり、参加者みな団体客のドリンク注文処理の複雑さに改めて感じ入っていたようでした。

そしてこれは「初回のドリンク注文受付」のみであり、ドリンクを配膳するところは別のユーザーストーリーとなりますし、2回め以降のドリンク注文、食べ物の注文や調理、配膳もそれぞれ別のユーザーストーリーです。「居酒屋へのシステム導入」という設定でワークショップを行いましたが、現実の業務手順はソフトウェアで表現できるよりはるかに複雑で高度な処理を多数行っていることがわかりました。

f:id:maverick-techblog:20191120014442j:plain

参加者の感想

ワークショップの最後には参加者の感想を共有しました。一番皆が同意したのは「この複雑な処理を毎日何度も行う人は優秀」という人類に対する畏敬の念でした。他の感想は以下にあるとおりです。

  • ふせんのドメインイベント(公式サイトやyoskhidiaさんのブログ参照)には主語を書いたほうがわかりやすい
  • 登場人物をドメインイベントを考えると同時に貼っていかないと忘れそう
  • ふせんの整理が横軸(時系列)と縦軸(参加者のセンスに任される)だけだと難しい、もうひと軸欲しいくらい
  • 対象の業務領域の経験者がいるとドメインイベント洗い出しがはかどる
  • ワークショップの中でどこまで異常系・例外的なフローを洗い出すか
  • ドメイン・イベントは過去形の動詞を用いるというルールだがこだわらなくてもよい?
  • ドメイン・イベント主体で洗い出すと抜けもれが少なくなりそう
  • 物理のふせん紙を使うと検索ができない
  • 矢印を描きたい(今回はカベに直接ふせんを貼りました)
  • 事前に思っていたより細かい点まで分析できた、その共通認識がもてた
  • 2回目を明日やったらもっと上手に分析できそう
  • 今回は来客から精算までというスコープだったがその前後にも話が広がって収集がつかない部分もあった
  • 人数が増えると情報が増えすぎてワークショップ運営が難しくなりそう
  • 食べ物は必要、リラックスできる要因になった

Event Stormingというワークショップ形式自体がおもしろく、また参加者のみなさんが積極的に参加し鋭い指摘を次々にしてくだたおかげで様々な学びがありました。数多く挙げられた感想にそれが表れています。Event Stormingはいろいろな対象の業務分析に役立ちそうなので機会を見つけて活用していきたいです。

おまけ、ふせん紙の品質について 今回ドメイン・イベント用に使っていたふせん紙が何度もはがれおちてしまいました。ドメイン・イベントはEvent Stormingワークショップの中で最も数多く、100枚や200枚のふせん紙を使うこともめずらしくないでしょう。「最もよく使うものこそ高品質で使いやすいものを」という人生の教訓まで得られた1日でした。

参加してくださった皆さまありがとうございました!

Scala関西 Summit 2019で登壇しました!

マーベリック株式会社技術広報のリチャード 伊真岡です。

Scala関西 Summit 2019に参加してきました。2日間に渡って行われたこのカンファレンスですが、1日目の感想をブログにまとめます。公式Webサイトにあるように2トラックあった中で私は一日中ずっとトラック1の発表を聞き続けました。

2019.scala-kansai.org

トラック1ではClean Architectureに関連した発表が5本中3本ありました。弊社ではClean Architecture本やエリック・エヴァンズのドメイン駆動設計本の読書会を行っている最中ですので、実践をしている開発者たちからの知見がえられるのは嬉しかったです。

Dependent method types を利用した軽量Clean Architecture の紹介

最初のトークはがくぞさん。(スライド)

難しい抽象化の概念と捉えられがちなモナドにたよることなく、Tagless FinalやFree Monadなどで実現される利点をDependent method typeによって得ようという発表でした。

Scalaは高度な抽象化ができるからといって、モナドという理解のハードルが高いと思われがちな概念を取り入れるより、Javaに馴染みの深いエンジニアにもその延長線で理解できる技術を使うのはメリットがありそうです。

ソースコード全体に関わるかつ短期では変更しづらい方針なので、Tagless Finalを採用するかFree Monadにするか、Dependent method typeかは開発リーダーの腕が問われそうです。技術の取り入れやすさ、教育のしやすさ、メンテナンスコストなど様々なことを年単位で見通すという難しい判断になるでしょう。

Java 5.0時代の非同期処理技術から学び直すScala/Java非同期処理

次は私の発表。(スライド)

非同期処理はある程度時間をとって学ばないと何がなんだかわからない分野なので、いったんJava 5.0の時代までもどって、そこから現代の技術までたどると大きな流れが見えます。それが非同期処理理解の助けになりそうです、という発表でした。

Chatworkでリアクション機能をリリースした話

勝野さんの発表。(スライド)

Chatworkのリアクション機能楽しいですよね!今回はリアクション機能開発で行われていた性能要件分析とそれをもとにした技術選定、さらにベンチマーキングを行って詳細を検討する話でした。

特にベンチマーキングを行って「やってみないとわからないつまづき」(ベンチマーク環境でのAWSネットワーク構成の影響など)をひとつずつ潰して解決策を発見していく過程は、実践した人にしか提供できない価値ある情報だと思いました。

大きなシステムの改変を性能要件を見極めながら設計・開発していくのは非常に難しい一方楽しそうな仕事ですね。わたしもいつかそういう仕事をやってみたいと思える発表でした。

資産運用スタートアップの開発で採用した、PlayによるClean Arcitectureでの設計・開発事例

株式会社クラウドポートの若松さん(スライド)。資産運用サービスをたった3人のエンジニアで開発しているということにおどろいたのですが、少人数で効率良い開発を行うためにClean Architectureをどう活かしているかという発表でした。

Clean Architecture本に書いてあるようなメリットをうまく生かして少人数での開発を効率化している様子が伺える発表でした。

金融サービスの実装が複雑というのはそのとおりで、私が証券会社にいたときも感じていました。関連する規制・法律や業界慣習などが多岐にわたり複雑というのが一つの理由ですが、後のFolioさんの発表でも言及されますが金融関連のサービスは複雑になりがちです。

FOLIO のマイクロサービス in Action

最後はFolioのむらみんさんの発表(スライド)。むらみんさんの発表はスライドがきれいで発表慣れもしているのか話し方がわかりやすいです。

こちらもClean Architectureを絡めた話。Clean Architectureを使って層の分離をたもつことで、DDDの知識の蒸留のサイクルを回し続けるといった内容の前半と、マイクロサービス構成をFolioではどのように実現しているかという内容の後半に分かれていました。

Folio流の実践は工夫がたくさんあり、また発表からその工夫によって改善のサイクルがうまくまわっていることがうかがえる素敵な発表でした。

2日目はアンカンファレンスでした。アンカンファレンスの内容はこちらのブログには載せませんがツイッターのハッシュタグ #scala_ksでアンカンファレンスを含むScala 関西 Summitの様子がわかると思います。

楽しいカンファレンスでした!!

Chatworkさんでステートフルなサーバーレス技術である「CloudState」紹介ビデオの上映会を開催しました

マーベリック株式会社、技術広報のリチャード 伊真岡です。

10月4日金曜日にChatworkさんのオフィスで、ステートフルなサーバーレス技術、オープンソース・プロダクトであるCloudStateの上映会を行いました。

Chatworkさんには突然の申し出にもかかわらず快く会場を提供していただき、また上映会のあとも様々な知見を共有していただきました。ありがとうございます!

youtu.be

上映会の様子

上映会ではビデオを理解するために必要な背景知識の種類や量が多く、既存のサーバーレス技術の背景や現状、KubernetesやKnative、gRPC、そしてAkkaといった分野の理解が要求される内容でした。しかし参加者の皆さんは真剣に見入っていて、上映会のあとの感想の共有でも活発な意見交換がなされました。

f:id:maverick-techblog:20191008121826j:plain

かとじゅんさんによるAkka自体の紹介もあり、それがCloudStateのUser Functionsとどう組み合わされるのか?CloudStateの謳うメリット、とくに費用対効果の高さは本当に実現できるのか?私が事前にもっていた疑問や印象もぶつけてみて、わかっている部分とこれから検証していきたい部分の境界がよりはっきりしてきたと思います。

CloudStateとステートフルなサーバーレス技術の紹介

connpass.com

さてここからは、以下は上記Connpassでのイベント説明からの引用ですがステートフルなサーバーレス技術の必要性、そしてCloudStateの紹介をします。

FaaS == サーバーレス ???

サーバーレスはサーバーの管理・運用業務に頭を悩ませることなくシステム(の一部)を構成することが出来、開発者たちからも歓迎され、未来のクラウド技術の主軸を担うとの意見もあるほど注目されています。現状はサーバーレスの主な利用例としてはFaas(Function as a Service)形式のものがあり、マネージドなFaaSの代表格としてはAmazon Lambda、Google Functions、Firebase Functions等が挙げられるでしょう。

参考資料: サーバーレスアーキテクチャ再考 - ゆううきブログ

FaaSを例に取るとその主なメリットは:

  • 費用対効果が高い
  • 従量課金制である
  • 様々な自動化が容易である
  • 開発者体験(DX)を向上させる仕組みに富む
  • シンプルな構成でシステム開発時間を短縮できる

などが挙げられます。メリットだけをみるとまるで夢の技術のようですが、FaaSにはもちろん弱点もあります。それは:

一般用途のアプリケーションを構築するには制限が強すぎる

ということです。FaaSではFaaSに向いたシステムしか構築することが出来ず、そしてFaaSに向いたシステムとは思ったよりも範囲が狭いものであるということです。例えば、Webサービスを展開している多くの企業にとってそのバックエンドを全てFaaSに移すことは現実的ではないかもしれません。それを阻む主な要因としては:

  • Functionの寿命が短い
  • システム内部データの取得のために毎回データストアに問い合わせなくてはならない

などがあります。 多くの開発者は「サーバーレスはそういうもの」として受け入れていたのではないでしょうか。しかし、もしこれらの制限にとらわれないサーバーレス技術が利用可能だとしたらどうでしょう?つまりFaaSとは異なる制限をもつ別の種類のサーバーレス技術があるとしたらどうでしょう?

CloudState.io

CloudStateはLightbendが2019年8月に立ち上げた新しいオープンソース・プロジェクトです。 その目的はまさに上記で上げた問題点の解決、「クラウドの未来」であるサーバーレスの可能性を広げるためのプロジェクトです。

CloudStateという名のとおり、このプロジェクトではサーバーレス・システムでの分散ステートを扱います。AakkaのCluster、Event SourcingやCRDT、Kubernetes、 Knative、gRPCなどの技術を利用しサーバーレス・システム上での分散ステート管理に対する解決策を提供します。

感想

あらためて、参加者の皆さんのご協力で楽しく有意義な上映会になりました。上映会を終えてからもこのCloudStateという技術、そしてステートフルなサーバーレス技術という領域は非常に面白い分野なので、私は追いかけていくつもりです。

社内ドメインモデリング・ワークショップを開催しました

マーベリック技術広報のリチャード伊真岡です。8月26日月曜日に弊社内でドメインモデリング・ワークショップを行いました!当日の様子を紹介します。

f:id:maverick-techblog:20190903131621p:plain

題材

今回は映画館の券売・発券システムを題材に以下のような架空の設定を元にワークショップを進めました。

https://github.com/richardimaoka/ddd_hanson

ある映画館ウェブサイト https://cinemacity.co.jp/ の券売システムと、映画館においてある券売機システムを統合することになりました。我々はその統合システムの分析・開発をするプロジェクトを任されました。チケットの購入から発券にまつわるルールと業務フローを分析・モデリングしましょう。

準備段階当初はツイッター上で盛り上がった #チケット料金モデリング を参考にワークショップを行うつもりでした。しかし私個人の好みもあり、今回に限ってはチケット料金モデリングだけを扱うよりは、券売システム全体を考えるほうがドメインモデリングの醍醐味を味わえるのではないかと考え、上記のような題材を設定しました。

ユーザーストーリー分析

いきなりドメインモデリングに入る前にチケット販売・発券の業務フローを理解するためユーザーストーリー分析を行いました。理解が深まった後にドメインモデリングを行った方が議論がスムーズになるだろうと考えたからです。

こちらはユーザーストーリー分析で貼った付箋のすべてです。

f:id:maverick-techblog:20190903131654p:plain

  • 見たい映画を決める(スタート)
  • シアターの席に座る(ゴール)

という2つの時点を定め、それらをつなぐ動作(水色の付箋)を洗いだす事から始めていきました。

手法の詳細は省略しますがオライリーの「ユーザーストーリー・マッピング」という本に載っている印象的な言葉を紹介します。

  • ストーリーを使う目的は、良いストーリーを書くことではない
  • ストーリーを作る本当の目的は、共通理解をつかむことだ

以下の画像は書籍「ユーザーストーリー・マッピング」から引用したものです。左上にあるようにみんなが「合意できた」と思ったことも、よくよく話してみるとそれぞれが違ったことを考えていたということがあります。そういった認識の齟齬を防ぐため、話し合いを通してほんとうに共通した理解を得られるようにすることが重要だということが書籍では語られています。

f:id:maverick-techblog:20190903131915p:plain

私リチャードはすっかりこの考えに感化されてしまい、ワークショップでも10回以上共通理解、共通理解と繰り返す人になっていました。若干めんどくさい人ですね。

ドメインモデリング

ワークショップの後半はいよいよ本題であるドメインモデリングにみんなで取り組みました。ユーザーストーリー分析から「オブジェクト」を抜き出し、どの属性がどのオブジェクトに属するか議論をしていきました。

https://cinemacity.co.jp/ticket/ を見ていただくと料金体系だけでもなかなか複雑であることがわかるかと思います。今回の題材はチケット料金の計算ロジックをコードに落とし込めばよいというわけではないので「チケット料金計算に必要な属性を全部パラメタとして突っ込んで力技で金額を返すメソッド」を実装すればよいわけではありません。券売システム全体を考えどのオブジェクトがどの属性をもつか設計しなくてはなりませんでした。

ドメインモデリングを始めると議論は盛り上がり、以下のような点をはじめとしていい意味で意見が割れ、議論を深めるに連れ券売システムというドメインにたいして理解が深まっていきました:

  • 映画を見る場所をなんと呼ぶ?シアター?スクリーン?
  • 映画館オブジェクトはチケット販売システムには不要なんじゃない?
  • 18歳未満は見られない映画がある
    • R18指定の映画がある
    • 東京都の条例により、18歳未満の方は、終映時刻が23:00を過ぎる回は、保護者同伴の場合でも入場はできない
  • 料金はメソッドの戻り値として計算されるべきじゃないか?
    • いや計算された値は固定されないと支払い後料金が変わるのはおかしい
  • チケットの「予約オブジェクト」をつくろう
    • 「予約オブジェクト」は「チケット」と同一なのではないか?
    • いや、一回の予約で一人が友人の分もまとめてチケットを予約するかも

議論が白熱する中、何度か洞察の鋭い意見を出していた参加者の一人がチケットの券面のスクリーンショットをみつけました。項目が多い…!

f:id:maverick-techblog:20190903132047p:plain

これらすべてを「チケットオブジェクト」に結びつけてしまうと大変な巨大オブジェクトが出来上がってしまうので、できるだけきれいにしようとしたのがこちら。

f:id:maverick-techblog:20190903132416p:plain

オレンジの付箋はオブジェクト名、水色の付箋はオブジェクトの属性、黄色はメソッド(時間がなくて網羅できませんでした)、そしてピンクの付箋は疑問点やモデリングが複雑そうな項目を忘れないようにメモとして残したものです。

感想

複数人で議論を深めながら「映画チケット発券」に対する共通理解を育てていくというのが体験を伴って学べたのが何よりの収穫でした。

今回はユーザーストーリー分析とドメインモデリング含めて3時間だったので、ドメインモデリングが完了するところまでは想定しておらず、またソースコードに起こす時間もとっていませんでした。個人的にはあとでソースコードを書く時間をとって考察を深めてみるつもりです。

実際の業務のなかでドメインモデリングを活かすにはソースコード、ドメインモデル、さらにはユーザーストーリーも含めてなんども行ったり来たりしながら議論を深めて共通理解を育てていくのが良いのかなと思います。

参加者からも好評だったようで第2回社内ドメインモデリング・ワークショップをいま計画中です!

atWareさんで出張Akkaワークショップを行ってきました

マーベリックの技術広報リチャード伊真岡です。

今回横浜にあるatWareさんのオフィスでAkkaワークショップで行ってきました。atWareさんはScalaMatsuriのスポンサーやScala先駆者インタビューなど様々な活動を行い会社としてScalaコミュニティ、その他技術コミュニティの発展に貢献されています。またかとじゅんさんを講師として招いてドメインモデリングワークショップAkkaハンズオンも開催するなど社内での技術教育・啓蒙にも力を入れています。

今回はatWareさんに私の知り合いがいたことから実現したワークショップ。私が以前にOSS貢献をしていたころの経験談と、それから私の得意分野であるAkkaについてお話しさせていただきました。

さてこちらはatWareさんのオフィス。WeWorkオーシャンゲートみなとみらい内にあり、とてもキレイなオフィスでした。

f:id:maverick-techblog:20190828123819p:plain

前半はOSS活動について。私リチャードはakkaというJava/ScalaのOSSに100件ほどコントリビュートしたことがあるので、その時の経験をもとにOSS活動に対して私が持っている心構えを紹介しました。私自身の考えをお話しましたが、とはいえOSS活動というのは人それぞれの形があるので、GitHubにpull requestを送らなくてもOSSの普及や発展に少しでも間接的であっても貢献できればそれは立派なOSS活動だと私は思います。atWareの皆さんにはOSSにすでに貢献したことがある方もいましたし、今後のOSS活動に意欲的な方がいたようなのでそれぞれのみなさんが満足のいくOSS貢献の形を見つけていただければ嬉しいです。

f:id:maverick-techblog:20190828123926p:plain

後半はAkkaについて。よくある「Akka入門」という話に終始せず、より踏み込んだ中級者向けの内容になるように意識しました。

akkaはすでにモジュールの数が多く「なぜこんなに沢山あるのか…」と感じてしまうかもしれませんが、akkaの発展の歴史を振り返ればそれぞれのモジュールが当時その時点で課題となったことを解決するために作られたことがわかります。その歴史を把握することで各モジュールの関係性が明確になるのではと考え今回の内容に組み込みました。

それから私見とLightbend社のFast Data Platform構想を交えて「Akkaを導入するにはどういうパターンがあるか?」という点にまつわる話もしました。Akkaは学習負荷が小さくはないツールなので、Akkaの全てのツールを取り入れてシステムを作るのは苦労する面があるとおもいます。そこで最初からAkkaを最大限活かす方向性の導入方法と、部分的・あるいは段階的にAkkaを取り入れるパターンについて紹介しました。

とはいえせっかくAkkaに興味をもってくれたなら、やはりその良さを最大限活かすAkka actor + persistence + cluster-sharding を使ったフルパワー(?)構成で使うAkkaの力を知ってほしいと思っています。そこでいずれAkka Clusteringハンズオンをやりたい、そこで練習をとおしてAkkaの真の実力を体感してほしいというお話をしました。

勉強会の後は懇親会を開いていただきました。VimConfスタッフをしているasanoさんと話してカンファレンス・技術コミュニティ話で盛り上がったのですが、atWareさんは2017年から継続的にVimConfのスポンサーをしていて2018年は唯一のプラチナスポンサーだった会社です!Vimユーザの皆さんにはとても頼もしい会社ですね!

vimconf.org

今回の勉強会について楽しく振り返ることができ、また今後私が技術広報として企画していきたいことについても相談に乗っていただき非常に有意義でたのしい一日でした。atWareさんとマーベリックの絆も深まったことでお互い技術コミュニティに今後一層貢献していきたいと思います。