こんにちは。サーバサイドエンジニアの松宮です。 今年もScalaMatsuriに参加してきました。そして今年は大名スポンサーとして協賛させて頂きました! 濃い内容のセッションが盛りだくさんで非常に勉強になりましたので、いくつかピックアップしてレポートを書きたいと思います。 ↑ScalaMatsuriに参加したマイクロアドのエンジニアたち
目次
- いつかは導入したい、ES + CQRS
- これからは小まめにCVEをウォッチします・・!
- 10msの世界で戦うエンジニア必見ですね
- 関数型プログラミングは技術的にDDDをサポートする
- CPSとDIはユースケースによって使い分ける
- また来年!
いつかは導入したい、ES + CQRS
早速ですが、TIS株式会社の根来さんより「決済サービスで Akka Cluster 使ってみた」のレポートからです。 マイクロアドでもAkkaのモジュールはいくつか利用していますが、Akka ClusterやAkka Persistenceには手を出していなかったので非常に勉強になるセッションでした。
QRコード決済のサービスをCRUD構成でファーストリリースしたあとに、ES + CQRSでリプレースしたとのことで、なぜリプレースしたかについて話されていました。 ざっくりと以下のような理由でした。
- CRUDでは書き込みがボトルネックになる
- ES(イベントソーシング)によって、DBに状態を記録しなくなるため、ボトルネックが解消される
- CQRSを導入して、イベントを再生するコストが高いESのデメリットを補う
また、書き込みのスケーリングはAkka Clusterによってシャーディング、ES + CQRSはAkka Persistenceで実現していました。
そして特に興味深かったのは「如何に更新の遅延を短縮したか」という点です。 ES + CQRSは非常に優れたソリューションだと思いますが、「結果整合性」であるため、要件次第では導入が難しいですよね。
そこで、通常は下記のようにQuery-SideのReadModelUpdaterがEvent StoreをポーリングしてQuery Storeを更新するのですが、 更新の遅延を許容できない場合、Akka Persistenceのpubsub-notificationを有効化し、ReadModelUpdaterに更新を通知することで遅延を短縮したそうです。
ただし、遅延を短縮できたが、Entityを跨るイベントの順序が保証できなくなる問題があるとのことです。(ここでのEntityはアクターモデルのアクターのこと) こういったトレードオフの関係は非常に参考になりました。
その他、Akka Clusterを運用する上でのsplit-brainの解決方法やクラスターサイズとダウンタイムの説明等々、書き切れない程、盛り沢山なセッションで大満足でした! マイクロアドでもES + CQRS構成にしたいケースがあるのでいつかは導入したいです・・・。
これからは小まめにCVEをウォッチします・・!
続いては株式会社ビズリーチの基泰さんより「コードから理解するPlayframeworkの脆弱性」のセッションです。 Scalaでウェブアプリを開発するなら実績が十分あるPlay Frameworkは十分選択肢になるかと思いますが、そんなPlay Frameworkの脆弱性のお話です。資料が何と420ページと超大ボリュームですが、実際にはその一部を紹介しておりました。 Path Traversal, Server Side Request Forgery(SSRF), Unsafe deserialization, XmlExternalEntityが紹介されていました。
脆弱性の概要と図解の分かりやすい説明に始まり、コードを見ながら実際に何が原因かを深ぼっていき、修正するコードを示す、という系統立った説明が非常に分かりやすかったです。 そして、紹介されたどの脆弱性も意外とちょっとした実装のミスで発生していたので、セキュリティの審査は欠かせないなと実感しました。例えば下記のようにSSRFの脆弱性はURLのパース時に/(スラッシュ)で区切るコードがあるのですが、そのスコープの設定が甘かったのが原因だったり。
そして修正自体も数行で対応できてしまう脆弱性が多い印象を受けました。もちろん分かりやすい脆弱性をピックアップして紹介されたのかも知れませんが、Play Frameworkのような巨大なプロジェクトですら一般的に知られるような脆弱性が含まれる事実に驚きです。
登壇者の方からのアドバイスとしては、頻繁なアップデートを怠らないこと、セキュリティパッチを確認すること、CVEをRSSに登録するなどでウォッチすることを挙げていました。
10msの世界で戦うエンジニア必見ですね
続いてはFrindge81株式会社の藤野さんによる「ハイパフォーマンスScala」です。Frindge81様のUnipos*1はマイクロアドでも利用させてもらっています!
さて、セッションの内容はScalaで動作するソフトウェアの性能改善をする方法についてでした。Scalaは高性能な言語だと前置きした上で、大きく2点のパフォーマンス上の問題を取り上げていました。 1つがI/O、もう1つがGarbage Collection(GC)。
パフォーマンスを上げるためにスレッドを増やすと、コンテキストスイッチやスイッチや過剰にメモリを消費する等デメリットを挙げ、そこでノンブロッキング処理を紹介していました。 ノンブロッキングなため、あるタスクがI/O待ちの際、別のタスクを処理できるのでCPUリソースの無駄を解決できます。 ScalaならFutureでラップするだけでノンブロッキングになるので簡単ですね。
一方で注意点として、Futureを使うためにはスレッドプールのExecutionContextが必要になるのですが、不用意にglobalは使わないようにとのことでした。 globalとはScalaが標準で用意しているExecutionContextで、これをあちこちで利用していると、思わぬところでスレッドが枯渇して、意図しないパフォーマンスの低下が発生するかもしれない、ということだと思います。 なるほど、非常に参考になりますね。
続いてGCのお話です。GCの発生を抑えるポイントは
- 適切なコレクションを使う
- Boxingを避ける
- オブジェクトのライフサイクルを考える
どれも非常に納得です。Scalaのコレクションは選択肢が多すぎて困るくらいですが・・ また、Scalaはプリミティブ型かラッパークラスかを隠蔽していて、自動的にBoxing/Unboxingしているので、意図せずBoxingされて無駄に多くのメモリを消費するケースがあります。 そこで、下記のように@specializedのテーションを指定するとBoxingされなくなるようです。
case class SGenValue[@specialized T](value: T)
他にも参考になるお話が多かったのですが、とても書き切れないです・・!
関数型プログラミングは技術的にDDDをサポートする
続けてFringe81株式会社の小紫さんの「ピュアなドメインを支える技術」を聴講しました。ScalaでDDDを実践する上でのテクニックの解説でした。 説明が非常に丁寧で、こんなに理解できるセッションって中々巡り会えないので、一人感動していました(笑)
例で挙げていたケースとしては、ドメイン層に技術的な関心事が紛れてしまう事態をどのように解決するかです。 下記のように、ExecutionContextやDBSessionのような技術的詳細はドメイン層には持ち込みたくない訳ですね。
trait UserRepository { def findById(userId: UserId)(implicit ec: ExecutionContext, s: DBSession): Future[Option[User]] }
そこで、詳細はスライドで見て頂くとして、最終的には下記のようになっていました。
技術的関心事をF[_]
で抽象化。ただし抽象化しすぎると何も出来ない(mapすら使えない)のでコンテキストバウンドでせめてMonad
の振る舞いは追加します。
そうすると、下記のように技術的関心事は隠蔽したまま、map等が使えるようになります。
trait UserRepository[F[_]] { def findById(userId: UserId): F[Option[User]] } class UserApplication[F[_] : Monad: UserRepository] { def updateName(userId: String, newName: String) = { val userOptF: F[Option[User]] = UserRepository[F].findById(UserId(userId)) Monad[F].map(userOptF) { userOpt => ??? } } }
この手法はすごく参考なりました。またその後、DIの説明もあり、至れり尽くせりでした。 そして、DDDを支える技術として、「関数型プログラミングは技術的にDDDをサポートし、オブジェクト指向プログラミングはDDDの概念的な基礎になる」との説明は非常に印象的でした。
CPSとDIはユースケースによって使い分ける
最後はがくぞさんによる「継続とDI」を聞きました。モジュールの合成方法には「継続渡しスタイル(CPS)」と「Dependency Injection(DI)」があり、その紹介をされていました。
まず、CPSは関数が値を返さず後続処理を明示する方法になります。下記の例が紹介されていました。
def multipleFive[A](v: Int, k: Int => A): A = k(v * 5) def addTwo[A](v: Int, k: Int => A): A = k(v + 2) def main(): Unit = { addTwo(10, multipleFive(_, println)) }
multipleFive
とaddTwo
は第2引数が関数になっているのがポイントで、次の処理を引数で渡しているのがポイントです。
この引数に渡した関数を継続と呼ぶため、継続渡しスタイルと呼ぶそうです。CPSによって、multipleFiveとaddTwoはそれぞれ独立した関数になったので、再利用できるようになりましたね。
また、実装次第ではmap
やflatMap
を使えるようにも出来て、スライドを見て頂くと実装方法が書いてあります。
そして、DIと聞くと、僕はついDIコンテナを思い浮かべてしまいますが、そんな複雑なものでは無く、クラスにコンストラクタを用意して、外部から依存を与える方法を紹介していました。 ScalaではCPS、DIのどちらも利用できるので悩みどころですが、使い分けとしては下記が良さそうでした。
- 相互に強く関連するならDI。CPSだと関連性を表現できない
- アドホックな合成ならCPS。いくつかの関数を細かく合成するにはDIは大掛かり
Scalaで開発するならDIがメインになる気がしますが、どちらもユースケースに応じて使い分けるのが良さそうですね。
また来年!
今年のScalaMatsuriもとても楽しかったです。Doorkeeperの記録によると、昨年は323人で今年は499人が参加登録していたようで、Scalaのコミュニティが益々活発になっているようで嬉しい限りです。 また、今回は仕事で参加していたので、他社との合同勉強会や合同イベントの企画の話も出来たり、かなり有意義な時間を過ごせたと思っています。
マイクロアドはScalaを採用して5年は経ちますが、まだまだ認知度も低く、社外への露出も少ないので、合同勉強会や合同イベント等も積極的に開催して行きたいですし、Scalaコミュニティにも積極的に関わっていければと思っています。
それでは、今後ともマイクロアド並びに技術ブログをよろしくお願いいたします!