by shigemk2

当面は技術的なことしか書かない

リアクティブDDDについて メモ #reactive_shinjuku

はじめに

@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

CQRS

  • cluster shardingをやらないといけないかも
  • shard内でactorを分散するなど

actorの基礎

  • メソッドコールとは違う
  • fire and forgetは投げっぱなし
  • 戻り値に時間制限はない
  • 1way or 2way

  • actorの位置透過性

  • actor systemとヒエラルキー
    • 子アクターが一番下
    • supervisorストラテジー
      • 子アクターをどうするか
      • 例外ハンドリングは親に任せる
      • ストラテジーに対応した挙動ができる

集約アクター

  • PersistentActor
    • 1 aggregateをインスタンス化
      • 作成/更新/破棄/取得がこのインスタンスに届く
    • ジャーナリングする
    • 100件に1回スナップショットをとるサンプル
      • 生き返ったときにステートを再現する
      • パフォーマンスの維持
    • 他のsubscriberに通知
    • 副作用がおきることによるドメインイベントの永続化と発火

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

Lagom で学ぶ Reactive Microservices Architecture メモ #reactive_shinjuku

マイクロサービスに役立つかもしれないlagomのデザイン

lightbend社が作ったマイクロサービスアーキテクチャ向けフレームワーク

マイクロサービスアーキテクチャ

  • 2014/3にThoughtWorks社が提唱
  • 小さいサービスを組み合わせて1つのアプリケーションを構築

MSAの利点

  • 異なるチームで独立して開発
    • コミュニケーションコストを抑える
  • サービスごとに異なる技術
    • サービスの特性にあった技術
  • デプロイの影響範囲を小さく出来る
    • 新機能や修正を早くリリースできる
  • 障害を一部のサービスにとどめる
    • 全体の可用性を高める

MSAの注意点

  • 分散コンピューティングの落とし穴
    • ネットワークは信頼できる
    • レイテンシはゼロ
    • 帯域幅は無限
    • トランスポートコストはゼロ
  • 障害点が増えて可用性が下がる
  • オーバーヘッドが増えてレイテンシが大きくなる
  • 大規模になるほどこの問題は目立つ→最小化したい

この問題に立ち向かうのがLagom

  • リアクティブなマイクロサービス
  • リアクティブを達成するための様々な道具を備える
  • リアクティブシステムの性質
    • 弾力性
    • レジリエンス
    • 即応性
    • メッセージ駆動
  • 道具をかかえていること
  • 非同期/ノンブロッキングAPI
  • シャーディングによるステートフルなアーキテクチャ
  • 分散型の永続化機構
  • circuit breaker

Lagomを見習ってみよう

非同期/ノンブロッキングAPI

  • レイテンシを小さく
  • 影響を受けにくくする

  • 非同期にAPIを呼び出す − 1/5の時間で応答できる

  • ブロックしてタスクを実行
    • 待っている間もスレッドを専有する
  • 相手が応答しないときに大量のスレッドを消費すること

  • デメリット

    • 同期/ブロッキングAPIのデメリット
    • 非同期/ノンブロッキングAPIのデメリット
      • 特殊な書き方 学習コスト
      • スレッドセーフな実装になるよう注意
  • Lagomでは
    • 外部サービス/DB操作のAPIはすべて非同期/ノンブロッキング
  • Lagomを使わないで実現する方法
    • http
      • Play WS API
      • Gigahorse(Scala)
    • DB
      • Java Driver for Apache Cassandra
      • postgre-async mysql-async(Java)

sharding

  • 負荷分散の手法
  • 単一障害点がないので高い可用性
  • Lagomでは単体のサービスをスケールするときにつかう

Entity

  • サービスの現在の状態を分散して持つ
  • 状態はオンメモリで管理
  • 状態をDBに問い合わせる必要がない

具体的なイメージ

  • Chirper
    • FriendEntityが実装
  • FriendEntity
    • User IDごとにつくられる
    • ユーザー名/フォローしているユーザーのリストを状態としてもつ

Sharding

  • EntityはShardというグループにわけられる
  • Shardはノードに配置
  • EntityをIDで識別出来るようにしておくこと
  • メッセージを指定しておけば、どのShardRegionに送ってもそのIDをもつEntityに届く
  • 障害時 べつのノードにShardが移動(復元する)

    • →Entityの実装に集中できる
  • Lagomを使わんかったら Akka cluster shardingを使って実現する

  • LagomがReactiveを達成する道具

  • 分散型の永続化機構

ES

  • Lagomの永続化機構
    • CQRSとイベントソーシングをつかう
    • ES: イベントの永続化
    • イベントをデータストアにINSERTしていくこと − entityの状態が失われた場合はイベントを再生して状態を復元する
  • イベントはイミュータブル

  • デメリット

    • データ集計コスト
    • CQRSで解決

CQRS

  • コマンドクエリ責務分離
  • コマンドとクエリを分離できること

  • デメリット

    • 読み込み書き込みで完全な一貫性を保てない
    • 十分な時間がたつと整合性がとれる(結果整合性)
    • 一時的に一貫性が取れてなくても問題ないビジネスやシステムを設計すること
    • Entityをまたがる集計などを行うのに遣える
      • その日の振込金額の合計

