akkaのハンズオン・トレーニングを用意しました、Javaのサンプルコード付きです!

akkaのハンズオントレーニングを用意しました。全5モジュールあり、アクター、イベント・ソーシング、CQRS、クラスタリングを手を動かしながら学べます。

このトレーニングのサンプルコードはJavaで書かれており、Scalaのサンプルコードは用意していません。Scala版も用意したかったのですが、Java版のコードしか書く時間がありませんでした…もしScalaのサンプルコードをご自身で書いてみたい方は、私リチャードが喜んで手伝います。TwitterのDMで連絡をください。私のDMは全てのツイッターユーザーに開放しています。

トレーニングは手を動かすものなので、聞いたことがある程度にはakkaを知っている人が対象です。その前にakka自体を知らない、という方はCodeZineで私が連載している記事があるのでごらんください。

codezine.jp

トレーニングを準備した背景

このトレーニングを用意した意図は、長くakkaを触り続けている私自身が、akkaの公式ドキュメントを独力で読み解いて、akkaを用いたアプリケーションを走らせるのはハードルが高いと、いつも感じていたからです。akkaの非同期処理、クラスタリングによる水平スケール、CQRSパターンの採用など技術的な面白さやメリットは理解してもらいやすいと思いますが、いざ自分で動かそうとしても、akkaに関してはその一歩を踏み出すハードルが高くないか?という懸念をいつも持っていました。akka公式ドキュメントは情報量が豊富で、ほとんどの機能についてもれなく、詳しく書かれているものの、初心者が一番最初に読んでわかりやすい文章ではなさそうだと私は感じています。(ただし、公式ドキュメントのIntroduction to the Exampleの部分は初心者にもわかりやすいと思います。)

そこで、このハンズオントレーニングは、仮にakkaを本番環境で導入出来る技術レベルが10だとすれば、あなたをレベル1や2に素早く連れていくために用意しました。そこまで到達できれば、あとはakkaの技術レベルを独力で上げていくことは容易になるでしょう。

トレーニングの内容

トレーニングではアクターの新APIであるakka-actor-typedを使ってサンプルコードを実装しています。akkaを触ったことがあるという方でも、akka-actor-typedを試した人はまだ珍しいでしょう。akkaは2019年12月にバージョン2.6がリリースされ、ついにakka-actor-typedがデフォルトのアクターAPIとなりました。

akka.io

こちらの動画で解説されているようにakka-actor-typedの新APIは下記2つのアンチパターンを、そもそも利用不可能にして陥ることがないようにしてくれます。これらのアンチパターンはバージョン2.5以前のakkaでは初心者ほど陥りやすく、またある程度の経験者でも避けるためには相当な注意深さが必要でした。akka-actor-typedを利用することで、初心者でも確実にアンチパターンを避けられます。

  • メッセージがアクター間の「闇」に消える(deadLetters)
  • アクター内での危険な非同期処理

Akka Anti-Patterns, Goodbye: Six Features of Akka 2.6 - Lightbend Blog


(動画では残り4つのアンチパターンも合わせて解説されていますが、それらはakka-actor-typed以外の機能で塞がれた穴です。)

一方、多くのプログラマにとってakka-actor-typedのAPIは、旧APIに比べわかりづらいと感じてしまうのではないでしょうか?アクター間で送るメッセージに明示的な型の指定が必要になり、さらにBehaviorという見慣れない概念が出現するため、とっつきにくい印象を持たれがちだと思います。とくに旧アクターのAPIに慣れていればいるほど、この新APIを難しいと感じるでしょう。

しかし、私はこの新APIが実際には難しいものではなく、シーケンス図や状態遷移図を先に描いて「ソースコードを書く前に設計を行う」ことで、容易に設計したものをソースコードに落とし込めると気づきました。言い換えれば、いきなりソースコードを書き始めづらいAPIになっていて、「前倒しでしっかり設計をする」「設計した情報を型で表現する」という手順を踏むことを推奨しているAPIと考えられます。ここは私の個人的な感覚になってしまいますが、これを読んでいる方にも、手順に従えばakka-actor-typedは扱いやすいと感じてもらうため シーケンス図や状態遷移図など、図で設計情報を提示する ソースコードのサンプルは最小限にすることで、設計とソースコードの対応を理解しやすくする という2点に注意してトレーニングの内容を作成しています。

