- 複雑な Python アプリケーションは規模が大きくなるほど、単純な実装方法だけではテストや変更に対応しにくくなります。本書はその解決策として、アーキテクチャパターンをコード例で扱います
- MADE.com のオンライン家具販売とグローバルサプライチェーンを例に、現実の運用課題をソフトウェアモデルへ移すドメインモデリングを中心に据えています
- 中核となる軸は TDD、DDD、イベント駆動アーキテクチャであり、高速な単体テストと少数の E2E テスト、インフラから分離されたモデル、メッセージベースの統合を目指します
- Flask、SQLAlchemy、pytest、Docker、Redis を使用しますが、特定の技術選択が実装の詳細へ押し下げられる構造を作ることに焦点を当てています
- 複雑な Python アプリケーションを扱った経験があれば読める内容で、DDD や古典的なアプリケーションアーキテクチャパターンを事前に知っている必要はありません
テストしやすい構造から出発した問題意識
- Harry の前著 Test-Driven Development with Python の後、アプリケーションをどのように構造化すればテストしやすくなるのかという問いが残りました
- 重要なのは、ビジネスロジックを単体テストで十分に扱いながら、統合テストと E2E テストの数を最小限に抑える構造を作ることです
- 「Hexagonal Architecture」「Ports and Adapters」「Functional Core, Imperative Shell」といった概念には触れていたものの、当時は実際には十分に理解したり実践したりできていない状態でした
- Bob はチーム内にソフトウェアアーキテクチャを担う人がいなかったためアーキテクトになり、Ian Cooper からコードの書き方と思考法に関する新しいアプローチを学びました
MADE.com の事例:サプライチェーンをソフトウェアでモデル化する
- 2人の著者は欧州の e コマース企業 MADE.com で働き、本書の手法を実際のビジネス課題をモデル化する分散システムの構築に適用しました
- 例題のドメインは、Bob が MADE で最初に作ったシステムを基にしており、新しいプログラマーがチームに加わる際に教えるべき内容を整理する目的があります
- MADE.com は貨物パートナーとメーカーからなるグローバルサプライチェーンを運営しています
- コストを下げるため、在庫を倉庫に長く積み上げないよう配送を最適化しようとしています
- 理想的には、顧客がソファを購入したまさにその日に港へ到着し、保管を挟まず顧客の家へ配送される流れを望んでいます
- コンテナ船で商品が到着するまでに3か月かかることもあり、タイミング調整は困難です
- 実際の運用では、破損、浸水、嵐による遅延、物流パートナーの処理ミス、書類の不足、顧客の注文変更といった変数が絶えず発生します
- こうした問題に対処するため、現実世界の運用をソフトウェアで表現し、可能な限り多くの業務を自動化しようとします
Python プロジェクトが大きくなると見えてくる限界
- Python は急速に成長し成熟してきましたが、C# や Java の世界が長く扱ってきた種類の問題を、いま本格的に担い始めた段階だと見ています
- スタートアップは実際のビジネスになり、Web アプリやスクリプトによる自動化はエンタープライズソフトウェアへと成長していきます
- Python の哲学には “There should be one—and preferably only one—obvious way to do it” がありますが、プロジェクト規模が大きくなると、最も obvious な方法が複雑さや要件変更を管理するうえで常に適しているとは限りません
- 本書で扱う手法やパターンは新しいものではありませんが、その多くは Python の世界では比較的新しいものだと見ています
- Eric Evans の Domain-Driven Design、Martin Fowler の Patterns of Enterprise Application Architecture を置き換えようとする本ではなく、それらの古典を頻繁に参照し、読むことを勧めています
- 既存文献のコード例は Java や C++/# で書かれていることが多く、Python 開発者には読む負担が大きい場合があります
複雑さを管理するための3つの道具
- テスト駆動開発(TDD) は正しいコードを作り、回帰を恐れずにリファクタリングしたり機能を追加したりできるようにします
- できるだけ高速にテストを実行する方法
- 高速で依存関係のない単体テストから、できるだけ多くのカバレッジとフィードバックを得る方法
- 遅く不安定な E2E テストを最小限に抑える方法を扱います
- ドメイン駆動設計(DDD) はビジネスドメインのよいモデルを作ることに集中させます
- モデルがインフラ上の関心事に縛られないようにする方法
- 変更しにくいモデルにならないようにする方法を扱います
- メッセージで統合される疎結合のマイクロサービス、ときに reactive microservices と呼ばれる方式は、複数のアプリケーションやビジネスドメイン間の複雑さを管理する解決策として扱われます
- Flask、Django、Celery など Python エコシステムの既存ツールとどう組み合わせるかは、常に明確とは限りません
- マイクロサービスを使わない、あるいは関心がない場合でも、本書の大半のパターンとイベント駆動アーキテクチャの内容の多くはモノリシックアーキテクチャにも適用できます
読者と前提知識
- 本書は、ある程度複雑な Python アプリケーションを身近に扱ったことのある読者を想定しています
- 複雑さの管理から生じる痛みを経験した読者に向けられています
- DDD や古典的なアプリケーションアーキテクチャパターンに関する事前知識は必要ありません
- 説明はサンプルアプリケーションを中心に、章ごとに積み上げていく形です
- 著者たちは仕事で TDD を使っているため、まずテストコードを示し、その後に実装を示す流れをよく使います
- Flask、SQLAlchemy、pytest、Docker、Redis を使いますが、すでに慣れていれば役立つというだけで必須ではありません
- 主要な目標の1つは、特定の技術選択が些細な実装の詳細になるアーキテクチャを作ることです
本の構成
- 本書は2部に分かれています
-
第1部:ドメインモデリングを支えるアーキテクチャ
- ドメインモデリングと DDD は第1章、第2章、第7章で扱います
- 複雑なビジネス課題をコード内のドメインモデルに反映すべきだという前提に立ちます
- 外部依存がなく、高速な単体テストが可能なモデルから始める方法を説明します
- その後、適切な aggregate の選択とデータ整合性の問題を改めて扱います
- Repository、Service Layer、Unit of Work パターンは第2章、第4章、第5章で扱います
- これらのパターンは、モデルを不要な依存関係から切り離すために互いを補完します
- 永続ストレージの周囲に抽象化層を作り、システムの入口と主要なユースケースを捉えるサービス層を置きます
- Flask API や CLI のような薄い入口を作りやすい構造を示します
- 抽象化とテストに関する議論は第3章と第5章へ続きます
- Repository パターンを紹介した後、抽象化を選ぶ方法と結合の仕方における抽象化の役割を扱います
- Service Layer パターンの後には、テストピラミッドと、可能な限り高い抽象化レベルで単体テストを書く方法を扱います
-
第2部:イベント駆動アーキテクチャ
- イベント駆動アーキテクチャは第8章から第11章で扱います
- Domain Events、Message Bus、Handler パターンを紹介します
- Domain Events は、システム内のある相互作用が別の動作を引き起こすという概念を表す手段です
- Message Bus は、動作がイベントを発生させ、適切な handler を呼び出せるようにします
- イベントをマイクロサービスアーキテクチャにおけるサービス間統合パターンとして使う方法を扱います
- commands と events を区別し、アプリケーションは本質的にメッセージ処理システムになります
- 第12章では CQRS の例を、イベントを使う方式と使わない方式の両方で扱います
- 第13章では明示的・暗黙的な依存関係を整理し、簡単な依存性注入フレームワークを実装します
- エピローグでは、新しく始める単純な例ではなく、既存ソフトウェアにこうした原則を適用する方法と追加の読み物を提供します
サンプルコードと実際に試す方法
- 本書は1つのサンプルプロジェクトを中心に構成され、章が進むにつれてプロジェクトを段階的に拡張します
- 読者が実際にコードに触れ、パターンがどのように機能するのか感覚を得ることを重視しています
- すべてのコードは GitHub にあり、各章ごとに別ブランチがあります
- 進め方として3つの方法が提案されています
- 自分でリポジトリを作り、本書の例に沿ってアプリを構築し、必要に応じて著者たちのリポジトリを参照する
- 各パターンを章ごとに自分の小さなプロジェクトへ適用してみる
- 各章の「Exercise for the Reader」と、一部が空欄になっている GitHub のコードを使って素早く実習する
- 特に自分のプロジェクトにパターンを適用しようとする読者にとっては、単純な例を先に扱うことが安全な練習になります
- 少なくとも各章を読む際には、リポジトリから
git checkout でコードを取得し、実際に動くアプリの文脈でコードを確認することを勧めています
ライセンスと形式
- コードとオンライン版は Creative Commons CC BY-NC-ND ライセンスです
- 印刷版は別ライセンスに従い、再利用に関する懸念がある場合は O’Reilly に問い合わせるよう案内しています
- 本書の表記規則では、イタリック、固定幅、固定幅太字、固定幅イタリックを区別して使います
- イタリックは新しい用語、URL、メールアドレス、ファイル名、拡張子を表します
- 固定幅はプログラムリストとコード要素を表します
- 固定幅太字は、ユーザーがそのまま入力すべきコマンドやテキストを表します
- 固定幅イタリックは、ユーザーが提供する値、または文脈に応じて決まる値で置き換えられるテキストを表します
2件のコメント
日本語版が出ています。Pythonで見るアーキテクチャパターン
Hacker Newsの意見
この本は アーキテクチャパターン を学ぶのに最適な宝庫で、話にすぐ入り込めて要点もつかみやすい
ただし実務の観点では、こうしたパターンの一部は Python で複雑さや性能問題を大きく悪化させることがあり、特にすでに ActiveRecord パターン を使っている Django のようなフレームワークと組み合わせると、なおさらそうだ
Python を使う大小さまざまな会社を見てきたが、厳格なアーキテクチャパターンを使っていた大企業はコードこそ「きれい」だったものの、最初は単純に見える作業まで過度に複雑で、不必要に遅かった
逆に、そうしたパターンを気にしていなかった大企業には、巨大な if-else ファイルや、ビジネスロジックが全部入った Django モデルのような本当にひどいコードもあったが、読んで理解し、1000行の if-else を修正できるので、むしろ生産的だった
きれいなコードではない会社のほうが生産的だったと認めるのは気が進まないが、何がきれいかを巡って延々と続く議論もかなり避けられたわけだ
誰かが「X や Y をやるには、まず N 個の別のものを作らなければならない」と決めてしまい、簡単な作業でも複数のファイル・関数・クラスを修正しなければならない
アーキテクチャパターンを気にしない会社では見た目の悪いコードが多いかもしれないが、間接参照が少なく、作業対象が一つの領域に集まっているので、新しく入った人には理解しやすいことが多い
複数ファイルに散らばった小さな関数群よりも、簡単に移動しながら修正・デバッグできる 100行の関数 のほうが良いことも多い
抽象化がすべて悪いという意味ではないが、長く仕事をするほど、抽象化が少ないほうがコードの個々の単位や機能を推論しやすいと感じるようになり、本当に難しいものだけを抽象化し、単純なものまで抽象化しないほうに結論が傾いた
両方あればコードベースの探索は直感的だが、どちらか一方でも欠けていると、1000行の関数のほうが読みやすく見えることもある
宇宙船は高度に設計された製品であり、かなりの部分がソフトウェア制御装置として動いているので、すでに発見してコピーできる 整った設計モデル が含まれている
ドメインモデリングは既存モデルをコピーすることではなく、新しいソフトウェアが置き換えようとしている物理的・社会的技術よりも、ソフトウェアの利点を活かしてより良くすることであるべきだ
既存のドメインモデルにも優れた抽象化があり、新しいモデルに入れるべき中核要素は多いし、利害関係者が現在のシステムで何を達成しようとしているのかを理解することも重要だ
しかし、ビジネスや文化の世界のモデルはコード向けに最適化されておらず、古かったり雑然としていたりすることが多く、境界ケースやあまり明示されていない部分は人間が解釈して処理している
そうしたモデルは最終目標ではなく、インスピレーションの源 として扱うべきだ
こうしたアーキテクチャパターンはプロジェクト管理に似ていて、どちらも オーバーヘッド を生み、無分別に使えば得られる価値よりコストのほうが圧倒的に大きくなって悪評を招く
だが慎重に使えば、プロジェクト成功の決定打になりうる
たとえば単純なカレンダー REST API を作るなら、複雑なアーキテクチャは使わないだろう
一方、取引システムでは ポートとアダプターアーキテクチャ を守ることが、シミュレーションモードと運用モードをシームレスに切り替えるうえで非常に重要だった
シミュレーターと実際の取引エンジン、過去イベントフィードとリアルタイムフィードを簡単に差し替える必要があり、ビジネスロジックを二重に作って同期するわけにはいかないからだ
パターンは実装ではなく、何をしているのかについての思考プロセス、それを扱うための計画、残りのコードに提供しようとしている API の最上位設計に近い
Python では関数がオブジェクトなので、状態機械も互いを返す関数の束と
f = f(x)の反復だけで十分なことが多いSingleton の代わりに Borg パターンを使おうと言われることもあるが、実際には単にモジュールを使えばよい場合が多く、
sysがすでにシングルトンのようにしてくれている「依存性注入」も、たいていは関数に引数を渡し、ときには別の関数を渡すことを格好よく言い換えただけだ
Flyweight は別個の代物ではなくインターニング技法であり、原文で言う Command パターンは Jack Diederich の有名な批判講演(https://www.youtube.com/watch?v=o9pEzgHorH0)の要点の一つでもあって、
functools.partialをうまく使えばよいきれいなコードと醜いコードという二分法は、より良い例をまだ見ていないことから生じた錯覚かもしれない
短い関数は複雑なクラス階層を必要としないし、そもそもクラス自体が不要なこともある
オブジェクト指向プログラミングはクラスではなくオブジェクトに関するものであり、もしクラスが中心なら、クラス指向プログラミングと呼ばれていたはずだ
この本で、Python や特定の言語を超えた概念、たとえば イベント駆動アーキテクチャ、コマンド、CQRS のような部分は非常に有用だと思う
ただしほかの部分には問題もあり、経験の浅い開発者がそれを教義のように受け取り、すべてを一度に実装しようとすると危険になりうる
リポジトリパターンは一般的には役に立つが、この本の例も含め、多くの場合では得られるものがほとんどない一方で複雑さだけを増やす過剰な選択になる
特に SQLAlchemy を使っているなら、SQLAlchemy 自体がある程度リポジトリ、より正確には ORM を載せたリレーショナルデータベース抽象化レイヤーの役割を果たしている
サービスレイヤーと Unit of Work も、複雑なユースケースが多いアプリケーションには有用だが、責務の範囲が狭い小さなサービス群で構成されたシステムではすぐに肥大化する
Python における依存性注入も同様だ
設計パターンは結局ツールなのだから、開発者はいつ使うべきか、そしてもっと重要なのはいつ使うべきでないかを理解しなければならない
この本にもそうした助言はあるが、各章末よりも前のほうで、もっと目立つ形で扱ってほしかった
データベースアクセスが必要なら、関心の分離のために常に使うことを勧められるほど 軽量なパターンだと見ている
要するに、特定のエンティティに対するすべてのデータベースアクセスを、そのエンティティのリポジトリ一か所に通すようにするだけで、リポジトリ内部では直接クエリを実行しても ORM を使っても好きなようにできる
記事のリポジトリパターンの節にある内容のかなりの部分は、パターン自体というより Python、Django、SQLAlchemy の細部に近い
主に Python を使っていて、依存性注入を「関数が計算に必要なすべての入力を引数として受け取るようにすること」だと理解しているが、プロジェクト設計の中心原則にする価値があると感じている
サービスレイヤーと 依存性注入は、関数型プログラムを書くときにかなり役立った
Python で作った複雑な画像処理スクリプトを、Celery ベースの分散画像処理サービスのプラグインのように使っているが、サービスレイヤーと依存性注入によってコードを
dependency.do_thing(params)からdo_thing(dependency, params)に変えられるこうするとずっとテストしやすくなる
実際のデプロイ環境で I/O をすべてフェイクに置き換えた画像処理ジョブを動かすこともできるし、フェイクの Celery 上で実際の画像処理ジョブを走らせることもできる
本番デプロイ前に複数の関数をエンドツーエンドでテストできるし、サービスレイヤーで Result 型を併用すれば、プログラムをクラッシュさせずに関連するエラー情報を Web クライアントへ渡すのも簡単だった
パターンを見るのは興味深いが、序盤にどんな状況で実装すると最も有用なのかという ガイドがあればもっとよかった
パターンがツールなら、いつ使い、いつ避けるべきかを案内してくれるほうが助かる
長所と短所の節である程度は扱っているので、順序と強調の問題かもしれない
新しいビジネス向けの小さな Web アプリを作りながら Python を学んでみると、あるパターンは単純化のためのアイデアを与えてくれ、別のパターンは避けるべきだと判断する助けになった
「Clean Code」のようなもので経験の浅い開発者を洗脳し、とてつもない害を生み出したと考えている
ソフトウェア工学は厳密な自然科学ではないので、銀の弾丸はほとんどなく、すべてが トレードオフだ
ただ一つの真の道を知っていると言う人は危険か、初心者に近い
TypeScript 開発者だが、この本は一番好きな アーキテクチャ本の一つで、よく参照している
特にテスト用のフェイク Unit of Work とサービスパターンが好きで、すべてのプロジェクトでサードパーティサービスをフェイクに置き換えるときに信奉者のように使っている
ここで重要なのは、モックではなく フェイクオブジェクトだという点だ
命名で悩むときにも役立った。たとえばイベント名はインフラやパターン中心ではなく、ドメインに特化したものにするべきだと勧めている
USER_NOTIFICATIONよりCART_ITEM_BECAME_UNAVAILABLEのほうがよい、というような話だこうした内容の一部は当たり前ではあるが、チームメンバーに説明するのは面倒で、cosmic python がオンラインで公開されているのでリンクを送るのが簡単だ
全体として素晴らしく、私に大きな影響を与えた資料だった
https://www.obeythetestinggoat.com/pages/book.html
あの本も同じように愛着のある本で、実務では Python をほとんど使っていなかったのに、ほかの言語を使うときにもよく読み返してしまう
オンライン版と紙の本の両方があるのもよい
この本もぜひ読んでみようと思う
Python は優れた グルー言語だと思う
何もかもカプセル化と継承に押し込み、private フィールドしか持たせず、メソッド経由で設定させるような強制的なオブジェクト指向の考え方にはうんざりしている
SOLID、clean coding、clean architecture、GoF パターン、Uncle Bob にも疲れた
名詞の王国や FizzBuzz Enterprise Edition にも疲れた
今ではオブジェクト指向をできるだけ減らし、命令的または関数型の流れに従っている
Python をたまに使うのは、主に会社で .NET を使っているからであって嫌いだからではないが、そのときはオブジェクトやパターンから自由な体験を求めている
これはこの本に価値がないという意味ではなく、パターンを学ぶには有用だ
ただ、現実のあらゆるプログラミングをパターン・オブジェクト・SOLID に無理やり当てはめるべきではない
こうすると無関係な複雑さをドメインの外に留めておけるし、常に必要とは限らない完全なオブジェクトを作らなくてもそれらの関数を再利用できる
ここでやろうとしていることがおおむねこういうものだとしたら、かなりやりすぎに見える
SQLAlchemyのモデルを再実装して「リポジトリ」と呼び、SQLAlchemyのセッションを再実装して「ユニット・オブ・ワーク」と呼び、しかもモデルも使わない「サービス層」まで追加している
結合度を下げるという理由でモデルの属性をすべて個別の関数引数にばらし、デバッグのしやすさをなくすかのように、あらゆるものをメッセージバスに撒き散らしている
しかもこれは書き込み経路にしか当てはまらない
読み取りには別の非正規化テーブルを置き、生のSQLで問い合わせている
実際、第12章を見るとそうなっている
MADE.comのトラフィックを見ると、先月はデスクトップとモバイルを合わせて50万訪問程度で、1分あたり約12ビューの水準だ
そんな状況で一日中DDDを語っていられる職場がうらやましい、という皮肉な気分になる
各パターンごとに長所と短所の要約があり、不足している点はあっても、そのパターンがアプリケーションの要求に合っているかを考えるべきだとは明確に述べている
本の内容すべてが必要なアプリケーションは多くないだろうが、1つ以上のパターンを適用できるアプリケーションは間違いなく多い
ただ、捨てアカウントだとしたら、価値のない通りすがりの否定的反応に近い
数年前からPythonを仕事で使い始めた
KotlinとTypeScriptから移ってきて、言語そのものは取っつきやすかったが、慣れ親しんでいた疎結合やテストのしやすさを実現しつつ、Pythonらしいやり方にするのには苦労した
同僚の勧めでこの本を買って最初から最後まで読み、少し大きめのPythonコードベースで複雑さの管理をどう行うかを理解するのに大いに役立った
本が勧めるすべてのパターンに従っているわけではないが、何が可能か、そして他のパラダイムで積んだ経験をPythonに適用しつつも「Java開発者がPythonをやっている」感じにならないようにする方法を示してくれた
強く勧めたいし、払ったお金以上の価値があった
本当に素晴らしいPythonプログラミング本の1冊だ
惜しかった点が1つあるとすれば、コードに静的型付けが少ないことだが、これは著者たちが意図的にそう決めたことだ
コードに型ヒントを付けにくい度合いは、あとでそのコードを理解するのがどれほど難しいかを示す、とても良い尺度だ
ここでは型ヒントはある: https://www.cosmicpython.com/book/chapter_08_events_and_mess...
本の一部には型ヒントがない箇所もある
dataclassesを使っており、これは型注釈を必須にするPythonは静的型付けをサポートしていない
型注釈ベースのツールはコンパイル過程に影響を与えず、
dataclassesのようにメタプログラミングを使う場合でない限り、Pythonにコードを拒否させることもできない単に診断を提供するだけだ
この本は本棚に置いてある
K&Rのような小さな本で、よくめくっているせいでかなり擦り切れた跡が見える
とても良い読み物であり、要約でもあった
約3年前にC#/.NETベースのDDD環境で働いていたが、いまPythonでこれらの概念を見直すと、要点がよりうまく絞り込まれているように見える
こういう種類の内容に興味があるなら強く勧めたい
2年半から3年ほど前にこの本の紙版を読み、かなり楽しめた
テストを第一級のテーマとして保ち、機能を追加するたびに着実に更新していく点がうまかった
古いアーキテクチャ本の中には、テストをそこまで優先度高く扱っていないものもある
テストを準備しやすく、書きやすく、更新しやすくしておけば、コードを手で動かして問題を確認することが減り、開発プロセスがより楽しくなる
より速いフィードバックループが得られるということだ
この本のイベント指向の部分は非常に興味深かったが、今の仕事で実際に実装するにはあまり実用的には見えなかった