kbigwheelのプログラミング・ソフトウェア技術系ブログ

プログラミング・ソフトウェア技術系のことを書きます

PofEAA感想 4章~8章

第4章

サーバページ手法は「アルバム #1234 の詳細を示せ」など、レスポンスの処理が少ない場合に有効である。

PHPASPなどの仕組みについて。たしかに静的HTMLが多いサービスではPHPによる最低限のスクリプティングは有効だが、 現代ではそのようなサイト・サービスはほぼ新設されないだろう・・・。

コントローラは、多くの異なるコンテキストで使用される。

せやなあ。お互いの指しているコントローラの定義が違うことでよく混乱が生じた。

テンプレートビューを使用すると、(中略)結果として、サーバページ技術を使用するのであれば、多くの場合ヘルパーオブジェクトを使用して、ページ構造から切り離してプログラミングロジックを保持することを守らなければいけない。

せやなあ。そしてそれはどう考えても良い手じゃない。

テンプレートビュー、トランスフォームビューについて

現代ではAPI + リッチクライアント(SPA)が主流?トランスフォームビューのたとえで使われているXSLTって今まで一度も聞いたことなかった。

A single-stage view mostly has one view component for each screen in the application.

ほとんどの場合、シングルステージビューには、アプリケーションの画面ごとに1つのビューコンポーネントがある。

この訳はいくらなんでもおかしくね?

シングルステージビューはほとんどの場合アプリケーションのそれぞれのスクリーンごとに1つのビューコンポーネントを持つ。

"には" "がある" の翻訳が致命的にニュアンスを失わせている。 この辺でもう英語版読もうかという気になった。 この翻訳、こちらでいう腐った翻訳に該当する気がするなあ。

Page Controllersはクラシックな1エントリポイント1ビューな感じ。 その下のFront Controllerはエントリポイントが1箇所に集約されている、golangのweb serverみたいな感じ。

第5章

並行性のテストは難しい。

確かに。スレッド/プロセスを制御しつつテストすることは難しい。 進行状況を任意の状態に制御しつつだとなおさら。 さらにそれが容易に可能だったとしても今度は並行状態のパターン列挙は非常に難しい。 おそらく入力値の境界値分けによるテストケース生成と同じようなパターンになるだろうが、引数が増えれば値の組み合わせが増えるように並行実行の場合実行されるスレッドが増えるにつれテストケースもn乗で増えていくのである。

システム受け入れテストで最低限の動作保証を行うのが第一歩だろうが、この手の並行性の問題はカオスエンジニアリングを用いて本番でテストをしてすら検出することは難しい。 並行性の問題は現実的には起こらない理論上の問題ではなく、特定の状態、特定のタイミングでつまり確率的に必ず一定確率で起こるという質の悪い問題だから、無視することも難しい。

おそらくは並行処理を最低限に留める戦略が良いのではないかと思う。 複数・大量のデータをマルチスレッド・プロセスで並行実行するOpenMPのようなイメージの並行処理、これは良い。 なぜかというと以下の理由からマルチスレッド化したことによる複雑性の上昇が最低限であるためである。

  1. 処理対象と結果のデータが各スレッドで縦割りされており、スレッド間での書き換え可能なデータ共有(共有メモリモデル的な)が発生しづらい(共有メモリモデルによる並行処理の問題点はコップ本など参照)
  2. 各スレッドの処理が一様であり複雑性が低い

無論このタイプの並行処理を行う場合でもまずはパフォーマンス分析を行うことが最優先である。 80:20の法則を理解してネック部分だけをデータを基準とした平行化するのは十分に利益あるんじゃないかな。

ノードベースの並行性はできるならノード間結合テスト(全体テスト)で検出したいところ。

One of the great ironies of enterprise application development is that few branches of software development use concurrency more yet worry about it less.

皮肉なことに、エンタープライズアプリケーション開発において、並行性を使用することがなく、そのことを問題にしていないソフトウェア開発部門もある。

また誤訳

皮肉なことに、エンタープライズアプリケーション開発において、並行性を使用しているが、そのことをあまり問題にしていないソフトウェア開発部門もある。

こんな翻訳でよく他の人は読めてるな。本当に正常に中身を理解できているんだろうか?

あとこの指摘は非常によく分かる。PHP系のWebアプリケーションを開発している人間が並行性についてかなり無頓着なのはこの背景があると思う。

As long as you do all your data manipulation within a transaction, nothing really bad will happen to you.

