石橋秀仁(zerobase)書き散らす

まじめなブログは別にあります→ja.ishibashihideto.net

分散オブジェクト・アーキテクチャ/ドメイン指向アーキテクチャ

これから述べる分散オブジェクト・アーキテクチャに近いフレームワークやアプリケーションの実装がすでにあれば教えて頂けると助かります。

背景:ゼロベースの管理会計システムを開発するよ

(凡例:追記削除

目的

ドメイン駆動設計(DDD)を原理的に突き詰めたアーキテクチャによって、オブジェクト指向プログラミングの作業から、インフラストラクチャ層に関する配慮がほとんど不要になる。SQLもデータスキーマ設計も不要になる。

といっても、Ruby on RailsActiveRecord的な話ではないので、誤解なきよう。むしろ正反対。リレーショナル・データベースを使わない。

ドメインのオブジェクトをシンプルに書けばシステムが実現するようになる。一言で言えば、「GOOS/DDD原理主義者のためのフレームワークが欲しい」という話。

ウェブ・アプリケーションの三層アーキテクチャ再考

ウェブ・アプリケーションについて、

を対応付けると:

インフラストラクチャ 三層アーキテクチャ DDD四層
ブラウザ(ユーザー・エージェント) プレゼンテーション層 ユーザー・インターフェイス層
ウェブサーバー プレゼンテーション層/アプリケーション層 ユーザー・インターフェイス層/アプリケーション層/ドメイン層
データベース・サーバー データ層 インフラストラクチャ層

例外もあるだろうが、私の知る範囲では、これが多い。

このアーキテクチャでシステムをスケール・アウトするには、ウェブサーバーを増やす。基本的な設計では、データベース・サーバーを増やせず、ボトルネックになる。特別な技術を用いれば、データベース・サーバーをスケール・アウトできるが、データベース設計を変える必要がある。(データベース設計を変えずにスケールアウトする手法があれば教えて頂けると助かります)

さて、違うアーキテクチャも考えられる。

RPCで連携する分散オブジェクト・アーキテクチャだ:

インフラストラクチャ DDD四層
ブラウザ(ユーザー・エージェント) ユーザー・インターフェイス層
ウェブサーバー ユーザー・インターフェイス層/インフラストラクチャ層
オブジェクト・サーバー アプリケーション層/ドメイン層/インフラストラクチャ層

相違点

三層アーキテクチャでは多数のアプリケーション・サーバーが、(一つないし少数の)データベース・サーバーとTCPインターフェイスで疎結合されている。

データとアプリケーション・ロジックが、ネットワーク上に分散配置されている。

それに対して、分散オブジェクト・アーキテクチャは大きく異なる。

分散オブジェクト・アーキテクチャでは、データとアプリケーション・ロジックが物理的にカップリングされる。

データと処理が、オブジェクトとしてオブジェクト・サーバー上でカプセル化されるのだ。

オブジェクト指向のコンセプトを、そのまま物理的配置に対応させるアーキテクチャだと言える。

この観点からは、三層アーキテクチャでは、データと処理が物理的にカプセル化されていないことになる。

分散オブジェクト・アーキテクチャの動作原理

ウェブサーバーは、HTTPリクエストを適切なオブジェクトへ転送する。つまり、ルーティング処理を担当する。もちろん静的コンテンツは自らレスポンスする。それもルーティングの一種だから、ウェブサーバーはルーティングに特化する

また、ユーザー・インターフェイス層の担い手として、HTMLやJSONを生成することも、ウェブ・サーバーの仕事だ。

ウェブサーバーがメッセージを送る先は、オブジェクト・サーバーだ。

ウェブサーバーからのメッセージを受け取ったオブジェクトがアプリケーション・ロジックを処理する。

スケールアウトするには、ウェブサーバーとオブジェクト・サーバーをそれぞれ増やす。ウェブサーバーとオブジェクト・サーバーは、一対一で対応しないので、それぞれ任意に拡張できる。

オブジェクト間の通信はTCPベースのRPCで行われる。HTTPベースのWeb APIかもしれない。

オブジェクトの物理的配置

どのオブジェクトが、どのオブジェクト・サーバーに配置されるか。これはパフォーマンスを大きく左右する。もちろん、サーバー間RPCを少なくする配置ほど、高いパフォーマンスになる。

その配置を人間が設定するかもしれないし、自動化されたログ分析と配置ポリシーによって動的に再配置されるかもしれない。

十分にインテリジェントなオブジェクト配置アルゴリズムは、「同じクラスのオブジェクトを集める」(RDBのテーブル的な配置)のではなく、「よく会話するオブジェクトを集める」だろう。オブジェクト間コミュニケーション指向の配置アルゴリズムだ。

分散オブジェクト・アーキテクチャのプログラミング・パラダイム

三層アーキテクチャなら、あるクラス(テーブル)のオブジェクト(レコード)全部に対する一括集計が用意にできる。リレーショナル・データベースでSQLを実行するだけだ。

例えば、

SELECT TOTAL(balance) FROM customers WHERE balance >= 1000000

とすれば、口座残高100万円以上の顧客の口座残高を合計することができる。

しかし、ここではオブジェクト指向パラダイムSQLという異物が混じっている。

分散オブジェクト・アーキテクチャでは、次のようなコードになるだろう。

Balance rangeMinimum = new Balance(1000000);
Balace total = Customer.filterCustomers(function (customer){
  customer.balance() >= rangeMinimum
}).map(function (customer){
  customer.balance()
}).sum();

不変の値オブジェクト(Value Object)を用いて、副作用のない集計処理を書いている。このようなコードは、言語やフレームワークの機能によってスレッドセーフに並列処理できる。

オブジェクトが複数のオブジェクト・サーバーに物理的に分散しており、実際の処理にはRPCが必要だという事情も、フレームワークによって完璧に隠蔽される。コードには一切現れない。並列処理したければ、副作用のないコードを書けばいいだけだ。

むしろ、次のように言うことができる。

関数型オブジェクト指向パラダイムでコーディングしていれば、並列分散処理可能なコードになる。スケールアウトはインフラストラクチャ層に丸投げできる。そして、それは正しい。スケールアウトはインフラストラクチャ層の関心事でしかないからだ。

そのためにアプリケーション層やドメイン層のプログラミングで工夫すべき点は、それほど多くない。基本的には素直に書けばいい。

最大のポイントは、副作用のないコードを書くことだ。それは関数型言語でなくても可能だが、関数型言語こそ最適だろう。

このように、オブジェクト指向プログラミングのパラダイムからSQLを排し、関数型言語パラダイムを持ち込むことには、多大な利点がある。

SQLを排しているのだから、リレーショナル・データベースのスキーマを設計する必要もない。

オブジェクトの永続化については、透過的永続化フレームワークとキャッシュ・オン・メモリ(memcachedなど)を導入すればよいだろう。アプリケーション層とドメイン層に影響を与えずに、インフラストラクチャ層だけで、しかもなるべく自動的に、永続化という目的を達成するのだ。

RDB/SQL的な集合演算のパフォーマンス改善

もし集計処理を高速化したければ、リレーショナル・データベースのINDEXに相当する機能を、分散オブジェクト・ブローカーに実装すればいいはずだ。インフラストラクチャ層の設定変更によって、アプリケーションが高速化するだろう。また、遅延評価も有効だろう。

RESTとSOA

このような分散オブジェクト・アーキテクチャは、ラディカルなサービス指向アーキテクチャ(SOA)と言えるだろう。

"Tell, Don't Ask"(命じよ、尋ねるな)という原則に照らすと、RESTのようなリソース指向アーキテクチャ(ROA)では、「尋ねる」ことがシステムの動作原理になっている。

プリミティブなデータ型を暴露するアーキテクチャは、オブジェクト指向の対極にある。

つまり、全レイヤーで一貫してオブジェクト指向であろうとするドメイン指向アーキテクチャと、RESTは、相性が悪い。

ドメイン駆動設計を原理的に突き詰めた結果がサービス指向アーキテクチャ(SOA)になるのは、偶然ではない。

ちなみに、RESTが役立つ場合もある。オブジェクトをリソースとみなして、GETアクションに限定したREST APIを、ユーザー・インターフェイス層に対して提供する場合だ。そのようなAPIは自動生成してもよいだろう。

REST APIでオブジェクトの内部データ構造を暴露すると、ユーザー・インターフェイス層がそれに強く依存してしまうので望ましくない

先行事例

課題

関連情報

追記:執筆後に知った関連情報

「分散オブジェクト・サーバー」は「オブジェクト指向Hadoop」のようなものかもしれない。だとするとHBase、ZooKeeper、Finagleあたりが参考になりそうだ。

フォローアップ