Circuit Breaker

  • 外部サービスの障害時に、即時にエラーを返す仕組み
  • 電子工学では電流が流れ過ぎるときに即座に電流を遮断する仕組み
  • Close 回路が閉じた状態で通信
  • Open 回路が開いた状態で遮断

  • close: 外部サービスへリクエスト

  • open: すぐにエラーを返す
  • half-open: リクエストを試す
    • リクエストが成功したらcloseに戻る
    • closeのときは通信できる
  • 障害が起きるとFailureCountがはじまる
    • FailureCountのしきい値にたっすると、リクエストを通さなくなる
    • 一定時間がたつと一時的にリクエストを通すこと
    • 正常に戻るとopenになりリクエストを通すようになる
  • 障害の連鎖を食い止めることができる
    • 複数のサービスが連携してリクエストを処理
    • 何らかの障害→依存するサービスも負荷があがる

まとめ

  • モノリシックなシステムを単純に分割するだけでは可用性やパフォーマンスの問題をかかえる
  • Lagomはそれを解決する手がかりになるかも

  • Lagomは使い物になるのか

    • Javaフレームワークとしては導入障壁が高い
    • 導入実績がない
    • バグを踏む可能性が高い
    • 非同期や関数型のプログラミングパラダイム
    • リアクティブな設計にはなるかも

ドメインイベントを設計する メモ #reactive_shinjuku

アジェンダ

  • メッセージの種類
  • ドメインイベントの設計

(DDD成分つよめ)

メッセージの種類

  • メッセージの種類
    • command message
      • point-to-pointで送る それ自体がactorの振る舞いを内包
    • document message
      • 情報を伝達
      • commandを送ってきたsenderへの返答(request-reply)
    • event message
      • ESの文脈
      • Pub/Subをつかう
      • 不特定多数へのブロードキャスト
      • commandが起点

DocumentとEventの違い

  • 内容とタイミング
  • Document 多少遅れても確実に届けたい
  • Event 不特定多数に、すぐに発行したい

ドメインイベントの設計

  • ドメインエキスパートがきにかける何かの出来事
  • DDD本には未収録
  • DDD Referenceには実装
  • 実装する手段としてEvent Message

ドメインイベントの見つけ方

  • 別の集約の状態に依存している集約を見つけるのが感嘆
  • するときに
  • そうなったら
  • 通知してほしい
  • 発生したときに

ポイント

  • 名前/プロパティはユビキタス言語に沿ったものにすること
  • 出来事が過去に発生したことがわかるようにする
  • 集約上で何らかのコマンドを実行した結果として発生するイベントの場合は、実行したコマンドに基づくイベント名にする
  • イミュータブルにしておく
  • 命令は過去形

設計の注意点

  • イミュータブルにしておく
    • 過去のイベントであるので
    • 発生要因の情報をもたせる
    • 追加の問い合わせはさける(処理コストと複雑さが増大するので)
    • 必要なものはイベントに含める
    • 必要最小限な情報だけを含める
    • entityレベルで含めると情報量が多すぎる

イベントを集約としてあつかう

  • 集約から発行される以外にも、ユーザのアクションによってクライアントから直接イベントを作られる設計
  • イベントを集約としてモデリング
  • モデルの構造の一部にできる

  • ドメインイベントは脇役ではなく第一級のオブジェクト

  • ミュータブルとイミュータブルをわけられる
  • ミュータブルな領域を狭めてモデルの複雑性を低減

永続化の注意点

  • データとして保存する

まとめ

  • messagingのひとつとしてのevent message
  • ドメインイベントとしてモデリングするだけでも有用

  • メッセージングのモデリングの有用なパターンが本に書いてある!

  • EIPにはエンタープライズ領域でのシステム構築に使えるヒントがいっぱい

  • RMPはActor Modelへの適応を実践している

プロセスマネジャーとバックプレッシャー メモ #reactive_shinjuku

  • 長期に渡るプロセスの中央集権的な管理を行う実装パターン
  • actorを使うと実装しやすい

  • 問題 未完了のプロセスが増える

  • 解決策 アクターにバックプレッシャーをもたらす必要があること

  • actorで実装したい

  • バックプレッシャーを使うとプロセスマネージャが管理しやすいこと
  • 同時に処理していく量が増えるとバックプレッシャーをかける
  • 最大値に達したらメッセージを受け付けない
  • 実装にはakka streamを使うとakka actorと連携が容易になる
  • source 一つの出力のみをもつストリーム
  • sink ひとつの入力のみをもつストリーム

  • actorのなかにsourceとsinkがいて、あわせて入出力のストリームが出来ること

  • プロセスマネージャが完了したときにバッファーから処理中のメッセージを取り除くこと
  • メッセージはストリームを通るのでストリームの多様なステージの恩恵を受けられる throttleを使う

ノンブロッキング・フレームワークでのスレッドプールの使い方 メモ #reactive_shinjuku

  • 同期処理と非同期処理
    • 取得を待つ 逐次的に処理
    • 取得を待たない
      • 処理が複雑になる
  • スレッドの枯渇
    • Request-Response
  • 遅延の伝搬

  • サブシステムの障害をシステム全体に波及させない

  • 障害が発生しても応答を早く返す

  • bulkhead-pattern

    • 道路が1つしかないと事故ですべての車両が待たされる
    • 道路を複数用意しておくと事故の起きている経路を通らない車両は待たされない
    • スレッドプールは障害ドメインによって分ける
      • データベースや外部サービスとの接続
      • スレッドの枯渇による影響を最小限に
      • バルクヘッド 船の隔壁
  • little's law
    • L = λW
    • L スレッド数 λ 到着率 W1リクエストあたりのk処理事案
    • スレッドを効率的に使えるように適切なプールサイズを見積もる
  • curcuit-breaker
    • 経路を通らせないのでどの車両も待たせない
    • 応答性を保つ
    • 障害が起きた経路はフェイルオーバーする