ここまで読んでやっと気づいたけど、こう書いてあるってことはここまでの話に出てきた並行性って主にデータソースが絡んだ外部永続化データの操作に関してで、共有メモリモデルのことはまったく念頭に置かれていない?

その後やっとマルチスレッドの話が出てきた。これまでの並行性の話はapacheがリクエストの度にspawnするスレッドのイメージだったようだ。

After all, if you aren’t familiar with source code control systems, you really shouldn’t be developing enterprise applications.

最後に、もしあなたがソースコード管理システムに慣れていないようであれば、あなたは本当にエンタープライズアプリケーション開発をするべきでない。

辛辣w けどそのとおりだよなあ。

しばしば一貫性のない読み込み問題は見過ごされがちである、なぜならほとんどの人々は更新喪失問題が並行処理の最も重要な問題だと捉えがちだからである。

せやな。

Temporal reads

こんなところで過去に技術的に衝突した問題の解決策を見るとは・・・。

デッドロックの解説、とくにその回避策の列挙と説明・例がめちゃくちゃわかりやすい。

Consistency: A system’s resources must be in a consistent, noncorrupt state at both the start and the completion of a transaction.

一貫性(COnsistency):トランザクションの開始時点から終了時点まで、システムのリソースは必ず一貫した正常な状態で無くてはならない。

おしい。開始から終了までではなく開始と終了時点のみでいい。間ずっとだとしたらbetweenを使う。

Across multiple transactions the application’s responsibility is to ensure that one session doesn’t step all over another session’s changes, leaving the record set in the invalid state of having lost a user’s work.

複数のトランザクション間での(一貫性をサポートする)アプリケーションの責任は、レコードセットをユーザーの作業を失った無効な状態にしておくことで、あるセッションが別のセッションの変更を一歩も踏み出していないことを保証することです。

ちょっとよくわからん。作業中の状態をデータベースへ一切書き出さないってこと?

そのあとビジネストランザクションはセッションと1対1で紐付けるほうがよいって書いてあるけどそのとおりだとは思う。 ただ、近年の高度に自由度の高いアプリケーションではセッションが使われない・使えない・使いにくい状況も多く、このプラクティスに十分な現実性があるかはちょっとわからない。

5章読了。長い上に概念的にも難しい単語が多くなかなかきつかった。 これでこの分野の上辺だけっていうのだから並列実行はなるべく簡単な部分のみ触っていきたい。

第6章

開幕でStatefulをステートレスと誤訳していてうんざり

この6章は旅行中読んでいたのでメモはほぼない。

第7章

この章も翻訳ミスが多い。価値を表すValueを値と翻訳していたり、有名なData Transfer Object → データ変換オブジェクト の翻訳ミスなど。

XML - httpインターフェイスについてはこの時代での意見でしかなく、今ならRESTful API、更にはgraph-QLが主軸になるだろう。

第8章

ユニットオブワーク、先に11章を見て概要を把握したけどアプリケーション内でDBのサブコピーを作ってビジネストランザクションレベルでの変更を一旦プールし、ビジネストランザクションの終了時 = コミット時に本当のDBへ反映するようないわば階段の踊り場的仕組みを指しているらしい。 多数のサーバが並列でスケールするような今、そういった仕組みは1サーバで完結せず良くない気がするね。

ここでJavaと.NET環境にこだわるのは、これらが将来にかけてエンタープライズアプリケーション開発の共通プラットフォームになる可能性が高いからである(ただし、個人的にはPythonRubyなどの動的に型付けされるスクリプティング言語を検討したいし、競争も必要だと思う)。

書かれた時代を考えるとなかなかの慧眼。

ストアドプロシージャについて

せやねぇ、たしかにこれを使えば大小の問題はあるものの基本的にパフォーマンスは上がる。 けどポータビリティや冗長化・エラー処理・スケーリングに問題が多いから他の選択肢が有効でない部分だけ特効で最適化するために使うって方針がたしかに無難。

Webサービスについての考察は時代背景的に古いモノではあるものの、確かにマイクロサービス的なものも視野に入っているのはやはりさすが。

Webサーバ/APIサーバ用コンテナイメージの標準化を考える

きっかけ

まだ僕がコンテナ技術に不慣れなこともあって、こういった標準仕様が存在すればよいなと思っていたことがちょうど言語化されたものをtwitterで見た。

  • コンテナに割り当てるリソースの管理
  • オートスケーリング
  • graceful shutdown
  • logの転送
  • 秘匿情報のinject

