モノリスからマイクロサービスへ
分割する対象とサービス境界の引き方を判断します。
「モノリスからマイクロサービスへ」はCoddyKit上の無料PHP Academyレッスンです。 これはレッスン1/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはPHP Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 PHP Academyコースには全4レッスンが含まれています。
モノリスからマイクロサービスへ
マイクロサービスは、標準的に選ぶものではなく、組織と運用に関する大きな選択です。PHPモノリスを分割すると、プロセス内の単純さと引き換えに、ネットワーク呼び出し、分散データ、デプロイの複雑さを抱えることになります。間違った理由で行えば、すべてが遅くなります。
このレッスンでは、最も難しい部分であるサービス境界の設計を扱います。境界を正しく設計できれば、残りは配管作業です。間違えると、分散モノリス、つまりコストばかりがかかり、メリットのない構成を作ることになります。
分割する理由(分割しない理由)
分割する正当な理由は次のとおりです。
- 独立したデプロイとチームによる所有。
- 負荷の高いサブシステムを独立してスケーリングできること。
- テクノロジーやランタイムの分離と、障害の封じ込め。
悪い理由は、「コードが乱雑だから」(まずモノリスをリファクタリングしてください)や、流行を追いかけることです。2つのサービスを常に一緒にデプロイする必要がある、または同じデータベーステーブルを共有しているなら、それは二つの顔を持つ1つのサービスです。
境界づけられたコンテキスト
ドメイン駆動設計では、境界を定義するための最も明確な手段として境界づけられたコンテキストを用います。1つのコンテキスト内では、用語に1つの明確な意味を持たせます。「顧客」という言葉も、請求では請求書の宛先を意味し、サポートではチケットの作成者を意味します。
各境界づけられたコンテキストは、サービスの有力な候補です。境界は業務上の言葉と、組織が実際にどのようにコミュニケーションを取っているかに沿って定めます。これはConwayの法則の実例です。
高凝集・低結合
適切なサービス境界では、一緒に変更されるものを内部にまとめ、独立して変更されるものを外部に出します。分割案は、次の点で評価します。
- 2つのサービスを同時に変更する必要がある機能はどれくらいありますか(少ないほどよいでしょう)。
- サービス間の呼び出しはどれほど細かく頻繁ですか(粗粒度であるべきです)。
1つの機能を実装するたびに境界をまたぐ必要があるなら、その境界の位置は間違っています。
<?php
// Chatty boundary smell: N network calls to render one view
foreach ($order->lineItems as $item) {
$product = $catalogApi->get($item->productId); // one call PER item!
$names[] = $product['name'];
}
// Coarse-grained: one batch call across the boundary
$ids = array_map(fn($i) => $i->productId, $order->lineItems);
$names = $catalogApi->getMany($ids); // single round-trip
echo count($names) . " products in one call\n";サービスごとのデータベース
絶対に譲れないルールは、各サービスが自分のデータを所有し、他のサービスはそのテーブルに触れないことです。共有データベースを使うと、分割によって解消しようとした結合が再び生まれ、スキーマの変更によって無関係なサービスまで壊れてしまいます。
つまり、モノリスでSQL JOINだったサービス間クエリは、API呼び出しまたは複製された読み取りモデルに置き換わります。これが分割の代償であり、分割する目的でもあります。
<?php
// In the monolith: one JOIN across domains
$sql = 'SELECT o.id, c.email
FROM orders o JOIN customers c ON c.id = o.customer_id';
// After split: Orders service holds only the foreign id;
// it asks the Customers service for the rest (or keeps a local read model).
$order = $orderRepo->find($id); // local
$customer = $customersApi->get($order->customerId); // network call
echo $order->id . ' / ' . $customer->email . "\n";ストラングラーフィグ・パターン
モノリスを一括で書き換えてはいけません。ストラングラーフィグ・パターンでは、段階的に切り出します。ファサードやプロキシを通じてトラフィックの一部を新しいサービスへルーティングし、機能を成長させ、新しいサービスが完全に置き換えられるようになった時点でのみ、古いコードパスを廃止します。
リバースプロキシ(またはAPIゲートウェイ)を手前に配置し、ルートごとにレガシーモノリスと新しいサービスのどちらへ接続するかを決定します。移行は機能単位で進め、常にリリース可能な状態を保ちます。
<?php
// Facade routing: peel off one capability at a time
function route(string $path): string {
$migrated = ['/invoices', '/invoices/pdf']; // moved to billing-svc
foreach ($migrated as $prefix) {
if (str_starts_with($path, $prefix)) {
return 'http://billing-svc' . $path;
}
}
return 'http://legacy-monolith' . $path; // everything else, for now
}
echo route('/invoices/pdf'), "\n";
echo route('/users/42'), "\n";モジュールの切り出し
実践的な切り出しの順序は次のとおりです。
- 依存元が少なく、データの所有権が明確なモジュールを見つけます。
- まずモノリス内で、そのモジュールへのプロセス内呼び出しをインターフェースの背後に隠します。
- そのモジュールが所有するデータを専用のスキーマまたはDBへ移します。
- インターフェースの実装をネットワーククライアントに置き換えます。
- ファサード経由でトラフィックを切り替え、古いコードを削除します。
まずモノリス内でインターフェースによるラップを行うことで、ネットワーク化のリスクを下げられます。
<?php
// Step 2: hide the implementation behind a port the monolith calls
interface InvoiceService {
public function generate(string $orderId): string; // returns invoice id
}
// Today: local class. Tomorrow: HTTP client to billing-svc.
// The monolith's calling code never changes.
final class LocalInvoiceService implements InvoiceService {
public function generate(string $orderId): string { return 'inv-1'; }
}分散データの整合性
データを分割すると、サービスをまたぐACIDトランザクションは使えなくなります。結果整合性を受け入れ、各サービスが自分のデータに関するイベントを発行し、他のサービスがそのイベントからローカルの読み取りモデルを構築するようにします。
OrdersサービスはリクエストのたびにCustomersサービスへ問い合わせるのではなく、必要なフィールドだけを含む小さなプロジェクションを保持し、CustomerUpdatedイベントによって更新します。これにより、実行時の依存関係とレイテンシの発生源を1つ減らせます。
<?php
// Orders service keeps a tiny local projection of customer data
function onCustomerUpdated(PDO $db, array $evt): void {
$db->prepare(
'INSERT INTO customer_read_model (id, email)
VALUES (:id, :email)
ON CONFLICT (id) DO UPDATE SET email = EXCLUDED.email'
)->execute(['id' => $evt['id'], 'email' => $evt['email']]);
}運用の現実
マイクロサービスでは、複雑さがコードから運用へ移ります。分割する前に、次のものが必要です。
- 集中ログ管理と分散トレーシング(呼び出しをまたぐ相関ID)。
- サービスごとのCI/CDと、独立してバージョン管理されたデプロイ。
- すべての呼び出しに対するヘルスチェック、タイムアウト、リトライ、サーキットブレーカー。
- プロバイダーがコンシューマーをひそかに壊さないためのコントラクトテスト。
チームが1つのサービスを適切に運用できないなら、10個に増やすと事態は10倍悪化します。
サービスの適切なサイズ設定
「マイクロ」という言葉は誤解を招きます。コード行数ではなく、業務上の能力とチームの所有権に基づいてサービスのサイズを決めてください。細かく分けすぎる(ナノサービス)と、1つの機能で大量のネットワーク呼び出しが発生します。粗すぎると、モノリスに逆戻りです。
健全な目安は、1つのチームが責任を持って管理でき、単独でデプロイでき、多数の他サービスを同期的に連鎖させなくても中核ユースケースを実現できることです。
モジュラーモノリスという選択肢
分散化する前に、モジュラーモノリスを検討してください。デプロイ単位は1つですが、内部は明示的な境界とそれぞれのスキーマを持つモジュールに分割され、公開インターフェースを通じてのみ通信します。モジュール間でテーブルに直接アクセスすることはありません。
分散システムのコストを負担せずに、明確な境界と容易なリファクタリングを実現できます。モジュールに本当に独立したスケーリングや所有権が必要になったときには、ストラングラーフィグ・パターンで切り出せる形がすでに整っています。多くのチームにとって、まず選ぶべきなのはこの方法です。
<?php
// Modules talk only through interfaces, never each other's tables.
namespace App\Billing; // owns billing_* tables
interface BillingFacade {
public function invoiceForOrder(string $orderId): string;
}
namespace App\Sales; // owns sales_* tables
final class Checkout {
public function __construct(private \App\Billing\BillingFacade $billing) {}
// Sales never SELECTs from billing_* directly - only via the facade.
}クイックチェック
悪い分割を見分けます。
まとめ
サービス境界を適切に設計するための要点は次のとおりです。
- コードが乱雑だからではなく、デプロイ、スケーリング、所有権を独立させるために分割します。
- 境界を境界づけられたコンテキストに合わせ、高凝集・低結合を目指します。
- サービスごとのデータベースとし、共有テーブルを置かず、JOINをAPIまたは読み取りモデルに置き換えます。
- ストラングラーフィグ・パターンで段階的に移行します。
- 結果整合性を受け入れ、スケールアウトする前に運用へ投資します。
次は、これらのサービスが実際にどのように通信するか、RESTとgRPCについて学びます。
よくある質問
「モノリスからマイクロサービスへ」レッスンは無料ですか?
はい。「モノリスからマイクロサービスへ」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、PHP Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 PHP Academyコースには全4レッスンが含まれています。
「モノリスからマイクロサービスへ」で何を学びますか?
分割する対象とサービス境界の引き方を判断します。 ブラウザで直接実行するハンズオンコードでPHP Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
PHP Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのPHP Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン1/4です。
「モノリスからマイクロサービスへ」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このPHP Academyレッスンでコードを書いて実行できますか?
はい。すべてのPHP Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- モノリスからマイクロサービスへ
- サービス間通信:RESTとgRPC
- APIゲートウェイとサービスディスカバリ
- レジリエンス:サーキットブレーカーとリトライ