全5モジュールの中でアクターを中心に、イベント・ソーシング、CQRS、クラスタリングといった機能についてのトレーニングなので、ストリームやHTTPはトレーニングの範疇外です。(HTTPはサンプルコード内で利用していますが、解説はしていません。)

さいごに

実はこのトレーニングは去年の年末ごろから構想していて、今年の1月から本格的に準備を始めたのですが、途中コロナウイルス拡大防止の流れをうけ、「リモートでも学びやすい、つまり独力でも学びやすい形式」として大幅に作り変えたものです。ただし、独力でも学びやすいと言ってもトレーナーが居るほうがより効果的に学ぶことができますので、私リチャードに声をかけていただければ、解説のためのビデオ会議セッションを設けます。ツイッターのDMでご連絡ください。またこのトレーニングを見た、あるいは取り組んでみた感想を #akka #handsonといったハッシュタグと共にツイートしていただければ喜んで見に行きます!

各モジュールはコマンドを走らせて動作確認するだけなら、1モジュールあたり30分かからずに終えることも可能なほどシンプルで最小限の内容です。先ほど紹介したように「レベル1や2に素早く到達して、あとは独力でakkaの面白さを探求できる」状態に達していただければと思います。このトレーニングを利用して、そしてぜひ私をビデオ会議で呼び出して、akkaを動かす楽しさを味わっていただければと思います。

それでは、Happy hakking!

マイクロサービスについて

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

※今回クラウド環境前提でのマイクロサービスについて記載します

マイクロサービス(アーキテクチャ)とは!

アプリケーションを分割し、小さなコンポーネント(部品)として再構成します。 マイクロサービスに対し、従来の構成はモノシリック(アーキテクチャ)といいます。

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

モノシリック(アーキテクチャ)とは

プロセス同士が固く結合しており、一枚岩(モノリス)のように単一のサービスとして 提供されます。

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

マイクロサービスのメリット/デメリット

メリット

  • 耐障害性が上がります
  • デプロイが気軽にできます
  • サーバ/ミドルウェアの管理工数が減ります
  • (クラウド環境前提ですが)スケーラビリティに優れています

デメリット

  • コンポーネントに分割する際に、かなりの工数がかかります
  • 比較的新しめの考え方なので、対応できる人材が少なめです

モノシリックのメリット/デメリット

メリット

  • 従来のやり方なので、対応できる人材が多いです

デメリット

  • 結合しているので、一部分で障害が発生すると全体に影響を受けやすいです
  • デプロイをする際も思わぬ部分が影響を受けたりもします。
    • 例えば一部機能でライブラリのバージョンを上げた場合、別の部分で影響が発生したなんてこともあります

世の中の風潮としては、マイクロサービス(or Cloud Native)に移行する流れのように見受けられますが、自社のリソースを把握し、どちらを利用する、もしくは一部を移行し併用する事を検討していきたいと思っております。

Scala 3.0/Dotty enumの紹介 (後半) Scalaにおけるenumeration/列挙体の歴史とScala 3.0 enum導入の背景

マーベリック株式会社、技術広報のリチャード 伊真岡です。前回に引き続きScala 3.0の主要機能の一つenumについて紹介します。前回はScala特有の事情ではなく、プログラミング言語を問わず、広く一般にenumeration/列挙体と呼ばれる機能がどんな場面で役に立つのかを紹介しました。

techlog.mvrck.co.jp

今回はScalaにおけるenumeration/列挙体の歴史とその前提となるCやJavaからの流れ、そしてScala 3.0であらたにenumが新機能として導入される背景を紹介します。

C言語の列挙体

まずはCでの列挙体について少し紹介します。以下はint型引数とSomeEnum型引数を受け取る関数を定義して、コンパイラがエラーを吐くかを試したものです。

#include <stdio.h>
#include <stdlib.h>

enum SomeEnum {
    ZERO, ONE, TWO
};

void printInt(int i) {
    printf("print int! %d\n", i);
} 

