はじめに
@j5ik2o
chatwork
- 内部的な細かい実装は掘り下げない
概論を話すこと
chatwork
- scala/akka + ddd
angular + ddd
大規模サービスの文脈が強い話
- わりとハイコンテクスト
- リアクティブシステムとドメイン駆動設計を組み合わせる
- vaughn vernon
- スタックの中に使われないオブジェクト
- 有効な参照があるとGCされずに残る
- レイヤーが深くなるとオーバーヘッドが起きやすい
- アクターでコールスタックを閉じ込めていくスタイル
DDD と Akkaの関連性
- Domain-driven
- DDDをやるためにakkaを使うことが出来る
- エンティティや集約をakkaで表現
- actorと境界付けられたコンテキストをデザイン
- メッセージパッシング 世界で起こった出来事をメッセージとして表現
- akkaの本を参照
- work distribution
アジェンダ
リアクティブとakka
- サーバ台数の変化
- 数十代から数千コアへ
- 応答時間
- 秒からミリ秒へ
- ハードウェアをまたがるマルチコアをフル活用
- 非機能要件がだいぶ変化して今のシステムでは支えられない
リアクティブ宣言
LightBendのCTOがFirst Author
- コンセプト
- 即応性
- 一番重要なポイント
- システムが限りなく早く応答すること
- 耐障害性
- 弾力性
- メッセージ駆動
- 即応性
即応性
- 速やかに反応すること
- 応答性が障害に直面しても維持すること
- PubSubモデルによって関心のある変更の集合を伝搬することで実現
- あるactorからあるactorへ通知を行うときに、変更の集合を伝搬させるスタイル
- ブロッキングはしない
- fire and forget
- 自分が送ったらすぐ忘れる
- 送られた方も反応して処理したらすぐ忘れる
- callして戻り値を待つことはない
ソフトウェアの災いを表す用語
- 欠陥については、オブジェクト指向入門あたりに出てくる
- 誤差
- 理論的に正しい値からの相違
- 欠陥
- 意図した振る舞いからシステムがそれる
- 作っている間は想定できないはず
- (障害)
- システムの縮退/喪失 − supervisionによって耐障害性を作りこむのがakka
- 故障
- 誤差
耐障害性
- 一度も障害を起こさない完全なアプリケーション・ソフトウェアを実装することではない
- 障害から回復する能力を持つシステムを実装すること
- 下位のコンポーネントのスーパーバイザと対話
- let it crash
- スーパーバイザは下位コンポーネントを停止/再起動/無視するなどの命令をだす + 上位のスーパーバイザにエスカレート
- back of supervisor
- サーキットブレイカーを間に挟むなど
- 急激な負荷を防ぐ
- 振る舞いを決定出来る
- akka: actorefを維持したままにもできる
- 参照が書き換わる
- 自己回復力が高い(レジリエントの本来の意味)
- 障害を閉じ込める
- supervisorの上のほうで障害が起こるとしんどい
弾力性
- スケーラビリティ
- プログラミングコードレベルで意識しなくていい
- リアクティブ宣言としてはそこまでは書いていないけども
- actorのメッセージパッシングで吸収する
- actorrefさえ知っていればメッセージパッシングで-スケーラビリティを発揮出来る
メッセージ駆動
- メッセージを受信してはじめて利用可能なスレッドを使って反応
- メッセージに反応しないコンポーネントは貴重なCPU資源を消費しない
- それに反応するかどうかはactorが決める
- 処理するかしないかを自分で決める
- メッセージを処理できないときは自分で受け付けない
- レスポンスも非同期
- メソッドベースのプログラミングに比べるとレスポンス時間などの制約がゆるい
- akka-remoteを使うと空間的にも分離できる
- 非同期で処理しているときに割り込みが発生することはない
- ポーリング/ブロッキングは(使えるけど)採用していないので、CPUをすぐに解放するので低レイテンシを導ける
akkaとは
- MITの教授が73年に提唱したものだが起源はErlang
なぜakkaなのか
- シングルコア危機
- マルチコア危機
- クロック数は前よりも同じか下がっている
- 単一コアでのスケーラビリティには限度がある
- 可視性の問題
- 単一コアが低速になる問題
- ムーアの法則はマルチコアによって維持されている
- C10K問題
- 増え続けるクライアントをどのようにさばくのか
- 問題はブロッキングI/O
- ブロッキングIOはスケーリングしない
- マルチコア危機をターゲットにした言語
- ruby3 erlang scala
- erlangの伝説
- 1986の電話交換機での可用性を叩きだした とにかく止まらない
- supervisorによる耐障害性
- akkaの登場
- ノンブロッキングIOでC10Kを解決、erlangの2倍のスループットを達成
akkaの主なプロダクト
- akka-actor
- akka-remote
- akka-cluster
- akka-http
- play2.5で実験導入
- akka-persistence
- akka-persistence-query
- クエリコンポーネント
- akka-stream
- akkaだけで達成できないstaticタイプな配信など
DDDのエッセンス
ドメインモデルとは
- 特定の図ではなく図が伝えようとする考え方
どんなモデルも興味関心のあるものを表す
モデルとはデータの入れ物
- それを操作して手続きを実現するサービスを実装
- alan kayのdynabook構想
- メンタルモデルを拡張したものがコンピュータ
- それをソースコードに反映するための言語としてのsmalltalk
- mvcのtrygve
- モデルとは知識の表象
- モデル=考え方としてコードを結びつけたソフトウェアを実装するのがDDDの考え方
DDDでどう変わるか
- モノリスなシステムが要件の変化とともに複雑化
- 境界付けられたコンテキストによってシステムを分割結合する
- 予め予測できない問題もあるので、マイクロサービスは銀の弾丸ではない
- ユビキタス言語
- 社内でも同じ意味で違う言葉を使っているひとが部署ごとに存在すること
- 顧客の言いなりになるのではなく自発的に共通言語を作っていくこと
- トランザクションスクリプト
- ロジックの重複に気づきにくい
- 重複は起きやすく、共通化も難しい
- DDDを使うと抑制できる可能性がある
- 実際のビジネスの変化に対応
- ビジネスの概念が変わるとコードを変える or 捨てる
- 変更コストはタダではないが低くすることは出来る
境界付けられたコンテキスト
- だまし絵の例(ルビンの壺)
- 同じ記号なのに意味が変わる
- 同じ記号に違う意味を混ぜてはいけない
- アカウント 銀行口座 or 個人の記録
- e.g.: DDD 4章の象のはなし
- BCとマイクロサービス
- BCごとにサービスを分けるひつようがある
FYI: BCとマイクロサービス
- building microservicesに書いてある
- 追加書き込みだけでシリアライズするのがドメインイベント
- BC間でのイベントの伝達手段のひとつ
- 非同期にレポートを作成
FYI: リアクティブ宣言とDDD
- コンポーネント
- BCを意識して隔離すること
- 隔離して責務に対して独立した進化ができる
ユビキタス言語
- 共通言語を使うこと
- 翻訳コスト
- 共通言語を実装にいれる
ドメインモデルとコードのひも付け
- 貧血症
- 振る舞いがない
- メソッドがない
- 非貧血症
- メンバーにならないとメッセージが送信できない
- 何ができないかがはっきりする
- チャットのサービスで何ができないかの考え方が理解できるようになる
ドメイン層の隔離
- レイヤー化アーキテクチャ
- ドメインモデルが他のレイヤーに侵食されないように隔離すること
- インターフェイス/アプリケーション/ドメイン/インフラストラクチャ
DIPによってドメインそうの独立性をたかめるレイアウト
- ドメイン層を下にすることで中立性を高める
- 集約とテーブルの相互変換
FYI: ヘキサゴナルアーキテクチャ
- DIPを円状に展開したレイヤー化アーキテクチャ
集約
- 関連の局所化と不変条件維持
- 複雑な関連が伴うモデル
- オブジェクトに対する変更の一貫性を保つのが難しい(循環参照)
- 守るべきルール
- 結果整合
- オブジェクトに責務を与えないといけない
- 粗結合にして関連を抑えないと一貫性を保つのが難しい
- 集約がひとつの境界
- でも慎重にロックしすぎる(巨大な集約)と複数ユーザが干渉しあうのでシステムが使い物にならない
- 境界定義はちゃんとしないといけない
集約とは
- エンティティと値オブジェクトを集約の中にまとめて、各集約の周囲に境界を定義すること
- 見えないようにすること
- でもimmutableだったら見えてもいいかも
FYI: 集約をactorで実装する理由
- 集約アクター内部の参照に直接アクセスできない
- 集約アクターを経由して必然的に不変条件を維持できる
- メッセージを介してのみ変更の変更や取得が可能
FYI: active recordは集約ではない
- ドメインモデルは概念を投影したものであって、テーブルを反映したものがドメインモデルではない
- 仮にドメインモデル=テーブルとすると、暗黙の境界を定めることになるのでコードにドメインモデルの考え方を反映できない
akkaを基にしたレイヤー化アーキテクチャ
- ほぼすべてがakka-actor/akka-streamベースとなる
- 集約は整合性の最小単位で、集約の外は結果整合
- SSの場合はリポジトリが必要 ESであればakka-persistenceを使えばリポジトリ不要
- event sourcing
- state sourcing(自前で作る必要がある)
- ちゃんとした設計が必要なので解は用意できない
- 一連の手続きはプロセスマネージャで実現
CQRSとES
- cluster shardingをやらないといけないかも
- shard内でactorを分散するなど
actorの基礎
- メソッドコールとは違う
- fire and forgetは投げっぱなし
- 戻り値に時間制限はない
1way or 2way
actorの位置透過性
- actor systemとヒエラルキー
- 子アクターが一番下
- supervisorストラテジー
- 子アクターをどうするか
- 例外ハンドリングは親に任せる
- ストラテジーに対応した挙動ができる
集約アクター
- PersistentActor
- 1 aggregateをインスタンス化
- 作成/更新/破棄/取得がこのインスタンスに届く
- ジャーナリングする
- 100件に1回スナップショットをとるサンプル
- 生き返ったときにステートを再現する
- パフォーマンスの維持
- 他のsubscriberに通知
- 副作用がおきることによるドメインイベントの永続化と発火
- 1 aggregateをインスタンス化
2wayの例
作成
- 状態をもつ
- 永続化ID
- コマンドハンドラ
- イベント永続化
- senderにレスポンスを返す
- subscriberにドメインイベントを通知
必要に応じてスナップショットを作成
イベントが積み上がっていくスタイル
更新
- 状態を返す
- 状態をリカバリ
- スナップショットを取得して状態の更新(古いイベントのスキップ)
アプリケーションサービスとしてプロセスマネージャ
- 複数の集約をI/Oするプロセスマネージャ
- プロセスマネージャの定義
- 最も動的で中央集権的なルーティングをした場合はプロセスマネージャにたどり着く
todoとtodo groupを作成する一連の手続き
PersistentFSMで実装するプロセスマネージャ
- 受信したイベントの永続化
- State状態に応じたDataもスナップショットとして永続化
- コンテキスト情報の変化
- 集約アクターを使って集約ロジックを使える
ドメインイベントハンドラ
- 状態の初期化
- 受け取ったらドメインイベントの発火→コマンドを発火
- 全部成功したらsucceeded(persistent actorを使うのもありかも)
akka-http
- akka-http bindAndHandle
- 覚えておくと超便利
- サーバの起動
- routeの合成
- akka-http Route
- Future 型変換 onSuccessでcompleteのメッセージを返す例
- 書き込みで副作用があるのでactorに対してメッセージを送る例
- DAOをストリームでラップする例
- ストリームに組み込んでレスポンスを返す
まとめ
- ブロッキングベースのコードより複雑化しているが、生でスレッドを使う/自前でノンブロッキングAPIを実装するよりは抽象化されている
- 並行処理を考えていなければ難しい考え方
- 高い非機能要件を問われる場合は一行の価値あり
- リアクティブシステムはまだ黎明期
- フレームワークや導入事例が充実していない
- spring reactor にはsupervisionがない
- まじめにやるならscala akkaかerlang otpくらいしか想像できない
- 大規模サービスでなくてもOK
- スケーラビリティを考える場合は有効
- スケーラブルなサービスを作りたいのであれば一行の価値あり
今回書かなかったこと
- akka-stream
- akka-cluster akka-cluster-sharding
- backoff supervisor/circuitbreaker
- enterprise integration patterns