日々のこと

まいにちの暮らしをつれづれ書きます

texta.fm 聴いてます

最近、設計の考えを広げるために texta.fm という podcast がおすすめだと教えてもらったので、少しずつ聴いてます。

anchor.fm

ピクスタCTOのYasaichiさんと、技術顧問をされている t_wadaさんのpodcastです。パーフェクト Ruby on Rails(増補改訂版)の書籍が題材になっています。

技術の生まれた時代背景や設計思想など技術についての様々な話が歯切れよく語られていくので、聞いていてとてもおもしろいです。
興味深い!と思いつつ聞いたそばから忘れてしまいそうでもったいないので、印象的だった部分について備忘録として書き留めていこうと思います。自分の主観で、気になったところをメモしています。

聴いた感想

  • 自分は Rails で開発しているので、他のフレームワークや設計論の中でのRails の設計思想などがテンポよく語られていくので、とても聴いていてたのしい
  • いま、達人プログラマを読んでいて「契約による設計」の話がでてきてピンときてなかったが、2話目を聞いて解像度があがった気がした
  • サービスオブジェクトは、Rails のレールから外れてしまいがちで悩みどころが多いのだろうかと思って聴いていたが、イベントをモデルとして定義することで Rails の世界にかちっとはまる、という話を聴いて、そんな考え方になるのかと目からうろこだった
  • 3話まで聴いたが、続きも聴いていきたい

1. Software Development in 2003

  • パーフェクト Ruby on Rails 11-1 「アーキテクチャパターンから見るRails」が題材
  • ドメイン駆動開発(DDD)の書籍についてとその当時の背景などについて
  • DDDが解決しようとしてたのは、分析モデルとコードの乖離、開発者とビジネス側の乖離の2つだった
  • ソースコードドメインモデルの一部であり、ソースコードとモデルとで双方向にフィードバックサイクルをまわしていくというようなことが書かれいて画期的だった

Sideshow 1. The School of OOP

  • RailsActiveRecord パターンなのでデータモデリングがほぼ全て
  • ピュアなオブジェクトモデリングをどうRDBに落とし込むかという話をしてたところに、Rails が、DB設計をしっかりしていたらほとんどのシステムうまくいくという挑戦状を叩きつけてきていて、Rails は最高にロックだった
  • ドメイン駆動開発の本が出た時代にはまだ Rails は生まれてなかった
    • Rails の出る1年前にDDD本が出版された
  • そういう部分を差し引いて考えながらDDD本を読んでいくと良さそう

2. The Power of Constraints

  • パーフェクト Ruby on Rails 11-2 「値オブジェクト」
    • 値オブジェクトのことは Value Object と用語を統一して話していた
  • Value Object の Immutability とバリデーションについて

Value Object

  • Value Object は値が変わらないので共有することができる
  • 普遍性が大切
  • 例. 小切手の額面を持っている Value Obect
    • そのオブジェクトが生存している間は値が変わらないことを保証している
  • Entity と Value Object のちがい
    • 値が等価であるかを比較するものが、識別子で比較するのが Entity、値で比較するのが Value Object
  • Value Object は完全コンストラクタパターンによって実装されている
    • インスタンスが生成されたら変わらない
    • Value Object はコンストラクタでありえないものだったら assertion や例外を投げる