void printEnum(SomeEnum e) {
    printf("print enum! %d\n", e);
} 

int main() {
    printInt(0);    
    printInt(ZERO); 

    // compile error: invalid conversion from 'int' to 'SomeEnum'  
    // printEnum(1); 


    printEnum(ONE);
    return 0;
}

printInt(ZERO);とint引数が期待される箇所に、異なる型であるSomeEnum型の変数を渡すことができてしまいます。

Cのenumの使い方に関しては、int値のラベル付けとしての意味合いが強いと言えます。例えばswitchの条件分岐を記述する際にコードを読みやすくするために使えます。

#include <stdio.h>
#include <stdlib.h>

enum SomeEnum {
    ZERO, ONE, TWO
};

int main() {
    int i;
    int check = scanf("%d", &i);
    
    if(check != 1) {
        return 1; //error reading from console
    } else {
        switch(i) {
            case ZERO :
            printf("You entered ZERO = %d", i);
            break; 
            
            case ONE :
            printf("You entered ONE = %d", i);
            break; 
    
            case TWO :
            printf("You entered TWO = %d", i);
            break; 
            
            default :
            printf("You entered something else = %d", i);
        }
        
        return 0;
    }
}

つまりCのenumは前回の記事でも紹介した「コード中で可能な値を現実世界での正当な値に完全に一致させる」という意味合いは弱かったようです。このアイデアはElmのドキュメントに丁寧な説明がありますので再びリンクを貼ります。

guide.elm-lang.jp

Javaでのenum

次にJavaのEnumerationを振り返ります。JavaにもC同様enumというキーワードがあり、以下のコードでDayという型を定義できます。ちなみにJavaでは定数は大文字にする慣例があるので以下の曜日名はすべて大文字を使いました。

public enum Day {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

C同様switch文で使うことも出来ます。

class Main {
    public enum Day {
        MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
    }