これらはコンテナでWebサーバやAPIサーバを構築する場合どれもほぼ必須だったりあれば非常に便利にも関わらず、実行環境や個人の好みに実装が依存してしまっている。結果的に各地で車輪の再発明が行われたり手間を理由に実装が後回しにされている。

Web Developer も知っておきたい Kubernetes における Sidecar Pattern と Ambassador Pattern - Quipper Product Team Blogこの記事のようにここの項目で優れた技術やアイデア・仕組みが創出されても再利用されづらいのが問題である。

提案

次のようなコンテナイメージのインターフェイスの標準仕様はどうか。

  • 名前: Elastic Container Implementation Standard 柔軟性を持ったコンテナ実装の標準仕様(仮)
  • 利用方法: コンテナ作成後、そのコンテナのインターフェイス仕様を記したJSONファイルを作成してセットで取り扱う。
  • JSONサンプルイメージ
{
    "resouce": {
        "memory": "4G",
        "vcpu": 2
    },
    "scaling": {
        "min_instance": 1,
        "max_instance": 10,
        "min_cpu": "3%",
        "max_cpu": "90%",
        "min_memory": "10%",
        "max_memory": "80%"
    },
    "shutdown": {
        "max_wait_disconnection": "60s"
    },
    "log": {
        "path": "/var/log/apache/"
    },
    "variable": [
        "env",
        "db_url",
        "db_user",
        "db_password"
    ]
}

まずコンテナイメージ作成側のメリットとして、無数のバリエーションがあったリソース管理やオートスケーリングしきい値の設定方法、logの転送方式が標準化され、実装者による仕様のブレがなくなる。 コンテナ基盤(kubernetesやECS、あるいはその上に構築されるコンテナ実行環境)側のメリットとしてはまず提供するべき機能が明確になる。次にコンテナ側の仕様が固定されるため個別のコンテナの仕様に合わせる必要がなくなる。

次のステップ

実際に上の仕様にそってコンテナイメージを作ってみる。 また、その仕様に沿ったコンテナを実行できる環境をKubernetes(できるならEKS?)ベースで構築する(CloudFormationかTerraformを使いたい)

蛇足

イデアとしては動かすアプリケーションの仕様をyamlで定めたElastic Beanstalkと思想が通じるものがある。 この仕様はBeanstalkがサポートしていたDocker以外のアプリケーションを切り捨てることでDockerに特化し、かつ仕様をシンプルにしたものと言える。

PofEAA感想 はじめに~3章

はじめに

HTMLよりも優れたフロントエンドを望むためリッチクライアントが求められる

内容が古いため今の実情と乖離しているところもある

その理由は、パフォーマンスに関するアドバイスは、現実のシステム構成でパフォーマンスの測定を行ってみるまでは、当てにならないからである

現在になるまでこの点は一切変わってねえなあ。だからカオスエンジニアリングなど生まれたんだろうが

第1章

編集途中で消えて萎えたのでなし

第2章

レコードセットが何を指すのかわからん。 単なるRDBMSの行の集合ではなさそう。 → P36

レコードセット、つまりデータベースのテーブルの性質を持つ、テーブルと行で構成された包括的なデータ構造

第3章

3.2、ユニットオブワークという名前でDBデータ(レコードデータ)の書き込みを含めたオンメモリ上でのキャッシングの話をしているけど、 今の横にスケールさせるアーキテクチャではキャッシング機構が1サーバで完結しないので微妙な気がするね。 現代ではもっとシンプルなキャッシュとトランザクション手法が取られるんじゃないかなあ。

3.3、joinを使って複数のテーブルのデータを一気に返す方法、それはできるけどさすがに悪手だろw DBへのクエリ回数が膨大なシステムだとそうやって最適化する方法はあるかもしれないね。最終手段だろうけど。

3.4.1のオブジェクト型とリレーショナルデータベース間の関係情報の保持の仕方についての説明は非常によくわかる。 しかしその後の一意マッピングやレイジーロードといったアプリケーションサーバ内での頑張りすぎるキャッシング手法はあんまりイケてない。というか現代でやったら顰蹙モノの複雑さだと思う。

LOBに関してDB自体がその内包データを理解できないという欠点は、現代ではJSON型としてOracle, Mariaなど各プロダクトが限定的ながら解決しつつある感じ。