契約による設計について

  • アサーション(表明): ありえないもの
  • 例外: ありえるもの
  • 契約による設計では、事前条件は呼び出し側が守るべきもの(契約)
    • 守らなかったらなにが起こるか知りませんよ
    • 防御的プログラミングの問題を解こうとしていた
    • 防御的プログラミングは局所最適化、全体見るといろんなところで同じチェックをしてしまっている
    • それを解こうとして契約による設計という考え方
    • 信頼することによって全体の保守性をあげましょう
  • Rails のモデルは不正な状態がありうる(valid?メソッド)
  • Rails とそれ以外とで Webシステムをつくるために必要なレイヤーの数や構成しているオブジェクトの数が違う
    • Railsは少ない、それは DHH のセンスが良い/割り切りがすごいから
  • ユーザーからの入力は同時に複数の誤りがありうるので、誤りを受け止めて誤りをまとめて返す
  • バリデーションするのは状態(valid or invalid)を持つ入れもの
    • 合っている合ってないを含めて返す
  • Entity とバリデーションするものとでは、役割が違う
  • バリデーションするところは外のデフェンスライン
    • ユーザーが不正な入力をしたら浅いレイヤーですぐにまとめて返す
  • Rails以前は、バリデーションエラーを持っている主体は Form Object というものだった(画面に近い)
    • UIに引っ張られる
    • 情報の入れ物は画面に結びついている
  • 従来は、データベース、永続化された情報とのやりとりをするものとは、役割もレイヤも別だった
  • DHHが割り切ったのは、うまく作れば、画面の入力項目とデータベースのカラムはほとんど一緒なので、一緒にしてしまえば登場人物が大幅に減る
  • Rails における ActiveRecord のオブジェクトたちが、外部の入力を受け付けて不正な状態になることを受け入れることができれば Form Object をなくすことができる
  • Java エンジニアが Rails 見るとカルチャーギャップがあるが、その代わり、レイヤの構成要素とかなりのコードを減らすことができているのが Rails の強かったところ
  • 旧来、Form Object と Entity とでのバリデーション/ディフェンスが重複していたが、Railsはそれをモデルに寄せる力強い割り切りをしている
  • 本来違う役割のものがひとつの入れ物に入っていることを理解しておく必要がある

Ruby on Railsの正体と向き合い方 / What is Ruby on Rails and how to deal with it? - Speaker Deck

Sideshow 2. Testing Programmers' Loyalty

  • アサーション と例外の違いについて
  • アサーションと例外とで重複しているのではないかという問い
  • この問いに関して、「契約による設計」と表明(Assersion)プログラミングを分けて考えたい
  • 表明プログラミングは、あり得ないということについて表明を書いていくスタイル
  • 契約による設計は、事前/事後条件の表現として Assert を使っていくことが多い
  • 契約による設計をサポートしているプログラミング言語もいくつかある(Swiftなど)
  • 例外と表明は、ありえると思っているかありえないと思っているか
  • 表明「すでに言ったでしょ」「Null渡すなって言ってるじゃん」
    • プログラマに対する情報、エンドユーザーに対する情報ではない
    • 自分たちのプログラミングミスを早い段階で見つけるためのデバッグの道具
    • 表明は雑で荒っぽい、呼び出した人が悪い
    • 開発時に捉えなければいけないもの
  • 例外はありえるかもしれないので、備えているもの
  • Assertはtrueになる式が書いてある「こうでなければならない」
  • 例外は逆、違反していたらこういうことですよ、というもの
    • 例外は人間には認知負荷になる
  • 例外のプログラミングはごりっとしたコードになる
  • 表明を使ったプログラミングは仕様の記述みたいになる
  • 見た目は似てない
  • Assertionはドキュメントの代わり、仕様のコミュニケーションの道具
  • 例外は条件式が逆なので仕様の読み取りに少し認知負荷がかかる
  • お前はその表明を消せるのかという踏み絵について
    • よくよく考えるとやはり例外でお願いしますとなったりする
    • ありえないものではなく、実はあるかもしれない
  • 外側に防衛ラインがあり、その中には valid なはずで信頼をベースにしたプログラミングができる
  • Entity と Value Object の setter に何を書くべき?
    • Java の表明は public メソッドには使うなと書いてある
    • publicメソッドはどこから呼ばれるかわからない
    • アサーションをオフにしたら、public メソッドの引数のチェックをしなくなってしまうのでそれはだめとしている
    • Java は defensive programming 寄り
    • Value Object のインスタンスが必要だということは、それは valid であるということは絶対に守りたいライン
    • Value Object が valid かどうかのディフェンスはコンストラクタ(入り口)にあっても構わない
  • 全体最適(どうやったら利益が最大になるか)を考えて、適宜 Assertion/Exception を使い分けていく
  • 現場でそれを悩まないといけない
  • Assertionは組み込まれていない言語もある
  • どのくらい堅牢性が求められているかなどにもよる
  • Webで問題が起きたとき、すぐ修正してデプロイできるのであれば被害は少なく品質は高いとみなすような MTTR ベースの品質保証の考え方では、即死してすぐ検知できるならそっちの方がいい
  • 組み込み、クライアントを配布するソフトウェア(iOS/Androidアプリ)はなるべく死なない堅牢性、MTBFでなければいけない
  • コーディングスタイルが変わってくる
  • フェイルファースト
    • あり得ない状況が発生したらすぐ例外を発行して死んでOK
    • すぐ発見されるならその方がコストが低いが、組み込み・アプリはそうはいかないので、プログラミングスタイルが変わる
  • 何が求められているのかデプロイメントを開発者がコントロールできるかによってコーディングスタイルは変わってくる
  • 自分たちの置かれた状況に自覚的になって、最適なスタイルを選択することを愚直にやっていきましょう