    public static void main(String[ ] args) {
        Day today = Day.THURSDAY;
        switch (today) {
            case MONDAY   :
            case TUESDAY  :
            case WEDNESDAY:
            case THURSDAY :
                System.out.println("Low energy");
                break;
            case FRIDAY :
                System.out.println("Happiest day of the week?");
                break;
            case SATURDAY:
                System.out.println("The world is yours");
                break;
            case SUNDAY:
                System.out.println("Time ticking to the hell");
                break;
        }
    }
}

また.values()というstaticメソッドが自動で付与され、定義したenumの値全てを取得できます。

for(Day d: Day.values()) {
    System.out.println(d);
}

さらにvalueOf()というメソッドで文字列から定義したenum型のインスタンスへの変換も出来ます。

Day d1 = Day.valueOf("MONDAY");
Day d2 = Day.valueOf("Monday"); //IllegalArgumentException 大文字と小文字は区別される

Javaのenumは内部ではjava.lang.Enumというクラスを継承しており、values()valueOf()といったメソッドもこのjava.lang.Enumから引き継いだものです。通常のクラスと同様、メンバやメソッドを持つクラスとして扱うことも可能です。Comparableインターフェースを実装しているので下記のようにcompareTo()メソッドで順番の比較ができます。

//1日差、compareToの左(receiver)が小さい
Day.MONDAY.compareTo(Day.TUESDAY);     //戻り値-1
Day.TUESDAY.compareTo(Day.WEDNESDAY);  //戻り値-1 
Day.WEDNESDAY.compareTo(Day.THURSDAY); //戻り値-1 
//複数日差、compareToの左(receiver)が小さい
Day.MONDAY.compareTo(Day.THURSDAY);    //戻り値-3 
Day.MONDAY.compareTo(Day.SUNDAY);      //戻り値-6 
//compareToの右(引数)が小さい
Day.TUESDAY.compareTo(Day.MONDAY);     //戻り値1
Day.SUNDAY.compareTo(Day.WEDNESDAY);   //戻り値4 

短いシンタックスenumeration/列挙体を定義でき、C言語のenumと比べても高機能なJavaのenumですが、Scala視点から見るとAlgebraic Data Typesの表現が出来ないことを物足りなく感じるかもしれません。そこを踏まえた上でいよいよScalaのenumeration/列挙体の歴史を紹介していきます。

Scala 2.x系の場合

Scala 2.x系ではenumeration/列挙体相当の機能を実現する選択肢が複数存在し、enumというそのものズバリなキーワードはScala 3.0/Dottyになるまで採用されていません。Scalaの思想としてlanguage constructが少ないことを重視したため、enumというキーワードを言語に追加することにはScalaの作成者であるOdersky教授は慎重でした。

ここからはScala 2系までのenumeration/列挙体相当の機能の実現方法を紹介します。まず1つ目はcase class/case objectを使う方法です。おそらくこの手法が現在のScalaコミュニティでは最も広く利用されていると思います。

sealed abstract class Day
case object Monday extends Day
case object Tuesday extends Day
case object Wednesday extends Day
case object Thursday extends Day
case object Friday extends Day
case object Saturday extends Day
case object Sunday extends Day

Algebraic Data Typesを表現することも出来ます。

//実際のScala 2.13 Option定義を簡略化したものです
sealed abstract class Option[+A]
final case class Some[+A](x: A) extends Option[A]
case object None extends Option[Nothing]

この手法はやや記述量が多く、例えばHaskellで同等のenumeration/列挙体やAlgebraic Data Typesを実現する場合と比べるとその差がわかります。

data DayOfWeek
    = Sunday
    | Monday
    | Tuesday
    | Wednesday
    | Thursday
    | Friday
    | Saturday
data Maybe a = Just a | Nothing

case class/objectの値が多数にのぼる場合は無視できない記述量の多さになってしまいます。しかし、必ずしも可読性が低いとは言えません。Scalaコードに目が慣れたプログラマはcase class/objectを見ると読解しやすいと感じるでしょう。

またAlgebraic Data Typesとして利用すると、以下のように「子」型になってしまいます。Scalaの標準ライブラリは可能な限り具体的な型を返却するように実装されていますが、Algebraic Data Typesとして使う場合は型推論を混乱させる場合があり不便です。

val opt1 = Some("str") //Some[String]型

これを避けるために明示的な型指定をしないといけません。

val opt2: Option[String] = Some("str") //Option[String]型

またJavaのEnumerationと比べて、valuesメソッドによって全ての値を取得することも出来ませんし、valueOf()メソッドでStringからインスタンスへの解決を行うことも出来ません。値どうしの順番づけもないので「MondayはTuesdayの前」といった表現をするには、自分でComparator等を実装する必要があります。

2つ目の手法はscala.Enumerationです。

object Day extends Enumeration {
  val Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday = Value
}

case class/case objectを使ったパターンと比べるとデメリットがいくつかあるため、Scalaプログラマの中には「scala.Enumerationを使うな!」という主張を行う人々もいます。しかし、後述のようにメリットも有るため適材適所で活かす余地のある手法です。

利点はJava enumと共通する部分が多くあります。少ない記述量でenumeration/列挙体を表現できる、valuesメソッドですべての値を取得、witName(s: String)メソッドでStringからの解決、順序定義が自動的になされる、といった点が挙げられます。これらはcase class/objectにはない優れた特徴です。

欠点のひとつ目は、Erasureの後はextends Enumerationしたクラスは同じ方になってしまうことです。これは以下のメソッドオーバーロードに関するエラーを見ていただければわかりやすいでしょう。

object Colours extends Enumeration {
  val Red, Amber, Green = Value
}

object Day extends Enumeration {
  val Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday = Value
}

object Functions {
  def f(x: Colours.Value)  = "That's a colour"
  def f(x: WeekDays.Value) = "That's a weekday"
}
double definition:
def f(x: Playground.this.Colours.Value): String at line 12 and
def f(x: Playground.this.WeekDays.Value): String at line 13
have same type after erasure: (x: Enumeration#Value)String

もう1つの欠点はパターンマッチでexhausitiveness checkが働かないことです。以下のコードはパターンマッチのcaseを網羅していませんが、コンパイラは警告を出しません。

object Day extends Enumeration {
  val Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday = Value
}

def g(day: Day.Value) = day match {
  //コンパイラはexhaustiveness check警告を出さない
  case Day.Monday => println("Mon!")
  case Day.Tuesday => println("Tue!")
}

//scala.MatchErrorが投げられる
g(Day.Thursday)

3つ目の方法はJavaのenumを利用する方法で、Scalaプログラムの中に.javaファイルを置いてそれを.scalaファイルからimportします。JavaとScalaを共存させるプロジェクトで、かつenumeration/列挙体をJava側で定義する必要があればこの選択肢を選ぶことになります。利点はJavaのenumそのままです。欠点はScalaプログラムの中で使う場合パターンマッチでexhaustiveness checkが効かない、Algebraic Data Typesとして使えないなどがあげられます。

4つ目としてenumeratumのような外部ライブラリを使う方法があります。enumeratumの機能について詳しくは言及しませんが、enumeration/列挙体のためにライブラリを追加するとなると、ソースコードの各所で使われる大きな依存性になるでしょう。慎重に検討した上で使うのが良いと思います。

Scala 3.0/Dotty enum

上記の選択肢の欠点を個別にみると、先ほど紹介したScalaのlanguage constructを少なく保つという思想のため、新しいenumキーワードを加えるほどの動機にはなりませんでした。しかしすべて合わせると十分な理由であるとOdersky教授は判断し、Scala 3.0でenumが導入されます。

https://github.com/lampepfl/dotty/issues/1970

In my personal opinion, when taken alone, neither of these criticisms is strong enough to warrant introducing a new language feature. But taking them together could shift the balance.

Scala 3.0 enumはcase class/objectとscala.Enumerationの利点両方をほとんど実現できます。よって今後はenumeration/列挙体の実現にはScala 3.0 enumが主流になっていくと予想されます。Odersky教授が「Scala 3.0は、よりOpinionatedになる」と述べていたように、Scala初心者にとってはデフォルトの選択肢があることは安心につながるでしょう。

https://www.scala-lang.org/blog/2018/04/19/scala-3.html

What’s new in Scala 3? become more opinionated by promoting programming idioms we found to work well

基本的なScala 3.0 enumの使い方については前回の記事をごらんください。ここでは前回の記事で紹介しなかったAlgebraic Data Typesとしての使い方を紹介します。case class/objectによる表現と比べ記述量が減りました。

enum Option[+T] {
  case Some(x: T)
  case None
}

また上記のようにOptionを定義すると、Some()の戻り値型がSome[String]ではなくOption[String]になるので、型推論を混乱させる可能性が減ります。

Option.Some("str") //Option[String]型

ここで注意点を一つ紹介します。下記のコードのようにScala 3.0 enumではcase 名前 {/*ボディ*/}の形でボディを定義できません。

enum Color {
  case Red { //これはエラー、caseはbodyを持てない
   //...
  }
}

正しくはこのようにenumのブロックの中にメソッドを定義する必要があり、caseを並べたパターンマッチ(下記の例では)をつかってenumの値ごとの処理を実装するように推奨されています。

enum Color {
  case Red, Green, Blue

  //こんなかんじ
  def doSomething(color: Color): Unit = {
    case Red => ...
    case Green => ...
    case Blue => ...
  }
}

この理由についてはコンパイラ側の都合でスコープの混乱を解決するためです。こちらのPull Requestで詳しく述べられています。 https://github.com/lampepfl/dotty/pull/4003

What's more, once we have eliminated case bodies we also have eliminated scope confusion. All that remains are the case parameters and extends clause. Arguably, if we choose an ADT decomposition of a problem it's good style to write all methods using pattern matching instead of overriding individual cases. So this removes an unnecessary choice.

まとめ

Scala 3.0で導入予定のenumについて2回に渡って紹介しました。とくにScala初心者にとっておすすめの機能として期待されているので、3.0がリリースされたら利用を検討してください。

参考文献

松岡(little_hand_s)さんを招待してDDDモデリング・ハンズオンを開催しました

マーベリック株式会社、技術広報のリチャード 伊真岡です。先日弊社に松岡さんをお呼びしてDDDモデリング・ハンズオンを開催しました。

little-hands.hatenablog.com

弊社では「エリック・エヴァンズのドメイン駆動設計」の読書会を行っています。過去には「実践ドメイン駆動設計」も読書会の課題図書として取り上げたのですが、エヴァンズ本はそれと比べて抽象度が高く、読解はなかなか挑戦しがいがあります。読書会メンバーから「実際にモデリングを体験したら理解の助けになるのではないか」という声が上がり、ハンズオン形式のワークショップを提供してくれる社外の講師を探すことになりました。

エリック・エヴァンスのドメイン駆動設計

エリック・エヴァンスのドメイン駆動設計

  • 作者:Eric Evans
  • 出版社/メーカー: 翔泳社
  • 発売日: 2013/11/20
  • メディア: Kindle版

そこでDDDに関して、ブログや雑誌寄稿、登壇などで積極的に情報発信を行っている松岡さんにお願いしたところ、快く弊社でのモデリング・ハンズオン開催を引き受けてくださいました。

www.slideshare.net

ハンズオンはおよそ2時間半、最初の30分程度は松岡さんからのスライドを使った解説です。「モデリングとは何?」「どんな順番でモデリングを進めればいいの?」という基本に立ち返って、かつ松岡さんの実体験とともに解説していただき、エヴァンズ本を読んだけでは理解に自信が持てなかった部分を確認しながら学べました。

その後はハンズオンに移ります。ホワイトボードを使って「ユースケース図」「ドメインモデル図」という2種類の図をみんなで描きながらモデリングを行いました。

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

2チームに分かれてのモデリングで、片方のチームは「通販サイトのアフィリエイトプログラム」もう一方は「社内本棚に置く本の購入申請プロセス」というドメインをそれぞれ選びました。

「通販サイトのアフィリエイトプログラム」は非常に範囲が大きいドメインなので、ワークショップで収まる内容にするため適宜制約を仮定しつつ、一方で現実に即した「ポイント付与のタイミング」「ユーザや商品とアフィリエイトポイントの紐付け」などの難しい課題に挑戦していました。

「社内本棚に置く本の購入申請プロセス」は現在社内で実際に使っている手順をもとに議論しました。社内手順はスプレッドシートを使った非常に簡単なもので、「申請→承認くらいしか手順がない」「購入申請が予算内かどうかしか条件がない」と思っていたのですが、モデリングによってこの予想は裏切られます。運用でカバーしていた複雑さがあらわになり、管理者が様々な条件を考え、複数の状態遷移が伴う業務手順であることがわかりました。世の中の「スプレッドシートで十分」と思っている社内業務や、「CRUDで表現できる」と思っているアプリケーションの多くも、運用でカバーしている部分を解き明かせば想像以上に複雑なのかもしれません。

以下でワークショップに関する参加者からの感想の一部を紹介します:

  • ユースケース図→ドメインモデル図を作成する流れはわかりやすい、具体から抽象の流れはやりやすい
  • 困ったら世界を単純化(この世界の住民は全員アフィリエイトプログラムの会員など)で完結した、ただ実際にはドメインモデルはさらに複雑になりそう
  • ドメインモデルのオブジェクトとテーブルは別という意識が持てた
  • 多重度(1対N、N対N)を意識すると思考漏れに気付けそうだとわかった
  • 「状態遷移図をモデリングの早いタイミングで使っていい」「ホワイトボードの議論をツール(PlantUMLなど)で清書する」と現場でも使えそうなガイドをいただけた
  • 経験をもとに技術的な解決策を提示してもらえたのが学びになりました:
    • 「問)ユビキタス言語は頑張っても語彙がブレそう→答)画面の表示文言で合わせる方法がある」
    • 「問)ドメインモデルを自然に反映するためにデータベース層はRDBよりNoSQLの方が良い?→答)突き詰めるとCQRSが選択肢の一つになる」