3.4の継承とRDBの問題については継承をそもそも否定的だからこの手法もややこしくなっているのだと思う。 たしかに説明されている3パターンは現実に取りうる解だが、そもそもオブジェクト指向とリレーショナルデータベースをうまいことマッピングするのがORマッパーなり各種DBライブラリの役割なわけで、継承という非常にオブジェクト指向らしい特徴をリレーショナルデータベースに持ち込む事自体が相当な悪手じゃねえかなあ。 しかしそれを考えるとオブジェクト指向RDBインピーダンスが発生しているという指摘は重い。DBの接合部分に全体の1/3のコーディング労力が割かれているというのもあながち大げさではあるまい。

3.5.1、相変わらず技術書の英訳はガバガバ。

The extra step only pays for itself when you have many commonalities, so you should use it when you have similar but annoyingly different physical data stores.

追加のステップは、多くの共通性がある場合にだけ有効なので、類似性はあるが異なる物理的なデータを格納する場合は追加のステップを使用するべきである。

data storesのstoresを動詞として約してしまっている。実際には名詞。 以下が適当か

追加のステップは多くの共通性がある場合にだけ有効なので、類似性はあるが異なる物理的なデータストアを2つ以上使用する場合に追加のステップを使用するべきである。

3.7 データベースのコネクションプールは本質的に状態を持ってしまっている(使い回されるコネクションが前回の利用の影響を受けていないことが保証できない。また接続されて最初のクエリとその後のクエリで挙動やレスポンスタイムなどが変わることもある) = immutableでない ことからあんまりよくないかも。サーバレスフレームワークが前提のアーキテクチャではコネクションプールは期待できないし、各リクエストの独立性を高めるためにも毎回コネクションを張り直す方式が取れるならそのほうが良い。またこの本自体にもそう書かれていた(意外)。

新しい接続の生成速度が速くなっているなら、プールする必要はない。

This advice leads to a couple of issues: making sure you have the connection everywhere you need it and ensuring that you don't forget to close it at the end.

この方法では、2、3の問題が生じるので、必要であれば接続し、終了時には忘れずクローズするべきだ。

ひどい訳。以下が適当。

この方法には次の2つの問題がある: あなたがコネクションを使うところすべてでそれを持つ(実質的に作る = コネクションを張る?)必要があることと最後にそれを確かにクローズしないといけないことだ。

しかしこれだけ面倒くさいRDBオブジェクト指向型言語の間のパターンを多数見せられると両者の間のインピーダンスの高さを実感する。 RDBを使うのって今は当然だけど本当にそうか?オブジェクト指向型言語でいいのでは? まあでもそれMongoDBか。

いいねが2,3個しか付かなさそうなニッチな情報でも、それでもQiitaにそれを書くべき理由

これで困ってるの日本で自分を含めて2,3人じゃねーかって情報がときにある。 そういうことに限って大きなブロッカーで数時間、場合によっては日単位でタスクを遅らせることがある。

そういった情報はQiitaで記事にしてもいいねはほとんど2桁行かない。

だが、逆に言うとその情報がウェブ上にあれば数時間、あるいは数日節約できる人間が日本に数人はいる。

上のはどれも確実に数時間単位で作業時間を節約してくれた記事である。 特に最後のそれはこの記事を見つけなければ解決が極めて難しかった可能性が高い。 僕はこれらのような記事を書いてくれるエンジニア同輩にとても感謝している。

このような人々に報う方法を考えたとき、いいねを押して感謝を伝えることもよいが何より良いのは 自分ができる範囲で似たように困るかもしれない、日本全国で2,3人の後続のために記事を書くことではないかと思うのだ。

だから、ニッチな情報でもQiitaへ記事を書くべきである。 初出の情報であれば素晴らしいが既存の情報などを整理したり情報を付け加えるだけでも良い。 きちんと調べて正しいと思ったことを書くことは重要だが、正直なところ間違った情報や推測ですら十分に貴重だったり価値があったりする。 間違ったり古くなった情報はコメントや修正リクエストで直していけばいいのだ。

1年遅く生まれたあなたのために、ニッチな情報でもQiitaへ記事を書こう。

「マイクロサービスアーキテクチャ」感想

O'Reilly Japan - マイクロサービスアーキテクチャ

おすすめ度: ★★★★☆