PHP7 で堅牢なコードを書く - 例外処理、表明プログラミング、契約による設計 / PHP Conference 2016 - Speaker Deck

3. Low-Code Development

パーフェクト Ruby on Rails 11-3 「サービスオブジェクト」

サービスオブジェクト

  • サービスオブジェクトは禍根を残しがち
  • 導入時のポイント
    1. モデルに実装すべきロジックまで実装しない
    2. イベントの見落としがないか確認する(Immutable data modeling に紐づいている)
  • サービスオブジェクト
    • ドメインモデルにできなかったけど存在するドメインロジック(手続き)をオブジェクトとして定義したもの、と捉えている
  • 「サービス」3つ
    1. サービスを独立したレイヤーと捉えているもの
      • コントローラとモデルの間にあるもの
      • モデルに依存するけどコントローラに依存しない
      • モデルに置くにはおかしいロジックの置き場所としてサービスという第4のレイヤに持ってくる
      • トランザクションスクリプトの置き場所としてサービスレイヤを定めている
      • 手続き指向でロジックを書いていく
    2. ドメインレイヤの中身だけどドメインモデルの中の一部
      • DDDサービスオブジェクトの考え方
      • ドメインを構成する基本的なオブジェクトは3つ
      • Entity、Value Object、Service Object(ドメインロジックが置かれている場所)
    3. 分散システムにおける各分散ノードが公開している責務としてのサービス
      • 認証サービスetc
      • マイクロサービスの公開している機能群
  • 2つ目のサービスの定義と思って パーフェクト Rails の書籍を書いていた
  • ドメインロジックを置く場所としてサービスオブジェクト
  • モデルのレイヤに近いもの
  • ドメインモデルにおさまりきらなかったサービスオブジェクト
  • Railsのレールに乗っていないので設計しづらい
  • RubyRailsでどうサービスオブジェクトを作るか悩みがち
    • クラスではなくてもいいのでは?後々のTestabilityが関係してくる
    • 誰がサービスオブジェクトを作るのか(newするのか) or newをやめさせてコンストラクタを封じてシングルインスタンスの世界で状態を持たないようにするのか
    • サービスオブジェクトには状態がなくふるまいしかないので、インスタンスがなくてもいい(Procでいい?)
    • 一貫性のあるインターフェース or 名前をつけることができれば、混乱をある程度は防ぐことはできる
    • 基底クラスを作ってインターフェースの実装を強制させるという作りをしたくなったりする

