ソフトウェアアーキテクチャのパターンはなぜ進化し続けるのか

 ・ 11

photo by Tom Barrett(https://unsplash.com/@wistomsin?utm_source=templater_proxy&utm_medium=referral) on Unsplash

開発の勉強をしていると、新しい単語が次から次へと出てきますよね。Monolith、Microservices、Hexagonal、DDD、CQRS、Event Sourcing、Serverless、Service Mesh… ある人は「今はもう全部マイクロサービスだ」と言い、別の人は「Amazonはモノリスに戻したらしい」と言います。

結論から言うと、パターンに正解はありません。 どのパターンもその時代の特定の問題への答えであり、新しいパターンは前のパターンの限界の上に生まれるからです。だから見るべきなのは「最新かどうか」ではなく「なぜ生まれたか」です。

パターンを進化させる4つの圧力#

新しいパターンは「かっこいいから」生まれるわけではありません。いつも4つの圧力が同時に働いています。

ハードウェアとインフラの変化#

サーバーが高価だった時代は、1台のマシンにすべてを詰め込むしかありませんでした。クラウドの登場でサーバーは使い捨てになり、コンテナとKubernetesが当たり前になってデプロイのコストはほぼゼロに近づきました。

できるようになったことは、やがてやるべきことに変わります。 インフラが可能にしたことは結局誰かが試し、それがパターンになるわけです。

組織規模の変化(Conway's Law)#

1968年、Melvin Conwayはこう書きました。

「システムの構造は、それを作った組織のコミュニケーション構造に似る」

5人のチームが作るシステムと、500人の会社が作るシステムが同じになることは絶対にありません。5人なら1つのコードベースで衝突せずに働けますが、500人では不可能です。Microservicesは技術的な決定である前に、組織的な決定なのです。

前のパターンの弱点が露わになる#

新しいパターンは常に、前のパターンが一定の規模を超えたときに破綻する地点で生まれます。

Monolith
  → (デプロイが怖くなる)
    → Microservices
      → (サービス間通信の地獄)
        → Service Mesh, Event-Driven
          → (分散トランザクションの問題)
            → Saga, Eventual Consistency

すべての処方は新しい副作用を生み、その副作用が次のパターンの出発点になります。

ビジネス要件の変化#

以前は「明日までに処理されればよかった」ことが、今では「1秒以内に通知が届かなければならない」ことになりました。国内だけで売っていたサービスがグローバルになったりもします。ビジネスの速度と範囲が変われば、システムもそれに追随します。

時代ごとに辿るアーキテクチャの歴史#

それぞれの時代がどんな問題に直面し、どう解いたのかを見ると流れがつかめます。

1970〜80年代:手続き型の時代#

メモリはKB単位、CPUはMHz、プログラムは一人で全部書く時代です。問題は「どうすればリソースを節約しながら動くプログラムを作れるか」であり、答えは手続き型プログラミングと構造化プログラミングでした。

この時代のコードは本質的に関数の羅列でした。C言語が代表的です。「GOTOを使わず関数に分けよう」というのが当時の大きな進歩でした(Dijkstraの有名な "GOTO Considered Harmful", 1968)。

1990年代:オブジェクト指向とMVCの時代#

PCが普及しGUIが登場して、プログラムはどんどん大きくなります。コードが1万行を超えると関数だけでは管理できませんし、UIまで付くとさらに複雑になります。

答えはオブジェクト指向プログラミング(OOP)とMVC(Model-View-Controller)でした。OOPはデータとそれを扱う振る舞いをまとめて「オブジェクト」として捉える発想です。Smalltalkが始まりでしたが、大衆化させたのはJava(1995)でした。MVCはTrygve Reenskaugが1979年に提案したものですが、本格的に使われたのはGUIとWebが普及した90〜2000年代です。

2000年代前半:エンタープライズとLayered Architecture#

ドットコムバブル以降、銀行や保険会社、大企業が本格的にWebベースのシステムを構築し始めます。ビジネスロジックは巨大になり、数十人の開発者が同じシステムに手を入れます。

答えはLayered Architecture(N-tier)でした。Controller → Service → Repository → DB。この構造は今でも圧倒的に多く使われていて、Java EEとSpringが標準になりました。

この時代のもう一つの大きな出来事がDDD(Domain-Driven Design)の登場です。Eric Evansが2003年に本を出版して確立したもので、核心は「ビジネスドメインをコード構造の中心に置こう」ということ。AggregateやBounded Contextといった概念はここから出てきました。

DDDはLayered/Clean Architectureと組み合わせて使います。Layeredが「器の形」だとすれば、DDDは「器の中に入るオブジェクトをどう作るか」への答えなんですね。

2000年代中後半:SOAという実験#

会社の中にシステムが増えすぎます。決済、会員、商品のシステムが別々に動いているのに、互いにデータをやり取りしなければならない。そこで出てきた答えがSOA(Service-Oriented Architecture)でした。

SOAはMicroservicesの祖先です。ただ当時はESB(Enterprise Service Bus)という重いミドルウェアを介して通信していました。これがあまりに重くて複雑だったため、SOAは結局「理論は良かったが失敗した実験」と評価されるようになります。それでもその精神は生き残り、2010年代に別の形で復活します。

2010年代前半:クラウドとMicroservicesの時代#

AWSが本格的に定着し(EC2は2006年リリースですが普及は2010年代)、コンテナが登場し(Docker, 2013)、スマートフォンの爆発でトラフィックが数千倍に増えます。

Monolithではもう立ち行かなくなります。1行直すだけで全体をデプロイしなければならず、一部が落ちれば全体が落ちる。チームは100人、1000人へと大きくなるのに、1つのコードベースを一緒に触るのは地獄です。

Netflixがこの流れの代表格です。2008年にデータセンター障害で数日間サービスが止まった事件をきっかけに、モノリスを捨ててAWS上でマイクロサービスへ移行しました。その過程で作られたツール群(Eureka、Hystrix、Zuul)はOSSとして公開され、業界標準になりました。Amazonはそれより早く、2002年のJeff Bezosによる有名な「API Mandate」で社内システムをすべてAPI経由の通信にしています。これが結果的にAWSの誕生につながりました。

この時代の副産物として登場したパターンも一緒に見ておきましょう。

  • API Gateway — クライアントが数十のマイクロサービスをすべて知ることはできないので、入口を1つ作ろう
  • BFF (Backend For Frontend) — モバイルとWebでは必要なデータが違うので、クライアントごとにゲートウェイを分けよう。NetflixとSoundCloudが先導しました
  • Circuit Breaker — あるサービスが落ちたとき、他のサービスが延々と待ち続けないよう遮断しよう。NetflixのHystrixが代表例です

2010年代中盤:非同期とEvent-Driven#

マイクロサービスが増えるにつれ、同期通信の限界が見えてきます。A → B → C → Dと順番に呼び出すと、Dが遅いときに全体が遅くなるからです。そこで出てきた答えがEvent-Driven Architecture、そしてKafkaでした。

LinkedInで始まったKafka(2011)はこの時代の最も重要なインフラです。「イベントをログとして保存し、誰でも購読できるようにする」というシンプルな発想が、分散システムの通信のあり方を変えました。

この流れの中で一緒に浮上したパターンです。

  • CQRS (Command Query Responsibility Segregation) — 読み取りと書き込みを別のモデルに分離。読み取りトラフィックが圧倒的に多いシステム(例:Twitterのタイムライン)で有効です
  • Event Sourcing — 状態ではなくイベントの流れを保存。すべての変更が追跡できるので、監査が重要なドメイン(銀行、保険)に向いています
  • Saga Pattern — 分散トランザクションを補償トランザクションで処理。注文が失敗したら決済のキャンセルを自動でトリガーする、といった形です

2010年代後半:Container、Kubernetes、Service Mesh#

マイクロサービスが100個、1000個と増えて運用の複雑さが爆発します。答えはKubernetes(2014、GoogleがOSSとして公開)とService Mesh(Istio 2017、Linkerdなど)でした。

Kubernetesは「コンテナオーケストレーション」の標準になりました。複数のマシンに散らばったコンテナを自動で配置し、復旧し、拡張します。

Service Meshはさらに一歩進んで、サービス間の通信そのものをインフラレベルで処理します。リトライ、認証、トラフィック分散といった処理をアプリケーションコードから外し、サイドカープロキシ(Envoy)に移すわけです。

2010年代後半〜2020年代:Serverless#

AWS Lambda(2014)の登場で「サーバーを管理する必要がない」という発想が生まれます。トラフィックが不安定なワークロードや、月に数回しか回らないバッチ処理のために24時間サーバーを立てておくのは無駄でした。

Serverless/FaaS(Function as a Service)は関数単位でデプロイし、呼び出されたときだけコストが発生します。画像がアップロードされたらサムネイルを作る、決済が入ったら通知を送る、といったイベント駆動のワークロードに特によく合います。AWS Lambda、Vercel、Cloudflare Workersが代表的です。

2020年代:回帰と再評価#

ここからが面白いところです。流行が一周しています。

  • Modular Monolithの復活 — 「マイクロサービスに行く前に、モノリスをきちんとモジュール化しよう」という流れ。Shopifyが代表的です
  • Amazon Prime Videoの回帰(2023) — マイクロサービスで作った動画モニタリングシステムを再びモノリスにまとめ、コストを90%削減したという記事が話題になりました
  • DHH(Basecamp/Hey創業者)の「Majestic Monolith」 — 小さなチームにはモノリスが答えだという主張です

核心はこうです。マイクロサービスが間違っていたのではなく、あらゆる場所にマイクロサービスを適用したことが間違っていたのです。

1枚に圧縮した流れ#

[1970s] 手続き型
   ↓ (プログラムが大きくなる)
[1990s] OOP, MVC
   ↓ (エンタープライズシステムが巨大化する)
[2000s] Layered, DDD, SOA
   ↓ (クラウドとモバイルが爆発する)
[2010s] Microservices, Event-Driven, Kubernetes
   ↓ (運用の複雑さが爆発する)
[2020s] Modular Monolith, Serverless, 「必要な分だけ」

矢印を見れば分かります。新しいパターンは前のパターンが不足していたからではなく、環境が変わったから登場します。 そして環境がまた変われば、古いパターンが新しい服を着て戻ってきます。

会社ごとに選択が違う理由#

同じ時代でも会社によって使うパターンは違います。それぞれの問題が違うからです。

会社 主なパターン 理由
Netflix Microservices, BFF, Circuit Breaker グローバル配信、障害回復力が命
Amazon Microservices(API Mandate), Event-Driven 巨大な組織、独立デプロイが必須
Uber Event-Driven, DDD リアルタイムマッチング、複雑なドメイン
LinkedIn Kafkaベースのイベントストリーミング 大規模データパイプライン
Shopify Modular Monolith 高速な開発、明確なドメイン分離
Basecamp/Hey Majestic Monolith 小さなチーム、速い意思決定
Stack Overflow Monolith(今なお!) シンプルなドメイン、コスト効率
Discord Elixirベース、一部Rustのマイクロサービス 並行性、性能のホットスポット

特にStack Overflowが印象的です。世界トップ50のサイトなのに、今もモノリスなんですよね。マイクロサービスを知らないからではなく、自分たちの問題にはモノリスの方が適していると判断したからです。

パターンとの向き合い方#

「最新」より「なぜ」を見る#

新しいパターンが出たからといって無条件についていってはいけません。そのパターンがどんな問題を解こうとして作られたのかを、まず問うべきです。自分のシステムにその問題がなければ、そのパターンはオーバーエンジニアリングです。

パターンはトレードオフの集まり#

どんなパターンも、何かを得て何かを失います。

  • Microservices — 独立デプロイ ↔ 分散システムの複雑さ
  • Event-Driven — 疎結合 ↔ デバッグの難しさ、一貫性の問題
  • CQRS — 読み取り性能 ↔ コードの複雑さ、一貫性の遅延
  • Serverless — 運用負担の軽減 ↔ コールドスタート、ベンダーロックイン

タダ飯はありません。 何かを得るために何を諦めるのかが、いつだって核心です。

組織とビジネスを見る#

技術的なパターンの選択は、常に組織とビジネスの関数です。次の問いに答えないままパターンを選べば、必ず後悔します。

  • チームは5人か、500人か?
  • トラフィックは安定しているか、不安定か?
  • ドメインは単純なCRUDか、複雑なビジネスルールか?
  • コスト感度が高いか、性能が優先か?

段階的に進む#

グローバル企業はみなモノリスから始めました。AmazonもNetflixもUberもです。彼らは問題が起きてからマイクロサービスへ移りました。最初からマイクロサービスで始めたスタートアップの80%は、インフラの複雑さに足を取られます。

Start with a monolith. Extract services when the pain is real.

では今、何を勉強すればいいのか#

バックエンド開発者が学習の順番を決めるなら、こんな流れをおすすめします。

  1. Layered Architectureを本気で書いてみる — Spring BootでもDjangoでもExpressでも構いません
  2. そのコードをHexagonalにリファクタリングしてみる — 依存性逆転が体で腑に落ちるまで
  3. 簡単なDDDの概念を適用してみる — Aggregate、Value Object程度で十分です
  4. イベントベースの通信を一度使ってみる — KafkaでもRabbitMQでも
  5. その次にMicroservices、CQRS、Event Sourcingを見る — その頃にはきっと、なぜ必要なのかが見えてきます

残りはそのパターンが必要になった瞬間に勉強すれば大丈夫です。先に全部学ぼうとすると、深みのない知識ばかりが積み上がってしまいます。

おわりに#

アーキテクチャパターンの歴史は、結局「私たちはますます大きなシステムを、ますます大きな組織で、ますます速く作らなければならない」という圧力の歴史です。パターンはその圧力への答えであり、圧力が変われば答えも変わります。

だから「これが正解だ」と言う人は警戒した方がいいですね。正解はいつだって「何に対する答えなのか?」という問いの後ろにしか存在しないからです。

次に新しいパターンの名前を聞いたときは、こう問いかけてみてください。

「このパターンはどんな問題を解こうとして生まれたのか? 自分にその問題はあるのか?」

その問いが、技術トレンドの波の中で道に迷わないようにしてくれます。

もっと読むなら#

  • Domain-Driven Design — Eric Evans (2003)
  • Building Microservices — Sam Newman
  • Designing Data-Intensive Applications — Martin Kleppmann
  • Martin Fowlerのブログ — パターンの定義とトレードオフ
  • Software Architecture: The Hard Parts — Neal Ford et al.
  • Amazon Prime Videoのモノリス回帰の記事(2023)
  • DHHの「The Majestic Monolith」ブログ

It isn't what happens to us that causes us to suffer; it's what we say to ourselves about what happens.

— Pema Chodron


他の投稿
意味は探すものではなく、生きているうちについてくるもの 커버 이미지
 ・ 3

意味は探すものではなく、生きているうちについてくるもの

家はあるのに、行く場所がないということ 커버 이미지
 ・ 5

家はあるのに、行く場所がないということ

CIが全部BuildFailedで落ちた原因は、コードではなく請求ロックでした 커버 이미지
 ・ 6

CIが全部BuildFailedで落ちた原因は、コードではなく請求ロックでした