参加者からも好評だった今回のワークショップ、知識として身につけたものを手を動かして確認できる良い機会でした。松岡さんありがとうございました!弊社ではDDDを含め今後も開発チームのスキル向上を、楽しさを追い求めながら継続していきます。

第二回ビジネス&開発部門 合同勉強会を開催しました!

マーベリック株式会社、技術広報のリチャード 伊真岡です。昨年12月に第一回を開催した社内ビジネス & 開発部門合同勉強会、その第二回を1月7日に開催しました。

techlog.mvrck.co.jp

この勉強会は現在100人を超え、様々な部署に分かれている弊社の中でビジネス側と開発側のお互いの理解を深めようという意図で始めています。まずは楽しくワイワイしながらお互いの仕事内容を紹介し合いましょう、という会です。

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

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

第一回に引き続き、ビジネス側のメンバー2人がホワイトボードとともに「顧客への広告キャンペーンの提案の流れの紹介」「今提案の現場で何が求められているのか」「弊社が提案をする際どこが作業効率のボトルネックになっているのか」「業界の各参加者の知識レベル向上に伴う変化」などの話題について紹介しました。

今回寄せられた感想の一部はこちら。

  • 実際の提案の流れに沿って話してもらったのがよかった
  • 規制強化などの厳しいニュースがある業界ですが、明るい希望がある話が聞けるの大変いいです!
  • 営業さんの実際の動きや流れはなんとなくしかわからなかったので、聞けたことはよかったです!!
  • 変にオブラートに包まず、ぶっちゃけていくスタイルは個人的には好きです
  • 営業さんの仕事の流れがイメージできたこと。あとプレゼンの引き込まれ感がすごかったです!
  • マーケットの話はもっと聞いてみたくなりました!体系的に学ぶことも必要ですが、どこから入っていっていいかがわからん状態です。
  • 営業だけでなく運用の仕方など各部門毎の仕事内容を詳しく知りたい