Immutable Data Modeling

  • updateとdeleteを極力しないということを指向するデータモデリング
  • データベース設計の流儀の一つ
    • 業務システム(Enterprise)のデータベース設計の流れを汲んだ流派
  • 更新と削除のないデータモデルを作ることができれば、そこから事実が失われることはないという考え方
  • イミュータブルデータモデル - kawasima
  • 前段として、データと情報のちがい
    • データ: 意志を持たない事実の積み重ね(テーブル・カラムの設計)
      • ex. 毎日の気温を記録
    • 情報: 意志を持って、何かを知りたい人間がデータの塊の中から特定の視点で切り出したもの(SQL
      • ex. 去年と今年の10月の平均気温の差
  • どういう情報がほしいかはわかっているものもわかっていないものもある
    • 情報がほしい時に取り出せるようなデータ設計をしたい
    • 事実をなるべく失わずにデータベースにためておく、事実が失われなければ、情報は後から知りたい視点で作って取り出せるようにしようというのがRDBの考え方
  • どういう情報がほしいかは日々変わる
  • データベースにほしい情報の素が入っていないと情報を取り出せない
    • 設計の不備、新たにデータを格納するしかない
  • データベースに情報がはいっていても失われていたら情報は取り出せない
    • データモデリングのミス、入っていたものがなくなってしまって取り出せなくなってしまった
    • これを防ぎたいのが、Immutable Data Modeling の考え方
  • delete or updateしなければ入っていたデータがなくなる・書き換わる(改ざんされる)ことはない
  • エンタープライズシステムにおいて、企業の活動を漏らさず格納するのが大事
    • update文の発行はデータの改竄、delete文の発行はデータの削除、この2つは企業の活動の信頼性に大きく関わるもの
  • 業務システムは、実務的にはupdateもdeleteも発行するが、論理モデリングの段階では削除も更新もしないという制約をはめて考えていくとうまくデータモデリングができる(一種の思考実験)
  • updateやdeleteを行わずに、企業活動やシステムの様を記録していくにはどう考えていけば良いかとデータモデリングをしていく
  • 例)人事系システム 社員どうしの関係に上司と部下の関係(Employeeテーブル)
    • 単純な設計: 社員テーブルに上司の社員のidを入れる(外部キー)
      • 上司が変わる場合、上司のidをupdateする
      • 現在の上司しかわからない(前の上司は誰かという事実が失われる)
    • updateしないようにしなければいけない
    • 上司部下の関係を、社員テーブルの外に出して交差テーブルにinsertしていく
      • 上司が変わる場合、新たな上司部下の関係をinsertして、日付を入れる
      • 現在の上司を見るには、上司部下の最新のものを持ってくる
      • 事実を追記していくやり方
  • 効率はどうなるのかいろいろ気になってくるので、設計によりそれをイベントにするかリソースにするかという話が出てくる
  • updateやdeleteはある切り口(スナップショット)だけ取っておく行為
    • Rails は scaffold すると、update/deleteも一緒に生成される、こうした設計は単純さを取っている
    • Rails はシンプルなルールを仮定することで、全体の作りを減らして、考えることを減らすことにより生産性を上げ、コードの量を減らす
    • Rails が指向しているのは、ほとんどの Web のシステムで必要なのは最新のデータだけであるという考え方だろう
    • ほとんどのものはデータの CRUD で表現できる
  • Immutable Data Modeling でデータを追記していくとデータが増えていく、それへの対処は?
    • クラウドコンピューティングで、札束で殴ることができるので問題にならなくなるのもひとつ
    • Immutable Data Modeling は、一種の思考の枠
      • delete と update のない世界で考えると、これまで見失っていたものに気づけるようになってくる(イベントとリソース)
  • データモデルによる Entity をマスタとトランザクションとで分けるのが伝統的だった
    • ぶれがちであまりうまくいかない
  • イベントとリソースと分けた方がモデリングがうまくいく、ということが知られてきている
    • イベント: ある一点における活動を捉えたもの
    • リソース: そうではないもの
  • update と delete がない世界で考えると、これはイベントだったのか、というものが出てくる
  • リソースの update や delete で表現していたものがイベント
    • 「退会」とか、関係性がなくなったとか
  • 例)社員と部署があった時、社員と部下の関係をどうモデリングするか
    • 部署とユーザーの間に「所属」というレコードがある
    • 所属開始日、終了日が入っているが、update できないので、退職が表現できず困る
    • 💡 「着任」と「離任」というイベントにすればいいのではないか!
    • 着任と離任を紐づけて格納できるようにする
    • 関係性の発生も解消も表現できるようになる
  • 「着任」「離任」イベントのレコードが大量で、パフォーマンスが悪いということになる
  • 論理設計で「着任」「離任」イベントでモデリングしていたものを、物理設計で着任と離任を覚えていて計算して現在の部署を出すのは辛いので、「現在の所属」というテーブルはあっても良いのではという良い妥協が出てくる
  • Immutable Data Modeling で論理設計して、実装が近づいてきて現実的な計算量を考えた上で、物理設計で「現在の関係」というリソースは update / delete してもOKにする
  • そうやって設計すると、過去の着任・離任が取れるデータモデルになっている
  • Immutable Data Modelで考えると、事実を取りこぼさないような設計ができる
  • SQLがつらい、パフォーマンスがつらいという話になってくると、随時、最新のものを取ってくる View や Table を作るなど、物理設計の出番
  • 論理設計と物理設計でモードを分ける
    • 論理設計は理想の世界
    • 物理設計で実装が近くなってくると、現実の世界に近づいてくる

本題

  • サービスオブジェクトは実はイベントだったりする
  • 例えばパーフェクトRuby on Rails 11章「口座間の送金」は送金元の口座・送金先の口座どちらの責務かというと、どちらでもなく、「送金」というイベントを記録するべき
  • そうするとRailsの世界にカチッとハマる