良かった。やんごとなき理由により非自主的に読み始めた本だったが想像していた数倍の価値がある本だった。 マイクロサービスの基本などが抑えられているのもよかったが、何よりマイクロサービスベースの組織やそれを支える体制など組織論的なところまで踏み込んでいるのが良かった。 それがことごとくそのときの会社の体制と正反対だったことは皮肉だったが。 かなり思想的に影響を受けたし、考えを補強してくれた点も多かった。

僕がDIを否定する理由

予防線

私が開発当初から関わったシステムでコード的に最大規模のものはScalaで書いた検索システムの2万行ほどのもので、あとは保守開発や1万行以下のサービスばかりです。 なのでその程度のへなちょこエンジニアの主張なんざ聞くに値しないという主張は一理あるかもしれません。 ですのでもっと大規模なシステムでは以下の理屈は通用しないというようなことはあるかもしれません。

きっかけ

これを見てそういえばScalaを始めたときcake patternなど勉強したことを思い出した。 今はTagless Finalというものがあるのか、なるほどと思いつつこの手のものをそういえば使わなくなったのはなぜか思い返したところ、これがDI技術であることが理由だった。 そういえばある時からDIは根本的に良くない気がして使わなくなったのだった。

そもそもDIとは

猿でも分かる! Dependency Injection: 依存性の注入 - Qiita など今はウェブ上に非常に理解しやすい説明があるので好きなやつを見たらいいと思う。

僕流に解釈して説明すると以下のようになる。

関数、プログラム、アプリケーション、サービスのどの単位でも必ずセットになるものがある、入力と出力だ。 そしてテスト*1とは特定の入力に対して期待した出力であるかを試すものである。例外はない。

基本的にテストするときは対象の外側から入力を与えてやる。関数であれば引数、クラスであればコンストラクタ引数、APIであればクエリパラメータやヘッダである。 しかしDI技術はこの原則を無視する裏技を提供する。直接テスト対象のプライベート変数(内部変数)へ値を注入(Inject)できるようにするのである。

僕が思うDIの欠点

直接テスト対象のプライベート変数(内部変数)へ値を注入(Inject)するDIは次の欠点を生み出す。

  1. (コンストラクタ)引数以外は不変であるというクラスの原則・直感を破壊する
  2. テストにおける入力の範囲が曖昧になる
  3. 範囲が曖昧になった結果、本来不変であるまたは不変にできるような値もDIで注入できるようにしてしまう
  4. 入力の変数が増えた結果入力のパターンが爆発的に増加してテストが書きづらくなる

ただ、以上のような欠点をDIを使っている人に何度かぶつけてみるとそれらは一理あるものの以下の要因により必要悪としてDIは使われているという反応が多かった。

  1. コンストラクタで変数へセットするオブジェクトを組み立てているとそこをモックすることが困難になる
  2. 多数の入力(=依存性)があるクラスではそれらをコンストラクタで決まった順番に渡すことは苦痛になりやすい

これらの反論もまた一理あるものであるが、どこか納得出来ないものを抱えてなかなかDIを積極的に使うことができなかった。

解決のヒント

DIについて調べたり軽く使ってみつつ数年経ったのちに読んだ2つのアイデアが解決の糸口になった。

一つは "TDD is dead. Long live testing." 一番最初に読んだ日本語の記事は忘れてしまったけど、この要約など良い。どうやらテスト駆動型開発は死んだようです。これからのCI 重要なのはTDD is deadといいつつ実際にはユニットテスト的な粒度の小さいテスト駆動での開発の否定で、機能テスト・統合テストなどと呼ばれる粒度の高いテストをちゃんとしましょうということが要点(だと僕は捉えた)。

もう一つはMicroservice。 これの良いところ悪いところいろいろあるが僕が目をつけたのはインターフェイスがWeb APIとして定義されること、中身が十分に(Microservice architectureという本によれば、たしか1,2週間で書き直せる、だったかな)シンプルであることだった。

この2つのアイデアを組み合わせて僕は以下のように今は考えている。

結論

  1. テストはAPIそのものに対して行われ、個別のクラス・関数に対しては書かない。さらに各リクエストがAPIサーバ内で相互に影響を与えない*2のであればモッキングが必要なのはAPIサーバが依存するDB、memcahceまたは外部のAPIサーバなどになる。つまりDIによる強引なモックの注入が必要なくなり、ひいてはDIも不要になる。

*1:ユニットテスト結合テストなど粒度を問わない

*2:DBサーバやmemcacheなど外部の永続化層で間接的に影響し合う分には問題ない