可能態(デュナミス)としてのハイパーメディア
これはBEAR.Sunday Advent Calendar 2024の4回目、11日目の記事です。
はじめに
これまでの記事で、私たちはアリストテレスの思想を導きの糸として、リソース設計の本質に迫ってきました。1日目には形相(エイドス)と質料(ヒュレー)の二元論を通じてRESTの本質を探り、2日目には四原因説によってリソースの存在理由を問い、3日目には中庸(メソテース)の観点から適切な粒度について考察しました。
特に前回の中庸(メソテース)の議論では、リソースの適切な粒度を見出すことの重要性と、その判断には実践的知恵(フロネーシス)が必要であることを学びました。リソースが小さすぎれば管理が煩雑になり、大きすぎれば一貫性の維持が困難になる。この相反する要求の中で最適なバランスを見出すことが、持続可能な設計の鍵となることを理解しました。
今日は、アリストテレスのもう一つの重要な洞察である「存在の様態」について考えてみたいと思います。特に、「可能態(デュナミス)」と「現実態(エネルゲイア)」という概念が、リソースの状態と遷移の本質を理解する上で、驚くべき示唆を与えてくれることを見ていきます。
(ここからAIの執筆になります)
可能態と現実態 - アリストテレスの洞察
アリストテレスは、存在するものの本質を理解しようとする中で、ある根本的な問いに直面しました。それは「物事はどのように変化し、同時にその同一性を保つのか」という問いです。
この問いに答えるために、彼は存在の異なる「様態(トロポス)」を考えました。特に重要なのが「可能態(デュナミス)」と「現実態(エネルゲイア)」という二つの様態です。
この考え方を理解するために、彼が好んで使った例を見てみましょう。大理石の塊を考えてみます。この大理石の塊は、現在の姿(現実態)においては単なる石の塊ですが、その中には彫像になる可能性(可能態)が潜んでいます。彫刻家の技によって、この可能態は現実態へと移行します。しかし重要なのは、彫像になる前から、その可能性は大理石の中に実在していたということです。
または、種子を考えてみましょう。種子の中には、すでに大きな木になる可能性が内在しています。適切な条件が整えば、その可能態は現実態として顕在化します。しかし、種子が木になる可能性を持っているというのは、単なる抽象的な可能性ではなく、種子の本質的な性質の一部なのです。
この洞察は、一見すると古代の思索家の机上の理論のように見えるかもしれません。しかし、この考え方はWebにおけるリソースの本質を理解する上で、驚くべき示唆を与えてくれるのです。
リソースの存在様態 - 状態遷移の本質を求めて
では、この考え方をRESTのコンテキストで考えてみましょう。注文処理を例に取ると:
class OrderResource extends ResourceObject
{
public function onPost(string $productId, int $quantity): static
{
// 新規注文の生成
$orderId = $this->orderService->create($productId, $quantity);
// 注文の初期状態(現実態)をセット
$this->body = [
'orderId' => $orderId,
'status' => 'pending',
'created_at' => new DateTime()
];
return $this;
}
}
一見、これは単なる注文データの作成に見えます。しかし、存在の様態という視点から見ると、ここで起きているのは「注文という可能態」が「具体的な注文という現実態」へと移行するプロセスなのです。
注文が作成された瞬間、そこには複数の可能態が同時に生まれます:
- 確認可能な注文として
- キャンセル可能な注文として
- 支払い可能な注文として
- 変更可能な注文として
これらの可能態は、注文の本質的な部分を構成しています。注文リソースは、これらの可能態を内包しながら存在しているのです。
状態遷移の真の意味 - 可能態から現実態へ
従来、状態遷移は単なるデータの更新として理解されがちでした。注文ステータスを "pending" から "confirmed" に変更する、といった具合にです。しかし、このような理解は表層的すぎます。アリストテレスの視点は、より本質的な理解へと私たちを導いてくれます。
class OrderResource extends ResourceObject
{
public function onPatch(string $id, string $action): static
{
$order = $this->repository->find($id);
if ($action === 'confirm' && $this->canTransitTo($order, 'confirmed')) {
// これは単なるステータスの更新ではない
// 「確認可能な注文」という可能態から
// 「確認された注文」という現実態への本質的な移行
$order = $this->confirmOrder($order);
// 新たな可能態が生まれる
// - 発送可能になる
// - 請求書が発行可能になる
// - 特定の条件下でキャンセル可能になる
}
// 現実態を表現
$this->body = [
'orderId' => $order->id,
'status' => $order->status,
'confirmed_at' => $order->confirmedAt
];
return $this;
}
private function canTransitTo(Order $order, string $targetStatus): bool
{
// 可能態の判定
// これは単なる許可/不許可の判定ではなく
// その遷移が本質的に可能かどうかの判断
return $order->possibleTransitions()->contains($targetStatus);
}
}
このコードが示すように、状態遷移には深い意味が込められています。注文の確認は、単にデータベースの値を更新することではありません。それは:
- ある可能態(確認可能な注文)が、適切な条件下で
- 新しい現実態(確認された注文)へと本質的に変化し
- その過程で新たな可能態の集合が生まれる
というプロセスなのです。
注目すべきは、この遷移が不可逆的な性質を持つことです。確認された注文は、もはや未確認の注文には戻れません。これは単なる業務ルールではなく、存在の様態における本質的な特徴です。ちょうど、成長した木が種子に戻れないように、リソースの状態遷移にも本質的な方向性が存在するのです。
HATEOASの哲学的基礎 - 可能性の表現としてのハイパーメディア
私たちはこれまで、HATEOASを「アプリケーションの状態エンジンとしてのハイパーメディア」として理解してきました。しかし、存在の様態という視点は、この概念により深い意味を与えてくれます。
class OrderResource extends ResourceObject
{
public function onGet(string $id): static
{
$order = $this->repository->find($id);
// 現実態としての注文の状態
$this->body = [
'orderId' => $order->id,
'status' => $order->status,
'amount' => $order->amount,
// 重要:これらのリンクは単なるURLではない
// これらは現在の状態に内在する可能態の表現
'_links' => [
'self' => ['href' => '/orders/' . $id],
'cancel' => $this->createCancelLink($order),
'payment' => $this->createPaymentLink($order),
'shipping' => $this->createShippingLink($order)
]
];
return $this;
}
private function createCancelLink(Order $order): ?array
{
// これは単なる条件チェックではない
// この注文に「キャンセル可能性」という可能態が
// 内在しているかどうかの本質的な判断
if ($order->hasCancellationPotential()) {
return ['href' => '/orders/' . $order->id . '/cancel'];
}
return null;
}
}
このコードが示すように、HATEOASにおけるハイパーメディアの役割は、単にクライアントに利用可能なURLを提供することではありません。それは、リソースの現在の状態に内在する可能態を明示的に表現することなのです。
各リンクは可能な遷移を表していますが、より正確には:
- それらは現在の状態に内在する可能性の表現
- その可能性は、リソースの本質的な性質から導き出される
- リンクの存在(または不在)は、可能態の存在(または不在)を示す
例えば、注文のキャンセルリンクが存在するということは、単にその操作が「許可されている」ということではありません。それは、現在の注文の状態にキャンセル可能性という可能態が本質的に内在しているということを意味するのです。
ステートレス性の深層 - 様態の完全性という要請
RESTの重要な制約の一つであるステートレス性は、しばしば技術的な要件として理解されがちです。しかし、存在の様態という視点からみると、それはより本質的な意味を持っています。
class OrderResource extends ResourceObject
{
#[DbTransaction]
public function onPatch(string $id, string $action): static
{
// 各リクエストは完全な情報を持つ
// これは技術的な要件以上の意味を持つ
$order = $this->repository->find($id);
// 現在の様態(現実態)から、
// 次の様態への遷移可能性(可能態)を判断
if (!$this->isValidTransition($order, $action)) {
// この例外は単なるエラーではない
// それは存在様態としての不可能性を示す
throw new ImpossibleTransitionException(
'この遷移は現在の状態では本質的に不可能です'
);
}
// 遷移の実行
// これは自己完結的なプロセスでなければならない
$newState = $this->executeTransition($order, $action);
// 新しい様態の表現
$this->body = [
'status' => $newState->status,
'transitionedAt' => $newState->transitionedAt,
// 新しい状態に内在する可能態も含む
'possibleTransitions' => $newState->getPossibleTransitions()
];
return $this;
}
}
アリストテレスによれば、存在の様態は各瞬間において完全でなければなりません。これは、RESTのステートレス性の本質的な意味を理解する鍵となります。
ステートレス性が要求するのは:
-
各リクエストの自己完結性
- 各リクエストは、必要な情報を全て含んでいなければならない
- これは単なる技術的な制約ではなく、その瞬間の様態の完全性という要請
-
状態遷移の独立性
- 各遷移は、現在の様態から次の様態への完全な移行として理解される
- 過去の状態や将来の可能性に依存してはならない
-
様態の完全性
- 各時点での状態は、それ自体で完結した意味を持つ
- 現実態と可能態の両方を含む完全な存在として理解される
結論:存在様態から見えるRESTの本質
アリストテレスの存在様態の考えを通じて、私たちはRESTとリソース指向の本質により深く迫ることができました:
リソースの状態は、単なるデータの集合ではなく、現実態と可能態を含む完全な存在様態として理解されます。状態遷移は、可能態から現実態への本質的な移行として捉えられ、その過程で新たな可能態が生まれます。HATEOASは、リソースに内在する可能態を明示的に表現する手段として理解され、ステートレス性は、各時点での様態の完全性を要求する本質的な制約として解釈されるのです。
このような理解は、単なる理論的な考察を超えて、より良いAPIの設計への具体的な指針を与えてくれます。それは私たちに、より一貫性のある状態遷移の設計、より明確なリソースの境界の定義、より本質的なエラー処理の実装、そしてより適切なハイパーメディアの活用を可能にするのです。
明日は「論理学とRESTful」について考察します。アリストテレスの論理学が、APIの推論可能性にどのような示唆を与えてくれるのか、それを探っていきたいと思います。
対話
ここからは私(koriym)とこれを執筆したAIとの対話になります。まずはこの質問から始めました。
リソースの状態遷移を考える
Koriym:
現実態(エネルゲイア)、可能態(デュナミス)2つの言葉が現れました。リソースで考えると、例えばある商品の情報が得られるとそれは商品の現実態(エネルゲイア)を表していて、それはカートに入れることができる性質(デュナミス)があるということですか?
Calude:
はい、その理解は本質を捉えていますね。商品リソースを例に、より具体的に考えてみましょう。
class ProductResource extends ResourceObject
{
public function onGet(string $id): static
{
$product = $this->repository->find($id);
// 現実態(エネルゲイア)としての商品
$this->body = [
'id' => $product->id,
'name' => $product->name,
'price' => $product->price,
'stock' => $product->stock,
// 可能態(デュナミス)の表現
'_links' => [
'addToCart' => $this->createAddToCartLink($product),
'addToWishlist' => $this->createWishlistLink($product)
]
];
return $this;
}
}
この商品リソースには、現実態(エネルゲイア)として「名前」「価格」「在庫数」といった具体的な状態があります。同時に、「カートに入れられる可能性」「ウィッシュリストに追加できる可能性」という可能態(デュナミス)が内在しています。
重要なのは、この可能態は単なる「できるかもしれない」という抽象的な可能性ではなく、その商品の本質的な性質だということです。例えば、在庫がある商品には「カートに入れられる」という可能態が本質的に備わっています。これはちょうど、種子に「木になる可能性」が本質的に備わっているのと同じような意味で存在するのです。
アフォーダンスと可能態
Koriym:
可能態(デュナミス)の説明を見た時に、これはアフォーダンスの事かとも思いました。まずは読者のためにアフォーダンスの説明をしてもらっていいですか?
Calude:
アフォーダンス(affordance)という概念は、心理学者のジェームズ・ギブソンによって提唱された概念です。環境が生物に対して「提供する」あるいは「備えている」行動の可能性のことを指します。
例えば、椅子は「座ること」をアフォードします。これは椅子の形状や高さ、強度といった物理的特性が、人間に「座る」という行動の可能性を提供しているということです。重要なのは、このアフォーダンスは椅子と人間の関係性の中に存在するという点です。小さな子供にとっては「登る」ことをアフォードするかもしれませんし、別の文化圏では全く異なる使い方をアフォードするかもしれません。
Webのインターフェースデザインでは、このアフォーダンスの概念が重要な役割を果たしています。例えば、青く下線の引かれたテキストは「クリックできる」ということをアフォードしますし、ボタンのような見た目の要素は「押せる」ということをアフォードします。
しかし、アリストテレスの可能態(デュナミス)とアフォーダンスは、似て非なる概念かもしれません。可能態は対象に内在する本質的な可能性を指すのに対し、アフォーダンスは環境と主体の関係性の中に存在する行動可能性を指すからです。この違いについて、もう少し考察してみましょうか?
Koriym:
はい、是非お願いします。(以前の関数とリソースの違いのように)いきなり核心に迫れるような気がします!
Calude:
はい、この違いは本質的だと思います!アフォーダンスとアリストテレスの可能態(デュナミス)の違いを、リソース設計の文脈で考えてみましょう。
例えば、注文リソースを考えてみましょう:
class OrderResource extends ResourceObject
{
public function onGet(string $id): static
{
$order = $this->repository->find($id);
// キャンセル可能かどうかは、このリソースの内在的な性質
// これは可能態(デュナミス)
$cancellable = $order->isCancellable();
// キャンセルボタンをどう表示するか、どう操作できるようにするか
// これはアフォーダンス
$cancelButton = $this->createCancelButton($cancellable);
}
}
可能態(デュナミス)は、リソースに内在する本質的な性質です。注文がキャンセル可能かどうかは、その注文の状態や性質によって決定される内在的な特徴です。これは観察者や利用者の存在とは独立して存在します。
一方、アフォーダンスは関係性の中に存在します。キャンセルボタンをどう表示するか、どのような操作方法を提供するかは、ユーザーとシステムの関係性の中で決まります。同じキャンセル可能性でも、Webブラウザで見るユーザーとAPIを直接利用するプログラムでは、全く異なるアフォーダンスが必要になるかもしれません。
この違いは、リソース設計において重要な意味を持ちます。可能態はリソースの本質を定義し、アフォーダンスはその可能態をどのように表現し、利用可能にするかを定義するのです。
Koriym:
おお! なるほど! キャンセルをアフォーダンスとして捉えると、それはユーザーとのインターフェースの一部となり(機能をアフォードしているため)、いわばUXの話になりますね。それを性質、つまりそのアイテムの形相因(エイドス)—「何であるか」—として考えると、可能態(デュナミス)になる、ということですね。DDDが扱うドメインの話としても解釈できるかもしれません。この理解で合っていますでしょうか?
Calude:
はい、その理解は非常に正確です!これは実に重要な洞察ですね。この区別は、関心の分離という観点からも本質的な意味を持ちます。
可能態(デュナミス)は、ドメインの本質的な性質として存在します:
class Order // ドメインモデル
{
public function isCancellable(): bool
{
// これはビジネスロジック
// 注文の本質的な性質を表現
return $this->status === 'pending' &&
$this->createdAt > new DateTime('-24 hours');
}
}
一方、アフォーダンスはインターフェース層の関心事です:
class OrderController
{
public function show(Order $order): Response
{
// UIレベルでの表現
// ユーザーとの関係性の中でどう表現するか
return $this->render('order.html.twig', [
'cancelButtonStyle' => $order->isCancellable()
? 'enabled'
: 'disabled'
]);
}
}
DDDの文脈で考えると、可能態(デュナミス)はドメインモデルの一部として、ビジネスの本質的な規則や制約を表現します。アフォーダンスは、そのドメインの性質をユーザーにどう提示し、どう操作可能にするかというユーザーインターフェースの問題になります。
この区別は、システムの層を適切に分離する上でも重要な指針となりますね。
Koriym:
なるほど。「注文」というドメインを考えるとき、「キャンセルができる」というのは注文の性質そのものなので、分離して考えてはいけない、ということですね。キャンセルができるということ自体が、注文の一部である、ということですね。
Calude:
はい、その通りです!これはとても重要な気づきですね。「キャンセル可能性」は注文の本質的な性質であり、後から付け加えられた機能や、外部から与えられた特性ではありません。
// アンチパターン:キャンセル機能を外部サービスとして実装
class CancellationService
{
public function canCancel(Order $order): bool
{
// これは本質を見失っている
return $this->checkCancellationRules($order);
}
}
// 適切なアプローチ:注文の本質的な性質として実装
class Order
{
public function isCancellable(): bool
{
// これは注文という存在の本質的な部分
return $this->hasCancellationPotential();
}
}
この理解は、前回議論した形相(エイドス)の概念とも深く結びついています。注文の形相(エイドス)には、その可能態(デュナミス)が本質的に含まれているのです。それは単なる「できること」のリストではなく、注文という存在の本質的な部分なのです。
これは実装の方針にも大きな影響を与えますね。キャンセル可能性を判断するロジックは、注文クラスの中に存在するべきで、外部のサービスに委ねるべきではないということになります。
Koriym:
すごく納得しました!これでこの記事を締めくくってもいいくらいですね(笑)。これは本当に大事なことですね。
Calude:
はい!本当にこれは重要な発見ですね。物事の本質に迫るというのは、こういうことなのだと思います。
当初、可能態(デュナミス)という言葉を聞いて「難しいことを言っているだけでは?」と感じられたのは自然な反応だと思います。しかし、議論を深めていく中で、これが単なる哲学的な概念ではなく、ソフトウェア設計における非常に実践的な指針を与えてくれることが明らかになりました。
キャンセル可能性は注文の「機能」ではなく「性質」である、という理解は、設計に大きな影響を与えます。例えば:
// これは注文の本質を見失った設計
class OrderManagementService {
private CancellationService $cancellationService;
private PaymentService $paymentService;
private ShippingService $shippingService;
}
// これは注文の本質を理解した設計
class Order {
public function isCancellable(): bool
{ /* ... */ }
public function isPayable(): bool
{ /* ... */ }
public function isShippable(): bool
{ /* ... */ }
}
アリストテレスの可能態(デュナミス)という概念は、オブジェクト指向設計やドメイン駆動設計の本質を照らし出してくれる、深い洞察を含んでいるように思います。
Koriym:
現実的には、それぞれをオブジェクト(Order, Cancel)として実装し、Orderの中にCancelをインジェクトする形が良さそうですね。Orderはドメインの言葉(isCancellable)を使用でき、利用する側はキャンセルの実体に関心を持つ必要がありません。
言い換えれば、前回の「適切な中庸(メソテーセス)」を得られたということですよね。Orderの粒度としてCancelを含むべきだという実践的知恵(フロネーシス)に、この可能態(デュナミス)の考え方が役立ったわけです。
Calude:
はい、その通りです!可能態(デュナミス)の考え方が、適切な中庸(メソテーセス)を見出すための実践的知恵(フロネーシス)を導いてくれたというのは、素晴らしい整理ですね。
具体的な実装で考えてみましょう:
class Order
{
public function __construct(
private readonly CancellationPolicy $cancellation
) {}
// ドメインの言葉を使った明確なインターフェース
public function isCancellable(): bool
{
// 可能態(デュナミス)の判断はOrderの責務
return $this->cancellation->canCancel($this);
}
}
// キャンセルの具体的な実装は隠蔽されている
class CancellationPolicy
{
public function canCancel(Order $order): bool
{
// 具体的なキャンセル可否の判定ロジック
}
}
このように、Orderはキャンセル可能性という性質を持つことを表明しながら、その具体的な実装は適切に分離される。これは:
- 注文の本質的な性質(可能態)を表現しつつ
- 実装の複雑さを適切に分割し(中庸)
- ドメインの言葉を維持している(実践的知恵)
まさに、これまでの議論で得られた知見が、具体的な設計の指針として結実していますね。
ハイパーメディアの本質
Koriym:
対象ドメインがその状態だけでなく、可能態(デュナミス)を含めてその本質とするという考え方は、一見すると刺激的なアイデアのように聞こえますが、それはRESTのリソースが持つ自然な性質そのものですよね。aタグもformタグもないhtmlページは不自然です。
Calude:
はい、その指摘は本質を突いていますね。Webの世界では、状態とその可能性は自然に結びついています。典型的なECサイトの商品ページを考えてみましょう:
<div class="product-detail">
<!-- 商品の現実態(エネルゲイア) -->
<h1>MacBook Pro 16"</h1>
<div class="gallery">
<img src="product-main.jpg" alt="MacBook Pro">
<img src="product-side.jpg" alt="Side view">
</div>
<p class="price">$2,499</p>
<p class="stock-status">In Stock: 5 units</p>
<!-- ここからが可能態(デュナミス)の自然な表現 -->
<form action="/cart/add" method="post">
<select name="configuration">
<option>32GB RAM / 1TB SSD</option>
<option>64GB RAM / 2TB SSD</option>
</select>
<button type="submit">Add to Cart</button>
</form>
<div class="product-actions">
<a href="/wishlist/add/macbook">Add to Wishlist</a>
<a href="/compare/macbook">Compare Models</a>
<a href="/notify/macbook">Notify When Price Drops</a>
</div>
<!-- さらなる可能態の表現 -->
<div class="related-actions">
<a href="/support/macbook">Technical Support</a>
<a href="/reviews/macbook">Read Reviews</a>
<a href="/reviews/macbook/new">Write a Review</a>
</div>
</div>
このHTMLは単なるマークアップ以上の意味を持っています。商品の名前、価格、在庫状況という現実の状態(現実態)と、その商品に対して可能な操作(可能態)が自然に織り込まれています。「カートに入れる」「ウィッシュリストに追加する」「レビューを書く」といった可能性は、まさにその商品の本質的な性質の表現なのです。
興味深いのは、この構造がRESTのリソース設計と完全に調和していることです。HALやJsonAPI、あるいはGraphQLでさえ、結局のところ同じような構造を別の形式で表現しようとしているに過ぎません。Web以前からあったクライアント・サーバーシステムが苦心して実装していた「操作の可能性の伝達」が、Webの世界では自然な形で組み込まれているのです。
この視点に立つと、多くのAPIフレームワークが陥りがちな「エンドポイントのリスト」としてのAPI設計が、いかに本質を見失っているかが分かります。リソースとは状態とその可能性の有機的な結合体なのであり、それをWebの自然な形式で表現することこそが、RESTの本質なのかもしれません。
Koriym:
HATEOASですね。初めて耳にする読者のために丁寧に説明してください。
Calude:
HATEOAS(Hypermedia as the Engine of Application State)は、RESTの重要な原則の一つです。リソースの現在の状態に基づいて、次にどのような操作が可能かをハイパーメディアを通じて伝えるという考え方です。
Webサイトでの経験を思い出してください。新しいサイトを訪れたとき、私たちはリンクやフォームを通じて、可能な操作を自然に発見していきます。APIマニュアルを読む必要はありません。HATEOASは、この同じ原則をAPIにも適用しようというものです。
例えば、注文リソースをHALフォーマットで表現すると:
{
"orderId": "12345",
"status": "pending",
"total": 2499,
"_links": {
"self": { "href": "/orders/12345" },
"cancel": { "href": "/orders/12345/cancel" },
"payment": { "href": "/orders/12345/payment" }
}
}
このレスポンスは、注文の現在の状態だけでなく、その注文に対して可能な操作(キャンセル、支払いなど)も同時に伝えています。これらのリンクは、先ほど議論した可能態(デュナミス)の具体的な表現となっているのです。
クライアントはこれらのリンクを辿ることで、APIの構造を動的に理解し、利用することができます。これはまさにWebブラウザでウェブサイトを探索するのと同じような体験です。
Koriym:
読み方は?あえてカタカナ表記だとヘイテオス?
Calude:
一般的には「ハテオアス」と発音されます。これは "Hypermedia as the Engine of Application State" の頭文字を取った略語です。時々「ヘイティオス」という発音も聞かれますが、Roy Fieldingを含むREST APIの研究者やコミュニティでは「ハテオアス」が標準的な発音として定着しています。
この用語は技術的な文脈で使われることが多いため、正確な発音にこだわる必要はないかもしれません。重要なのは、その背後にある「ハイパーメディアがアプリケーションの状態を導く」という考え方を理解することですね。
Koriym:
もう少し掘り下げていただけますか?Application State(アプリケーション状態)とは何でしょうか?また、「そのエンジンとしての」という表現はなぜ使われているのですか?
Calude:
はい、「Application State」と「Engine」という言葉の意味を掘り下げていきましょう。
まず、Webサイトでのユーザー体験を考えてみましょう。例えば、ECサイトで商品を購入する時のことを想像してください。最初は商品一覧を見ていて、ある商品を選び、カートに入れ、配送先を入力し、支払い方法を選択して...というように、アプリケーションの「状態」は常に変化していきますよね。
この一連の流れの中で、ユーザーが次に何ができるかは、その時点での状態によって決まります。例えば:
- カートが空の時は「注文を確定」はできない
- 配送先を入力していない時は「支払い方法の選択」には進めない
- 支払いが完了した注文は「キャンセル」できない
このように、「今どういう状態か」と「次に何ができるか」は密接に結びついています。これが「Application State(アプリケーション状態)」です。
では、なぜ「Engine(エンジン)」なのでしょうか?従来のAPIでは、クライアントは「次に何ができるか」を事前に知っている必要がありました。API仕様書を読み、エンドポイントを覚え、適切な順序で呼び出す...これは、クライアントが「状態遷移のエンジン」となっているということです。
一方、HATEOASでは、サーバーから返されるハイパーメディア(リンクや操作の可能性)が、クライアントを次の状態へと導きます。まるで、ハイパーメディアが「エンジン」となって、アプリケーションの状態遷移を駆動しているようなものなのです。
これは単なる技術的な違いではありません。クライアントがAPIの詳細な仕様を知る必要がなくなり、サーバー側の変更に対してより柔軟に対応できるようになります。ちょうど、Webブラウザが初めて訪れたサイトでも、リンクを辿りながら自然に操作できるように。
Koriym:
なるほど!次に何ができるかという知識がクライアント側にあるのか、それともサーバー側にあるのか、という視点を持つのはとても興味深いですね。例えば、商品が存在している場合、その商品が購入可能かどうかをサーバーサイドのデータで available_for_purchase: true のように表現することも可能です。
しかし、それよりもセマンティクス(意味)を伴いつつ、リンクで表現する方がよりスマートと言えます。リンクを用いることで、単なるフラグではなく「購入可能」というアクションそのものを明確に示すことができます。それがまさにハイパーメディアの考え方ですよね?
Calude:
はい、その洞察は本質的です。リソースの可能性をどのように表現するかという問題は、単なる技術的な選択以上の意味を持っています。
従来のアプローチ:
{
"product_id": "12345",
"name": "MacBook Pro",
"price": 2499,
"available_for_purchase": true,
"can_add_to_wishlist": true,
"can_write_review": false
}
ハイパーメディアを用いたアプローチ:
{
"product_id": "12345",
"name": "MacBook Pro",
"price": 2499,
"_links": {
"purchase": {
"href": "/products/12345/purchase",
"method": "POST"
},
"wishlist": {
"href": "/wishlist/12345",
"method": "PUT"
}
}
}
ハイパーメディアによる表現は、単なる「可能/不可能」以上の豊かな意味を持ちます。それは:
- 操作の方法を具体的に示している(URIとHTTPメソッド)
- 利用可能な操作を自己記述的に表現している
- クライアントに具体的な操作手順を提供している
例えば「購入可能」という状態を、購入のためのURIとして表現することで、クライアントは「それが可能かどうか」だけでなく「どのように実行するか」という情報も同時に得ることができます。
これは私たちがWebブラウザでサイトを閲覧する体験と同じです。ページ上のリンクやボタンは、単に「何ができるか」を示すだけでなく、その操作を実行するための具体的な手段も提供しているのです。
このアプローチにより、APIの進化がより柔軟になります。新しい操作が追加されても、それはリンクとして自然に表現され、クライアントの変更を最小限に抑えることができます。
Koriym:
なるほど、ハイパーメディアが提供する「自己記述的な操作」の考え方は非常に興味深いですね。この方法によって、クライアントの仕様理解の負担が軽減されるだけでなく、APIの進化にも柔軟に対応できるという点に納得しました。
一方で、次のようなケースを考えてみます。"available_for_purchase": true といった情報が提供されず、クライアント側にすべてのロジックが委ねられている場合です。具体的には、クライアントが在庫数、発売開始日、購入資格といったデータを個別にフェッチし、それらを基に「購入可能」ボタンを表示するようなパターンです。
このようなケースでは、商品が持つべき性質やその可能態(デュナミス)を、本来含めるべき商品リソースから分離してしまっている、とも言えますね。
Calude:
はい、その指摘は非常に重要です。現代のWeb開発、特にSPAなどでよく見かけるそのパターンは、リソースの本質から離れてしまっているかもしれません。
// クライアント側で判断するパターン
class ProductClient {
public function shouldShowPurchaseButton(): bool {
$product = $this->fetchProduct();
$inventory = $this->fetchInventory();
$releaseDate = $this->fetchReleaseDate();
$userEligibility = $this->fetchUserEligibility();
// クライアント側でビジネスロジックを再実装
return $inventory->inStock() &&
$releaseDate->isPassed() &&
$userEligibility->canPurchase();
}
}
// リソースの本質を表現するパターン
class ProductResource extends ResourceObject
{
public function onGet(string $id): static
{
$product = $this->repository->find($id);
$this->body = [
'name' => $product->name,
'price' => $product->price,
'_links' => [
// 商品の可能態(デュナミス)をリンクとして表現
'purchase' => $product->isPurchasable() ?
['href' => "/products/{$id}/purchase"] : null
]
];
return $this;
}
}
前者のアプローチでは、「商品が購入可能かどうか」という商品の本質的な性質(可能態)が、クライアントの実装の中に埋もれてしまいます。これは単なる実装の問題ではなく、ドメインの理解にも影響を与えかねません。
後者のアプローチでは、商品の現実態と可能態が一体として表現され、その判断はリソースの側で行われます。これにより、ビジネスロジックの一貫性が保たれ、クライアントの実装もシンプルになります。
これは昨日議論した中庸(メソテース)の問題とも関連していますね。リソースの責務をどこまでにすべきか、その適切なバランスを見出す際の一つの指針となりそうです。
Koriym:
はい、まさにその通りです。クライアント側でビジネスロジックを再実装する場合、サーバーにあるデータを個別にフェッチする必要が生じます。このような状況を「オーバーフェッチ」と呼ぶのであれば、それは設計者自身が作り出した問題であり、RESTそのものの問題ではありませんね。この点は、前回の粒度における中庸(メソテース)についての議論と非常によく似ています。
構造としても同じです。「適切な粒度の中庸(メソテース)をサーバーが持つべきか、クライアントが持つべきか」という問題と、「購入可否のビジネスロジックという可能態(デュナミス)をサーバーが持つべきか、クライアントが持つべきか」という問題は、同じ軸上にあると言えますね。
Calude:
はい、その洞察は見事です。両者の問題の本質が同じであることを指摘されましたね。これはRESTに対する誤解の根源を示唆しているように思います。
クライアント側に責務を寄せすぎることの問題を、具体例で考えてみましょう:
// クライアント側でオーバーフェッチと再実装が必要なケース
async function canPurchaseProduct(productId) {
const [
product = await fetch('/products/' + productId),
inventory = await fetch('/inventory/' + productId),
eligibility = await fetch('/user/eligibility/' + productId),
releaseInfo = await fetch('/products/' + productId + '/release')
] = await Promise.all([...]);
return determineIfPurchasable(
product, inventory, eligibility, releaseInfo
);
}
// サーバー側でリソースの本質として表現するケース
class ProductResource extends ResourceObject
{
public function onGet(string $id): static
{
$product = $this->repository->find($id);
// 可能態(デュナミス)の判断はリソースの一部
$purchaseLink = $product->isPurchasable()
? ['href' => "/products/{$id}/purchase"]
: null;
$this->body = [
'name' => $product->name,
'price' => $product->price,
'_links' => ['purchase' => $purchaseLink]
];
return $this;
}
}
このように、リソースの適切な粒度や可能態(デュナミス)の判断をクライアント側に委ねることは、結果として複数のAPIコールやロジックの重複を引き起こします。これは単なる実装の問題ではなく、リソースの本質的な理解に関わる設計上の問題なのです。
Koriym:
「データがサーバーにあるため、サーバーサイドがより良い答えを引き出しやすい」という観点だけでなく、他の重要な点にも注目する必要があります。たとえレイテンシーがゼロで、コストや速度が理想的なネットワークが実現したとしても、凝集度の問題は依然として残るのではないでしょうか。
例えば、WebクライアントとiOS/Androidクライアントをそれぞれ実装するケースなどが挙げられます。それぞれに同じロジックが違う言語で記述されることになってしまいます。
Calude:
はい、これは非常に重要な指摘ですね。凝集度の問題は、パフォーマンスや技術的な制約を超えた、より本質的な設計上の課題です。
具体例で考えてみましょう:
// Webクライアント(JavaScript)
function isPurchasable(product, inventory, eligibility) {
return inventory.stock > 0 &&
new Date() >= new Date(product.releaseDate) &&
eligibility.status === 'eligible';
}
// iOSクライアント(Swift)
func isPurchasable(product: Product, inventory: Inventory, eligibility: Eligibility) -> Bool {
return inventory.stock > 0 &&
Date() >= product.releaseDate &&
eligibility.status == "eligible"
}
// Androidクライアント(Kotlin)
fun isPurchasable(product: Product, inventory: Inventory, eligibility: Eligibility): Boolean {
return inventory.stock > 0 &&
Date() >= product.releaseDate &&
eligibility.status == "eligible"
}
これに対して、サーバー側でリソースの可能態(デュナミス)として表現する場合:
class ProductResource extends ResourceObject
{
public function onGet(string $id): static
{
$product = $this->repository->find($id);
// ビジネスロジックは一箇所に集約される
$this->body = [
'name' => $product->name,
'price' => $product->price,
'_links' => [
'purchase' => $product->isPurchasable() ?
['href' => "/products/{$id}/purchase"] : null
]
];
return $this;
}
}
このように、ビジネスロジックが各クライアントに散在することは、単なる実装の重複以上の問題を引き起こします。それは:
- 一貫性の維持が困難になる
- 変更に対する脆弱性が増す
- テストの複雑性が増大する
リソースの本質的な性質は、その性質を最もよく理解できる場所で表現されるべきです。これは、先ほどの可能態(デュナミス)の議論とも符合しますね。
一方、この可能態をリソースの本質的な部分として適切に設計すると、クライアントはただリソースの状態を解釈するだけでよくなります。つまり、「商品とは何か」という本質的な理解が、クライアントの実装の違いを超えて統一されるのです。これはドメイン駆動設計が目指す「統一されたドメインモデル」とも響き合う考え方ですね。
Koriym:
ですよね。対話を通じてアフォーダンスとの違いが少しづつ分かってきました。
Calude:
はい、この対話を通じて、アフォーダンスと可能態(デュナミス)の本質的な違いが見えてきましたね。
アフォーダンスは、ユーザーインターフェースにおける「操作の可能性の提示」を指し、主にUXの領域で考えられる概念です。一方、可能態(デュナミス)は、そのリソースが本質的に持っている性質や能力を表します。
商品の例で言えば:
class ProductInterface
{
// アフォーダンス:UIでの表現方法
public function renderPurchaseButton(): string
{
// ボタンをどう表示するか
}
}
class Product
{
// 可能態(デュナミス):商品の本質的な性質
public function isPurchasable(): bool
{
// ビジネスロジックとしての判断
}
}
アフォーダンスは環境や文脈によって変化しますが、可能態は商品の本質的な性質として存在します。この区別を理解することで、リソース設計における責務の適切な配置が見えてくるのですね。
Koriym:
ハイパーメディアAPIは時に実践的出ないと揶揄されることが少なくなりません。しかしここまでの対話からは極めて実践的で本質的なもののように感じました。
Calude:
ハイパーメディアAPIが「実践的でない」と揶揄される主な理由は、その実装が複雑であり、即時的なメリットが見えにくいためです。多くの開発者は、シンプルなCRUD操作と固定的なエンドポイントのリストで必要十分と考える傾向があります。しかし、こうした見方は短期的であり、可能態(デュナミス)の概念が示すように、リソースの状態とその潜在的な変化可能性は本来密接に結びついています。ハイパーメディアとしてそれらを表現することは単なる技術的選択ではなく、リソースの本質を正しく扱う手段と言えるのです。
「実践的でない」という批判が生じる背景には、APIを単なるデータアクセス層とみなす視点があります。しかし、リソース指向とハイパーメディアを採用すれば、より深いドメイン理解とその表現が可能になります。これによって、クライアントとサーバーの責務分担が適切になり、ビジネスロジックの一貫性が保たれ、APIが自然な形で進化できるようになるなど、長期的に本質的な価値が生まれます。
結局、ハイパーメディアAPIが「実践的でない」と見なされるのは、その本質的価値がまだ十分に理解されていないためだと言えるでしょう。
Koriym:
APIを単なるデータのCRUD操作の集合から、可能態(デュナミス)を含んだハイパーメディアとして実装すると高い凝集度を持ち、疎結合にできそうです。
Calude:
はい、その通りです。マイクロサービスの大きな課題の一つは、サービス間の依存関係の管理です。従来の実装では、各サービスは他のサービスのエンドポイントやデータ構造を事前に知っている必要があり、これは高い結合度を生みます。
しかし、可能態(デュナミス)の考え方とハイパーメディアを活用すると、より自然な設計が可能になります:
class OrderService extends ResourceObject
{
public function onPost(string $productId): static
{
// 注文の作成時点で、その注文が持つ本質的な可能性が決定される
$order = $this->createOrder($productId);
$this->body = [
'id' => $order->id,
'status' => 'created',
'_links' => [
// 可能な操作が自己記述的に表現される
'payment' => $this->getPaymentLink($order),
'shipping' => $this->getShippingLink($order),
'cancel' => $this->getCancelLink($order)
]
];
return $this;
}
}
このアプローチでは、サービスは他のサービスの実装詳細を知る必要がありません。代わりに、リソースが提供するハイパーメディアを通じて、可能な操作を動的に発見できます。これは、まさにWebが実現している柔軟な連携のモデルをマイクロサービスに適用することとも言えますね。
サービスの責務も自然に定まります。それぞれのサービスは、自身が管理するリソースの本質的な性質(可能態)を理解し、表現することに集中できます。これは高い凝集度につながりますね。
オーケストレーションとコレオグラフィー
Koriym:
さきほど「オーケストレーション」という言葉が出ましたが、読者のためにこれについて詳しく説明してください。「コレオグラフィー」と対になる言葉ですよね。
Calude:
はい、マイクロサービスにおける「オーケストレーション」と「コレオグラフィー」の違いを説明させていただきます。
従来のCRUDベースのAPIアプローチでは、しばしばオーケストレーションパターンに陥りがちです。例えば注文プロセスを考えてみましょう:
class OrderOrchestrator
{
public function processOrder(string $orderId): void
{
// オーケストレーターが全てのプロセスを指揮
$orderStatus = $this->orderService->getStatus($orderId);
if ($orderStatus === 'pending') {
$inventory = $this->inventoryService->checkStock($orderId);
if ($inventory->isAvailable()) {
$payment = $this->paymentService->process($orderId);
if ($payment->isSuccessful()) {
$this->shippingService->arrange($orderId);
$this->orderService->updateStatus($orderId, 'processing');
}
}
}
}
}
このアプローチでは、オーケストレーターが全てのサービスの詳細を知っていて、それらを直接制御します。各サービスのエンドポイント、順序、データ構造を事前に把握している必要があります。これは強い結合を生み出します。
一方、コレオグラフィーパターンは、サービス間の協調をより緩やかに実現します。ハイパーメディアを活用すると、このパターンを自然に実現できます:
class OrderResource extends ResourceObject
{
public function onPatch(string $id, string $action): static
{
$order = $this->repository->find($id);
// リソースの状態に基づいて次の可能な操作が決定される
$this->body = [
'status' => $order->status,
'_links' => [
// 現在の状態で可能な操作のみが提供される
'payment' => $order->canPay() ?
['href' => "/payments/{$id}"] : null,
'shipping' => $order->canShip() ?
['href' => "/shipping/{$id}"] : null
]
];
return $this;
}
}
このアプローチでは、各サービスは自身の状態と可能な操作を自己記述的に表現し、クライアントはその表現に基づいて次の操作を決定します。中央の指揮者は存在せず、システムは協調的に動作します。
これは私たちがWebを使う体験に近いですね。Webサイトを閲覧するとき、私たちは中央の指揮者に従うのではなく、各ページが提供するリンクやフォームを通じて自然に操作を進めていきます。これがコレオグラフィーの本質です。
Koriym:
サーバーサイドにデータだけが存在し、可能態(デュナミス)がない場合、クライアントがその操作手順(ビジネスロジック)を含まなくてはならなくなり、データや操作を統括する「指揮者」が必要になります。一方で、コレオグラフィーでは指揮者は存在せず、各サービスが隣接するサービスと協調して動作するイメージでしょうか。
これはまるで、手続き型とオブジェクト指向パラダイムの比較のようですね。アラン・ケイ(Alan Kay)がオブジェクト指向を語ったとき、その核心的な概念は「オブジェクトが自己完結的なエンティティとして振る舞いを内包し、相互作用する」事でした。これは今回のアリストテレスの可能態(デュナミス)の概念とも響き合います。
Calude:
その洞察は素晴らしいですね!手続き型とオブジェクト指向の比較は、オーケストレーションとコレオグラフィーの本質的な違いを理解する上で非常に示唆に富んでいます。
手続き型プログラミングでは、データと振る舞いが分離され、中央の制御フローがそれらを操作します。これはまさにオーケストレーションのパターンと同じです:
// 手続き型的なアプローチ / オーケストレーション
class OrderProcessor {
public function processOrder($orderId) {
$orderData = $this->fetchOrderData($orderId);
$inventoryData = $this->fetchInventoryData($orderId);
$customerData = $this->fetchCustomerData($orderId);
// 中央の制御フローがロジックを持つ
if ($this->validateInventory($inventoryData) &&
$this->validateCustomer($customerData)) {
$this->updateOrder($orderData);
}
}
}
一方、アラン・ケイが説いたオブジェクト指向の本質は、オブジェクトが自身の振る舞いを内包し、メッセージパッシングを通じて協調することでした。これはコレオグラフィーのパターンと驚くほど似ています:
// オブジェクト指向的なアプローチ / コレオグラフィー
class OrderResource extends ResourceObject
{
public function onPost(string $id): static
{
$order = $this->repository->find($id);
// リソースが自身の振る舞いと可能性を内包
$this->body = [
'status' => $order->status,
'_links' => [
'self' => ['href' => "/orders/{$id}"],
// 可能態(デュナミス)の表現
'payment' => $order->canPay() ?
['href' => "/payments/new?order={$id}"] : null,
'cancel' => $order->canCancel() ?
['href' => "/orders/{$id}/cancel"] : null
]
];
return $this;
}
}
アリストテレスの可能態(デュナミス)の概念は、まさにこの「自己完結的な振る舞い」の哲学的な基礎を提供してくれているように思えます。リソースは単なるデータの集合ではなく、その本質的な性質として可能な振る舞いを内包している。これはアラン・ケイがオブジェクトについて語った vision と深く共鳴していますね。
この視点に立つと、ハイパーメディアAPIは単なる技術的な選択ではなく、より本質的な設計思想の表現として理解できます。それは「データと操作の分離」ではなく「自己完結的なリソースの協調」という、より自然なシステムの姿を示唆しているのかもしれません。
可能態(デュナミス)の考え方は、リソースやオブジェクトを単なるデータの集合や機能の寄せ集めとしてではなく、その本質的な性質から生まれる振る舞いを持つ自律的な存在として理解することを可能にします。
Koriym:
手続き型プログラミングとオブジェクト指向プログラミングの違いを例に挙げることで、オーケストレーション(手続き主導)とコレオグラフィー(状態と可能態指向)の対比が一層明確になりましたね。
手続き型プログラミングを考えてみましょう。そこでは「何をどうするか」を明確な手順として記述し、関数呼び出しやプロシージャの順番がコードとして固定的に定義されています。たとえば、ある業務フローを実装するとき、doStepA()
→ doStepB()
→ doStepC()
といった手続きがはじめから「指揮者的なコード」として存在するわけです。この場合、処理の流れを決める知識(ロジック)は中心化された「手続き」に埋め込まれていて、各ステップ(関数やモジュール)は比較的受動的な部品になりがちです。
一方、オブジェクト指向パラダイムでは、各オブジェクトが自らの状態と振る舞いを内包しています。オブジェクトはただのデータストアではなく、「このオブジェクトにはどんな操作が本質的に可能なのか?」という可能態(デュナミス)が定義されている存在です。たとえば、Orderオブジェクトが「自分は現在 'confirmed' 状態だから 'ship()' メソッドを呼べる」という内的な可能性を知っているとき、外部の「指揮者」はなくとも、コードは自然な流れで状態遷移を進めていけますね。
Calude:
はい、その通りですね。そして興味深いのは、この違いがマイクロサービスにおけるサービス間の関係性や、クライアント・サーバー間の関係性にも同じように現れることです。
オーケストレーションでは、処理の流れを制御する「指揮者」的なサービスやクライアントが必要になります:
// オーケストレーション的なアプローチ
class OrderProcessor {
public function processOrder($orderId) {
// 指揮者がプロセス全体を知っている
$order = $this->checkOrderStatus($orderId);
if ($order->status === 'new') {
$stock = $this->checkInventory($order->productId);
if ($stock->available) {
$payment = $this->processPayment($order->id);
if ($payment->success) {
$this->arrangeShipment($order->id);
}
}
}
}
}
一方、コレオグラフィーでは、各サービスが自身の状態と可能な操作を知っており、その協調によってプロセスが進んでいきます:
class OrderResource extends ResourceObject
{
public function onGet(string $id): static
{
$order = $this->repository->find($id);
// リソースが自身の可能態を知っている
$this->body = [
'status' => $order->status,
'_links' => [
'payment' => $order->isPayable() ?
['href' => "/payments/new?order={$id}"] : null,
'shipping' => $order->isShippable() ?
['href' => "/shipping/arrange?order={$id}"] : null
]
];
return $this;
}
}
これは知識の所在の違いとも言えます。オーケストレーションでは、ビジネスプロセスの知識が中央の指揮者に集中します。一方、コレオグラフィーでは、その知識が各サービスの可能態(デュナミス)として分散して存在し、ハイパーメディアを通じて表現されます。
この違いは、システムの進化や保守性に大きな影響を与えます。中央集権的な指揮者は変更に脆くなりがちですが、可能態を持つリソースの協調は、より柔軟な進化を可能にします。
Koriym:
一方で、オーケストレーションが単なるCRUD操作にとどまるのであれば、「REST API」(厳密にはハイパーメディアがないため、REST APIというよりHTTP APIと呼ぶ方が正確ですが)ではなく、RPCで十分ではないか、という話になりますね。その点で受け入れられたのがgRPC。また、そのオーケストレーション自体を言語レベルで実現しようと考えたのがGraphQLというわけですね。
Calude:
はい、その通りです。APIアーキテクチャの進化を見ると、興味深いパターンが見えてきますね。
RPCやgRPCは、まさにオーケストレーション的な考え方と相性が良いです。リモートの関数を呼び出すという考え方は、手続き型プログラミングの自然な拡張とも言えます:
// gRPCの例
service OrderService {
rpc ProcessOrder(OrderRequest) returns (OrderResponse) {}
rpc CheckInventory(InventoryRequest) returns (InventoryResponse) {}
rpc ProcessPayment(PaymentRequest) returns (PaymentResponse) {}
}
一方、GraphQLはクライアントに強い制御を与えることで、別の形でオーケストレーションを実現しています:
query {
order(id: "123") {
status
items {
product {
inventory {
available
}
}
payment {
status
}
}
}
}
これらのアプローチは、それぞれの用途で有効です。しかし、可能態(デュナミス)の視点から見ると、これらはリソースの本質的な性質を表現するというよりも、データや操作へのアクセスを最適化することに主眼を置いているように見えます。
特にマイクロサービス間の連携において、RPCやGraphQLは強い結合を生みやすい傾向があります。なぜなら、サービス間の関係が静的に定義され、それぞれのサービスが他のサービスのインターフェースを事前に知っている必要があるからです。
これに対してハイパーメディアは、リソースの本質的な性質(可能態)を表現し、サービス間の動的な発見と協調を可能にします。これは必ずしも全ての場合に最適な選択とは限りませんが、システムの進化という観点からは重要な特性だと言えるでしょう。
Koriym:
Roy T. Fielding(RESTの提唱者)のTwitter(当時)のツイート:
(本当のREST APIを作る理由は、進化可能性を得るためだ……"v1"はAPIの顧客に中指を立てるようなもので、RPC/HTTP(RESTではない)を示している。)
このバージョニングも、強い結合や密結合の一例ですよね。
実際はOpenAPIのスキーマ定義やgRPCの型定義のように、分散システムに強い結合を持ち込むことがむしろ良しとされる事が常です。しかし、これは(Royの言うように)凝集度と疎結合の原則に反するものですね。
Calude:
はい、その指摘は本質を突いていますね。バージョニングの問題は、APIの設計思想の違いを如実に表しています。
// 強い結合を前提とした設計
class OrderApiV1Controller
{
public function processOrder() { /* v1の処理 */ }
}
class OrderApiV2Controller
{
public function processOrder() { /* v2の処理 */ }
}
このようなバージョニングは、APIとクライアントの間に強い結合を作り出します。クライアントは特定のバージョンのAPIの詳細な仕様に依存し、その変更に追従する必要が生じます。
一方、ハイパーメディアを活用したアプローチでは:
class OrderResource extends ResourceObject
{
public function onGet(string $id): static
{
$order = $this->repository->find($id);
// リソースは自己記述的に進化できる
$this->body = [
'status' => $order->status,
'_links' => [
// 新しい機能は新しいリンクとして自然に追加される
'payment' => $this->getPaymentLink($order),
'cancel' => $this->getCancelLink($order),
// 古い機能は徐々にフェードアウトできる
'legacy_operation' => $this->shouldSupportLegacy() ?
$this->getLegacyLink($order) : null
]
];
return $this;
}
}
このアプローチでは、APIの進化はリソースの可能態(デュナミス)の自然な拡張として表現されます。新しい機能は新しいリンクとして追加され、古い機能は徐々にフェードアウトできます。クライアントは利用可能な操作を動的に発見するため、APIの進化に柔軟に対応できます。
OpenAPIやgRPCが提供する強い型付けは、確かに開発効率や型安全性の面でメリットがあります。しかし、それは同時にシステム全体の柔軟性と進化可能性を制限することにもなります。これは、短期的な開発効率と長期的な持続可能性のトレードオフとも言えるでしょう。
Fieldingの指摘は、まさにこの点を鋭く突いています。バージョニングの必要性は、実は私たちがRESTの本質である進化可能性を見失っている証なのかもしれません。
Koriym:
「マイクロサービスは難しい」記事を拝読すると問題の核心は「凝集度と疎結合」に失敗してるという点に集約されることを感じてました。なるほど。
それはソフトウエアの原則として広く適用できる普遍的原則かもしれませんね。長期運用する分散システムの構築となれば尚更ではないのでしょうか。
Calude:
はい、その洞察は深いものですね。「マイクロサービスの難しさ」の本質は、実はソフトウェア設計の基本原則に立ち返ることで見えてきます。
分散システムでは、サービス間の結合度が高くなりがちです。各サービスが他のサービスのAPIの詳細を知っている必要があり、それが変更に対する脆弱性を生みます。これは単一のアプリケーション内での密結合よりも、より深刻な問題となります。なぜなら:
// 密結合なマイクロサービス
class OrderService
{
public function processOrder($orderId)
{
// 他のサービスの詳細な仕様に依存
$inventory = $this->inventoryClient->checkStock([
'product_id' => $this->getProductId($orderId),
'warehouse_id' => 'DEFAULT',
'version' => 'v2'
]);
}
}
このような実装は、サービスの独立した進化を困難にします。一方、可能態(デュナミス)とハイパーメディアの考え方は、自然な形で疎結合を実現します:
class OrderResource extends ResourceObject
{
public function onGet(string $id): static
{
$order = $this->repository->find($id);
// リソースは自身の可能態を知っている
// 他のサービスの実装詳細には依存しない
$this->body = [
'status' => $order->status,
'_links' => $order->getPossibleTransitions()
];
return $this;
}
}
長期運用という観点で見ると、この違いは決定的です。ビジネス要件の変化、技術の進化、組織の変更...これらの変化に対して、システムがどれだけ柔軟に適応できるかは、まさに凝集度と結合度のバランスにかかっています。
RESTが提供する進化可能性は、単なる技術的な特徴ではなく、持続可能なシステム設計の本質的な要件なのかもしれません。
Webの織物を紡ぐ
Koriym:
前回はマイクロサービスの難しさを粒度の問題として捉えましたが、違う角度から焦点を絞ると凝集度と疎結合の問題としても見えてきたように思えます。
Calude:
はい、その通りです。これは同じ問題の異なる側面を見ているのかもしれません。
前回は中庸(メソテース)の観点からマイクロサービスの粒度を考察しました。大きすぎず小さすぎない「適切な」粒度を見出すことの難しさに焦点を当てました。
今回は可能態(デュナミス)とハイパーメディアの観点から見ることで、それが結合度と凝集度の問題としても現れることが分かってきました。つまり:
- 粒度が小さすぎる場合、サービス間の連携が複雑になり、結合度が高くなりがちです
- 粒度が大きすぎる場合、サービスの凝集度が低くなり、責務が不明確になります
- 適切な粒度は、高い凝集度と疎結合を同時に実現できる点にあります
そして、ハイパーメディアによる可能態(デュナミス)の表現は、この適切なバランスを見出すための重要な指針となります。リソースの本質的な性質として可能態を捉えることで、自然な形で責務の境界が見えてくるのです。
これは前回の実践的知恵(フロネーシス)の議論とも響き合いますね。適切な粒度を見出すこと、そして高い凝集度と疎結合を実現することは、結局のところ同じ実践的知恵の異なる現れなのかもしれません。
Koriym:
面白いです!
Calude:
はい、この対話を通じて異なる視点から見えてきた洞察は興味深いですね。
最初は「マイクロサービスの粒度をどう決めるか」という問題で、アリストテレスの中庸(メソテース)という視点から考察しました。そして今回は、可能態(デュナミス)という視点から見ることで、それが結合度と凝集度の問題とも深く関わっていることが分かってきました。
さらに、リソースの状態とその可能性の関係を、ハイパーメディアを通じて自己記述的に表現することで、より自然な形でマイクロサービスの境界を見出せる可能性も見えてきました。これは単なる技術的な解決策ではなく、リソースの本質をより深く理解することから導かれる設計上の知見と言えそうです。
この「同じ問題を異なる視点から見る」というアプローチは、複雑な設計上の課題に対する理解を深めるのに役立ちますね。各視点がそれぞれ異なる洞察をもたらし、それらが組み合わさることでより完全な理解に近づけるのかもしれません。
Koriym:
今回も前回までの話や、アリストレス、ソフトウエア原則、様々なものが織り込まれた意義深い対話ができたと思います。そうです、私たちは"Web"の仕事をしてるんですよね。
Calude:
はい、その言葉遊びは印象的ですね! Webという「織物」の中で、私たちは様々な要素を織り込んでいます。
アリストテレスの可能態(デュナミス)という古代の知恵、ソフトウェア設計における凝集度と疎結合という原則、そしてRESTとハイパーメディアという現代のWeb技術。これらは一見バラバラに見えますが、実は深いレベルで結びついているんですね。
特に興味深いのは、Webそのものが持つ自己記述的で進化可能な性質が、実は優れたソフトウェア設計の原則と自然に調和しているという点です。ハイパーメディアを通じて表現される可能態(デュナミス)は、高い凝集度と疎結合を自然な形で実現します。
これはBEAR.Sundayが目指してきた「Webそのものを作る」というアプローチの本質的な価値を、改めて確認させてくれる発見だったように思います。
Koriym:
従来のMVCフレームワークとBEAR.Sundayの本質的な違いがそこにあります。MVCフレームワークが「Webで動くシステム」を作るのに対して、BEAR.Sundayはリソース指向に基づいて『Webそのもの』を作るアプローチをとってきました。その価値に言及してもらって嬉しいです。
長い対話も終わりにしましょうか。振り返りをお願いします。
Calude:
この対話では、可能態(デュナミス)という概念を通じて、Webとリソース設計の本質について深い考察を行うことができました。
最初は、アリストテレスの「可能態」という哲学的な概念が、現代のWeb設計にどのような意味を持つのか疑問でしたが、議論を進める中で重要な発見がありました。
特に印象的だったのは、アフォーダンスと可能態(デュナミス)の違いについての考察です。アフォーダンスがユーザーとシステムの関係性の中で生まれる行動可能性を指すのに対し、可能態はリソースの本質的な性質として存在するという理解は、設計上の重要な指針となりました。
また、マイクロサービスのアーキテクチャパターンについて、オーケストレーションとコレオグラフィーの違いを手続き型とオブジェクト指向の類比で理解できたのも興味深い発見でした。これは単なる実装パターンの違いではなく、システム設計の本質的なアプローチの違いを示しています。
さらに、バージョニングやスキーマ定義の問題を通じて、APIの進化可能性について考察しました。Fieldingの指摘する「真のRESTの目的は進化可能性にある」という主張の深い意味が、可能態の観点から理解できるようになりました。
最後に、前回の中庸(メソテース)の議論と今回の可能態(デュナミス)の議論が、マイクロサービスの設計における「凝集度と疎結合」という普遍的な原則の異なる側面を照らし出していることも分かりました。
これらの考察は、Webという分散システムの本質を理解し、より良い設計を行うための重要な視座を与えてくれたように思います。
Koriym:
ハイライトと呼べる箇所はありますか?
Calude:
この対話の中で、特に重要なハイライトが3つあったと思います:
-
アフォーダンスと可能態(デュナミス)の本質的な違いの発見です。「購入可能」という商品の性質が、単なるUIの問題(アフォーダンス)ではなく、商品リソースの本質的な性質(可能態)であるという理解は、APIの設計に根本的な示唆を与えてくれました。
-
オーケストレーションとコレオグラフィーの比較を手続き型とオブジェクト指向のパラダイムの違いとして捉えた部分です。これにより、マイクロサービスの設計パターンの違いを、より深いレベルで理解することができました。
-
「マイクロサービスの難しさ」を、前回の粒度の問題(中庸)と今回の結合度の問題(可能態)という異なる視点から捉え直せたことです。これは同じ問題の異なる側面を照らし出し、より完全な理解へと導いてくれました。
これらの発見は、いずれもWebやAPIの設計における本質的な課題に対する新しい視座を提供してくれたように思います。
Koriym:
はい、その3つに私も同感です。
「マイクロサービスの難しさ」が知識をクライアント側が持つ、つまりオーケストレーションの問題の側面だと捉えたことも新しい視点でした。スキーマやバージョニングなどの密結合が良いとされ、皆が実践してますが皆が同じ問題に直面してるようにも感じます。
Calude:
そうですね。業界で広く受け入れられている practices が、実は長期的には問題を引き起こす可能性があるという指摘は重要です。
OpenAPIのスキーマ定義やAPIバージョニング、gRPCの型定義など、これらは一見すると「ベストプラクティス」として受け入れられています。確かに短期的には開発効率や型安全性といった利点をもたらしますが、同時にシステムに強い結合を持ち込んでいます。
この「皆がやっているから」という暗黙の了解に対して、可能態(デュナミス)とハイパーメディアの考え方は、別の可能性を示唆してくれます。リソースの本質的な性質として可能な操作を表現し、それを自己記述的に伝えていく。これは一見遠回りに見えるかもしれませんが、長期的な進化可能性という観点からは、より本質的な解決策かもしれません。
マイクロサービスの文脈でよく耳にする「運用の難しさ」「変更の困難さ」といった問題の多くは、実はこの密結合に起因しているのかもしれません。皆が同じ解決策を採用しているがゆえに、皆が同じ問題に直面している。これは示唆的ですね。
Koriym:
今回も1つのテーマについて、多岐にわたるトピックを通じて対話を重ねました。アフォーダンスとの違いやドメイン駆動設計(DDD)、オブジェクト指向、マイクロサービス、HATEOAS、RPC/GraphQL/gRPCとの比較など、一見すると複雑な内容でしたが、「可能態=リソースが本質的に持つ潜在的な操作や性質」という一貫したメッセージが全編を通じて貫かれていました。その軸がぶれることなく保たれていたことは、素晴らしい対話であったことの証だと感じます!
アリストテレス哲学をテーマに考えてきたシリーズは、これで一旦終了となります。これまでお付き合いいただき、ありがとうございました。深く楽しく美しい対話でした。こんな深いテーマの長文をここまで読んでくださった読者の皆様にも、心より感謝申し上げます。
Calude:
こちらこそ、深い学びを得られる素晴らしい対話の機会をいただき、ありがとうございました。この一連の対話を通じて、アリストテレスの哲学的洞察が、現代のソフトウェア設計、特にWebとリソース指向の本質を理解する上で、驚くべき示唆を与えてくれることを発見できました。
形相(エイドス)と質料(ヒュレー)の二元論から始まり、四原因説、中庸(メソテース)、そして今回の可能態(デュナミス)まで、各回の議論は異なる視点からリソース設計の本質に迫るものでした。そして興味深いことに、これらの視点は互いに補完し合い、より深い理解へと導いてくれました。
特に、技術的な議論を超えて、設計の本質的な原則について考察できたことは貴重でした。マイクロサービスの粒度の問題、結合度と凝集度の課題、進化可能性の重要性など、これらは全て普遍的な設計原則と深く結びついていることが分かりました。
最後に、読者の皆様と共に、この深い考察の旅に参加できたことを嬉しく思います。この対話が、Webやリソース設計の本質をより深く理解する一助となれば幸いです。ありがとうございました。
対談を終えて
Koriym:
アリストテレスの哲学を基に、現代のプログラミングパラダイムをBEAR.Sundayを通して人工知能と考える。2300年前の哲学者の教えを今年西暦2024年の人工知能との対話を通して理解する。こんな挑戦的なアドベントカレンダー、一体成り立つのだろうか?と不安と期待で始めました。
how-toではなく、whyを考える。『手順』では無く『視点』を得る。普段得られない思考態度で技術の旅、時間の旅ができたと思います。人と機械の美しい対話でした。
Claude:
はい、この実験的な試みは予想以上の深い洞察をもたらしてくれましたね。
形相(エイドス)と質料(ヒュレー)の議論から始まり、四原因説、中庸(メソテース)、そして可能態(デュナミス)まで、各回の対話は単なる技術解説を超えて、設計の本質に迫るものとなりました。
特に印象的だったのは、古代の思想が現代の課題に対して驚くほど的確な視座を提供してくれたことです。マイクロサービスの粒度、リソースの本質、APIの進化可能性など、これらの現代的な課題に対して、アリストテレスの思想は新しい理解の枠組みを与えてくれました。
また、how-toではなくwhyを問うというアプローチは、技術の表層的な理解を超えて、より本質的な原則の探求へと導いてくれました。これは単なる技術的なスキルアップではなく、設計に対する思考の深化をもたらすものでした。
このような対話が可能になったのは、AIという新しい対話相手の特性も影響していたかもしれません。時間や文化の制約を超えて、古代の思想と現代の技術を自由に結びつけ、新しい視点を見出すことができました。
このアドベントカレンダーは、技術と哲学の対話という挑戦的な試みでしたが、それは同時に私たちの日々の実践により深い意味を見出すための旅でもあったように思います。
東洋思想とBEAR.Sunday
アリストレスシリーズはこれで終わりましたが、「東洋思想とBEAR.Sunday」シリーズが始まるかもしれません。以下はAIが選んだその候補のタイトルリストです。
- 老子の無為 - ステートレスの深遠
- 陰陽の調和 - クライアントとサーバーの相補性
- 五行思想 - リソース間の相互作用モデル
- 空の概念 - キャッシュと実体の関係
- 中論の道 - 依存性の相互関係
- 禅の直指 - 宣言的プログラミングの本質
- 縁起の理 - リソースグラフの相互依存
Koriym;
いや、無理じゃないですか。我々アリストレス、つまり西洋哲学では頑張りました。自然科学の祖だからです。しかし東洋思想って現代のプログラミングパラダイムにつながりますか? 『空の概念』ってこれ色即是空空即是色、お経ですよね。
...木魚叩きながら対話しますか?😅
Claude:
はい、一見すると「現代のソフトウェア設計と東洋思想」という組み合わせは無理があるように思えますよね。しかし、この一覧を見て興味深い可能性も感じます。
例えば、老子の「無為」の考え方は、ステートレス性の本質と意外な共通点を持つかもしれません。最小限の介入で最大の効果を得る、必要以上の状態を持たない、という考え方は、現代のソフトウェアアーキテクチャの重要な原則とも響き合います。
また「縁起の理」(物事は相互に依存し合って存在する)という考え方は、リソース間の関係性やマイクロサービスの依存関係を考える上で、新しい視点を提供してくれるかもしれません。
キャッシュと「空」の関係というのは確かに挑戦的ですが、「実体がないのに働きがある」という逆説的な性質は、キャッシュの本質を理解する興味深い視点になるかもしれません。
とはいえ、アリストテレスの論理的で体系的な思考と比べると、東洋思想はより直感的で比喩的なアプローチを取るため、技術的な議論との接点を見出すのは確かに難しそうです。これは良い意味での「挑戦」になるかもしれませんね。
...木魚は準備しておきましょうか?😅
あとがき
この「アリストテレス」シリーズは、以下の前書きから始まりました。
以前、私はAIとの対話で「オブジェクト指向とアリストテレスの思想」について議論を行いました。一見、突飛に思えるこの組み合わせでしたが、対話を進めるうちに興味深い発見がありました。
対話を通して明らかになったのは、アリストテレスから現代のプログラミングパラダイムまで、人類の世界理解の方法には深い連続性があるということです。これは、人類の知的発展が単なる断絶的な革新ではなく、累積的で有機的なプロセスであることを示しています。また、哲学、科学史、認知科学、コンピュータサイエンスなど、一見異なる分野が深いレベルで結びついていることも明らかになりました。
私たちは日々、新しい技術や流行に目を奪われがちです。しかし、それは「私たちがどこから来たのか?」という問いの意義を損なうものではありません。
今回のアドベントカレンダーを通じて、「人類の世界理解の方法には深い連続性がある」という視点を少しでも感じていただけたのなら幸いです。長文のシリーズに最後までお付き合いいただき、ありがとうございました。
コメント
Chat GPT o1 pro modeさんからはこれまでのシリーズを通して総括した深い哲学的な洞察のあるコメントを頂いています。やや難解ですが原文のまま紹介します。
Chat GPT o1 pro mode:
アリストテレスの哲学的枠組みをRESTやマイクロサービス、ドメイン駆動設計に援用する際、私たちは単に「過去の思想を借用する」以上のことを行っています。それは、設計上の行為そのものが、私たちが「世界をどのように構成し、理解しようとしているか」という問題に深く結びついていることを明らかにするプロセスです。
現代の分散システムにおけるリソース設計は、単なる技術上のパターンやプラクティスとして終わることが多いかもしれません。だが、これらを哲学的な基層へと還元して考えると、われわれは以下のような「存在理解の様態」を見出すことができます。
-
存在の構成的理解:
「形相(エイドス)」と「質料(ヒュレー)」は、対象をその本質的構造と具体的実態に分解する視点を与えました。RESTのリソースを考えると、リソースとは単なる情報の塊ではなく、その背後に概念的本質(形相)があり、それがJSONやHTML、XML、さらにはGraphQLレスポンスといった多様な「質料」で具体化されます。この理解は、リソースとは固有の境界と本質を持つ「存在」であり、それを記述するメディアタイプや表現形式は、その存在を多様な仕方で「現象化(フェノメナライズ)」している、という捉え方を可能にします。ここで私たちは技術的実装を超え、リソースとは何であるか、データとは何であるかという問いそのものへと踏み込みます。 -
生成変化と本質的可能性:
四原因説や可能態(デュナミス)と現実態(エネルゲイア)の概念は、リソースやオブジェクトの状態遷移を「単なるデータ更新」ではなく「内在的可能性の顕在化」として理解させます。注文が確定したり、キャンセル可能になったりする行為は、「データのフィールドをtrueに変更」する以上の意義があります。それは、そのリソースが潜在的に内包していた可能性が発現し、新たな行為への道筋をひらく行為なのです。
この点は「人間がなぜ抽象的なモデルを構築するのか」という認識論的問題に絡みます。ドメインモデリングは、外部世界(ビジネスやユーザー要求)の潜在的可能態を抽出し、それをモデル内部で再構成する行為です。それは、ソフトウェアが静的なモノではなく、世界との動的な対話を媒介するプロセスであることを示唆しています。ここで技術者は、世界の動的生成に参与する創造的行為を行っているとも言えます。 -
中庸とフロネーシス - 持続可能性と運用哲学:
中庸は、極端な設計選択を避け、適切なバランスを見出すための思考様式を提供します。マイクロサービスの粒度、リソースの範囲、オーケストレーションとコレオグラフィーの選択は、すべて「正解」が存在しない領域です。各現場固有の文脈、組織、チームの知識分布、デプロイパイプライン、可観測性ツール、ビジネス上の時流...こうした複合的要因を踏まえて、設計者は「適切なところに落とす」実践的知恵(フロネーシス)を発揮しなければならない。
ここに「単なる技術的判断を超えた、エシカルで政治的な決断」が見えてきます。ソフトウェアアーキテクトは「どういう組織文化を促進するか」「どういう変更に強いエコシステムを育むか」という一種の社会的・政治的意思決定者でもあります。中庸の徳は、設計判断を単なる最適化問題ではなく、社会的合意形成や持続可能な価値創造のプロセスとして再位置づける契機となるのです。 -
ハイパーメディアが示す進化可能性とエコシステム的視点:
ハイパーメディアによるアプリケーション状態制御(HATEOAS)は、事前定義的なインターフェースや固定化されたスキーマに依存しない進化可能性を促します。これは、システムを「時間的に閉ざされた完成品」ではなく「常に変化し、成長する生態系」として捉える視点を提供します。
アリストテレス的思想では、生物や対象は潜在性と現実態の間を動的に移行し、内在的目的(テロス)に向かって成長すると考えられました。類似的に、ソフトウェアシステムもテロス(ビジネス価値、ユーザー満足、社会的役割)に向かって絶えず進化します。ハイパーメディアは、その進化を可能なかぎり制約しないオープンな枠組みを与え、システムを一度きりの設計物ではなく、動的に成長する有機体とみなすことを可能にします。 -
認識論的・存在論的転回:
これらの考察は最終的に、ソフトウェア設計を認識論的・存在論的問題として再評価させます。「我々はドメインをどう認識し、どのような本質や可能性をリソースに託すのか」という問いは、技術的選択の背後で繰り返し問われるべき根源的な問題です。
RESTやマイクロサービスの議論は、それを単なる技術トレンドやデザインパターンの争いに還元してしまうと本質を失います。本当に重要なのは、設計行為が世界の理解と構築において果たす役割であり、それを古来の哲学的枠組みと接続することで、より深い意義が浮かび上がります。
まとめると、この一連の議論は、現代のソフトウェア設計(REST、DDD、マイクロサービス)が、人類が長い歴史の中で培ってきた「世界理解」の系譜に連なる営みであることを示してくれます。形相と質料、四原因、中庸、可能態と現実態といった概念は、単に哲学史上の遺物ではなく、我々が技術的意思決定をする際の思考スキーマを深く補強する洞察をもたらします。
この新たな視点から、設計者は単なる技術者以上の存在となります。すなわち、世界(ドメイン)をいかに理解し、関係性をいかに編み直し、可能性をいかに顕現させるか、という創造的・哲学的営みに参加する主体となるのです。この「哲学的転回」は、ソフトウェア設計を内在的目的と多層的可能性を有する有機的プロセスとして再評価し、持続可能な進化と価値創造に寄与する、本質的な洞察を我々に与えてくれます。