引き続きビジネス側、開発側力を合わせて楽しさを追い求めていきます!

社内でビジネス&開発部門 合同勉強会を開催しました!

マーベリック株式会社、技術広報のリチャード 伊真岡です。本日は弊社で開催されたビジネス&開発部門 合同勉強会の様子を紹介いたします。

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

弊社は現在100人を超えるメンバーがいます。創業当初はもちろん小さな会社だったので、部門を越えた連携は容易でした。しかし人数が増えるにつれて会社として様々な活動ができるようになった一方、社内の他のチーム全てで何が行われるかを把握するのは難しくなっていきました。

開発部門では30人超のメンバーがバックエンド、フロントエンド、インフラ、機械学習、QA、コーポレートエンジニアなどの様々なチームに分かれています。さらに管理部門があり、開発と管理部門を合わせると会社の半分弱の人数を占めます。残り半分強はビジネスサイドと呼ばれ、広告代理店らしく営業、トレーディングデスク、クリエイティブ、など様々なチームと職種が存在します。

私は技術広報として開発部門に所属していますが、正直弊社のビジネスサイドがどうやって売上を上げているのか、その構成比、業界のトレンドや弊社の立ち位置などまだまだ把握できていない部分が数多くあります。一方でビジネスサイドも「もっと開発サイドの知見を活かせれば面白い取り組みがたくさんできるはず」という思いがあり「それならまずは気軽な勉強会で、開発とビジネスお互いが理解できる場をつくりましょう」ということで勉強会を開催することになりました。楽しそうな試みを思いついたら、現場の人間たちで実行まで移せるのはとてもマーベリックらしいと思います。

今回の勉強会ではまず営業から2人の人間が、弊社全体での売上構成の大まかな紹介、それからとある営業チームの様子紹介と、彼らの日々直面している課題を紹介してくれました。開発サイドからは15人ほどが参加して、開発から営業の2人へ多くの質問が飛び交う活気のある会となりました。一部感想を紹介すると:

  • 自社の利益構成や広告業界話はおもしろかった
  • 自社プロダクトと代理店販売の違いが興味深かった
  • 今回の続きとして同内容の話をもっと深く聞きたいし、違うテーマでも面白いと思う

さらに次回以降の勉強会に向けて、開発サイドからビジネスサイドへ共有できる話として:

  • Webシステムの構築やデータベースのテーブル設計など
  • 色んな方法での自動化に関しては話ができるかと思う、なにより「何も考えず自動化するとかえって悪化する」ケースについては役立つ知見を提供できそう

といった点があげられました。「Webシステムとデータベース?」と思うかもしれませんが、今回の営業側から参加したうちの一人はPythonとPHPが使えるそうで、彼は純粋にITの技術に興味があるのです。私リチャードはPythonもPHPもどちらもできないので負けています(笑)

感想にもあったように開発者からも勉強会は好評で、ビジネス側の2人も交流を楽しんでいたので、次回以降の勉強会を鋭意計画中です。

ここで話があさっての方向に飛ぶのですが、先日私の友人から「ガイアの夜明け」というテレビ番組を見るように強く勧められました。一部視聴者層から熱狂的な支持を得る、テレビ東京の看板番組です。その中でも友人曰く「メロン農家親子の回」が最高の出来だから絶対見ろ、メロンの出来も番組の出来も最高だから必ず見ろ、とのことでした。その中で出てきた「にっぽんの宝物」グランプリを主催する羽根拓也氏は「日本の地方には隠された宝物がある。少子高齢化でそれを継げないケースもあり、もったいない。売れる成功事例を作りたい」と、地方の隠された「宝物」を発掘しコラボ商品の企画を仕掛けています。

何が言いたいかというと、友人に感化されてただガイアの夜明けの話がしたかっただけなのですが、弊社の属するインターネット広告業界は競争が厳しく、規制も目まぐるしく変わり「今までと同じことを続けるだけでは生き残ることもままならない」という点は同じだと思います。そして、もともと宝物といえるような光る部分を持った人と人が、コラボレーションによって新しい価値を生む可能性がある点も似ていると思います。

現在のITの世界は、技術が細分化・高度化しすぎて、とても一人でプロダクト開発で採用する技術を隅から隅まで知り尽くすのは無理になってきていると言えます。もちろん世の中には超人的な技術者がいて、複数の分野で第一人者としての知識と開発力があり、新しい技術分野もあっという間にマスターしてしまう人がいるのは知っています。しかし会社としてそういう技術者を雇うのは困難を極めますし、そもそも数が少なすぎて彼らに頼った組織は作れないでしょう。一方でひとつの技術分野、もしくは1.5分野くらいで第一人者となれる技術者は弊社にもたくさんいると私は感じています。それはビジネスサイドでも同じで、業界に深く精通していて顧客から高い評価を受ける人間が数多くいます。

先程述べたPythonとPHPができる営業の人間は「今、営業が困っていることを、ITの力を使えば解決できるという場合も、営業だけではその可能性に気づくことさえ出来ない場合もある」と言っていました。反対に開発サイドも「営業が困っていること」自体に気づいてない場合が多くあるでしょうし、それが「気づいていない」という理由だけで実現できないのだとしたら機会損失です。

今後弊社内の勉強会を通して、「にっぽんの宝物」グランプリのように、もともと実力のある異分野の専門家たちがコラボする取り組みができれば面白そうです。この記事を読んでいる人の中でITの世界に長くいるひとは、世の中に出回っているプロダクトやサービスで、当初海の物とも山の物ともつかなかったのに、後に大きく成長していった例を数多く見てきたでしょう。もちろんプロダクトやサービスという大きな成果につながるものばかりではないでしょうが、社内でのコラボから生まれた成果がいつかマーベリックの事業になんらかの形で繋がる未来を期待しています。

以上、半分以上ガイアの夜明けの話のブログでした。

もうひとつ、友人はNHKのプロフェッショナル 仕事の流儀「疾走、あんこ道~菓子職人・小幡寿康~」も勧めていましたので、興味があれば見てください。

Scala 3.0/Dotty enumの紹介 (前半) enumeration/列挙体の利点と利用例

マーベリック株式会社、技術広報のリチャード 伊真岡です。今回は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に関する話題は後半の記事で触れます。

techlog.mvrck.co.jp

enumの利点

Scala 3.0のenumは昔からプログラミングの世界で利用されてたenumeration/列挙体と呼ばれる概念を改めて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を利用しない方がよい例

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系までで利用可能だった類似の機能との比較などを紹介します。

追記

初稿公開時:

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

としておりましたが、Finite State Machineの実装にScala 3.0/Dotty enumを利用する効果的なソースコード例を提示できないため、紹介は取りやめます。

参考文献: