te15

PHP のイベント駆動型サーバー

(jp) =

最近、私は PHP アプリケーション用のユニークな種類のアーキテクチャーをいじっています。 現実の問題がすぐに解決するとは思わないことを前もって伝えたいと思います。 それでも私はあなたを思考過程に巻き込みたいと思っています。 どんな素晴らしいアイデアが生まれるか誰にもわかりません。

この投稿では、アーキテクチャを順を追って説明し、その利点と欠点 (少なくとも、現時点で思いつくもの) について説明します。 私はオープン ソース化された概念実証コードベースを持っており、この記事全体でそこからの洞察を共有します。

では、まず最初に、アーキテクチャとは何かについて説明します。 これは長時間実行される PHP サーバーであり、状態全体がメモリにロードされ、保存されたイベントから構築されます。 言い換えれば、これは PHP で知られているイベント ソーシングですが、すべての集計とプロジェクションはメモリ内に読み込まれ、ディスクに保存されることはありません。

分解してみましょう!

# 長期稼働している PHP サーバー

このアーキテクチャの最初の柱は、長時間稼働するサーバーです。 現代の PHP ランドスケープは、これらの種類のプロセスを管理するためのいくつかの実績のあるソリューションを提供しています。速い要求/応答サイクル。

もちろん、この高速な要求/応答サイクルは、PHP を優れたものにした理由の 1 つです。状態のリークやすべての同期の維持について心配する必要はまったくありませんでした。要求が来ると、クリーンな PHP プロセスが開始され、アプリケーションが次の場所から起動します。 0. 応答が送信された後、アプリケーションは完全に破棄されます。

私は、この実戦で実証済みの手法を完全に捨てることを提案しているわけではありません。 高速な要求/応答サイクルは、これから説明するアーキテクチャの重要な部分です。 一方、常にアプリケーション全体をゼロから起動することには、欠点があります。

私が説明しているアーキテクチャでは、アプリケーションは 2 つの部分に分割されています。一方の部分は、HTTP 要求を受け入れて応答を生成する通常の PHP アプリであり、もう一方の部分は、常に実行されている舞台裏のバックエンド サーバーです。 アプリケーション全体の状態が常にメモリにロードされているサーバー。これにより、クライアント (通常の PHP アプリ) がサーバーに接続し、データを読み取り、イベントを保存できます。

アプリケーション全体の状態が常にメモリにロードされるため、データベース クエリを実行したり、データベースからオブジェクトへのデータのマッピングにリソースを費やしたり、ORM エンティティ間の循環参照などのパフォーマンスの問題を実行したりする必要はありません。

これは理論的にはいいように思えますが、複雑なクエリを実行できるようにする必要がある可能性があります。これは、データベースが高度に最適化されているためです。 このアーキテクチャーでは、通常の PHP アプリケーションで慣れ親しんだ特定の側面を再考する必要があることは明らかです。 これについては後で説明します。

まず、2 番目の柱であるイベント ソーシングを見てみましょう。

tpyoに気づきましたか? PR を送信して修正することができます。 このブログの最新情報を知りたい場合は、私をフォローしてください。 ツイッター または私のニュースレターを購読してください:

# イベントソーシング

イベント ソーシングをこのアーキテクチャのコアの一部にすることを提案する理由は何ですか? 通常のデータベースからすべてのデータがメモリ内にロードされた、長時間稼働するサーバーを持つことができます。

クライアントが更新を実行し、それをバックエンド サーバーに送信するとします。 サーバーは、データをデータベースに格納するだけでなく、メモリ内の状態を更新する必要があります。 このようなシステムは、更新後にすべてが正しくなるように、アプリケーションの状態を適切に更新する必要があります。

最も素朴なアプローチは、データベースで更新を実行し、アプリケーションの状態全体をリロードすることですが、パフォーマンスの問題により実際には不可能です。 もう 1 つのアプローチは、更新を受信したときに発生する必要があるすべてのことを追跡することです。これを行う最も柔軟な方法は、イベントを使用することです。

メモリ内の状態の同期を維持するために自然にイベント駆動型システムに傾倒している場合、データベースにすべてを格納するオーバーヘッドを追加し、ORM でデータをオブジェクトにマップし直す必要があるのはなぜでしょうか? イベント ソーシングが優れたアプローチである理由はここにあります。イベント ソーシングは、すべての状態同期の問題を自動的に解決し、データベースと通信して ORM を操作する必要がないため、パフォーマンスが向上します。

では、複雑なクエリについてはどうでしょうか。 たとえば、すべてがメモリに読み込まれているときに、何百万ものアイテムを含む製品ストアを検索するにはどうすればよいでしょうか。 PHP は、これらの種類のタスクに特に優れているわけではありません。 しかし、ここでも、イベント ソーシングは解決策を提供します: プロジェクションです。 特定のタスクに対して最適化された投影を作成し、それをデータベースに保存することもできます! これは、軽量のインメモリ SQLite データベース、または本格的な MySQL または PostgreSQL サーバーである可能性があります。

最も重要なことは、これらのデータベースがアプリケーション コアの一部ではなくなったことです。 それらはもはや真実の情報源ではなく、アプリケーションのコアの端にある有用なツールであり、ElasticSearch や Algolia のような最適化された検索インデックスの構築に非常に匹敵します。 これらのデータ ソースはいつでも破棄でき、保存されたイベントから再構築できます。

これが、イベント ソーシングがこのアーキテクチャに非常に適している最後の理由です。 サーバーのクラッシュやデプロイ後にサーバーの再起動が必要な場合、イベント ソーシングを使用すると、アプリケーションの状態をはるかに高速に再構築できます: スナップショットです。

このアーキテクチャでは、アプリケーション全体の状態のスナップショットが 1 日に 1 回か 2 回保存されます。 これは、すべてのイベントを再生する必要なく、サーバーを再構築できるポイントです。

ご覧のとおり、このアーキテクチャ内でイベント ソース システムを構築することには、いくつかの利点があります。 次に、最後の柱であるクライアントに進みます。

# クライアント

これについては前に述べましたが、「クライアント」とは、中央のバックエンド サーバーと通信するサーバー側の PHP アプリケーションを意味します。 それらは通常の PHP アプリケーションであり、通常の要求/応答サイクル内で短時間しか動作しません。

直接通信する代わりにイベントサーバーを使用する方法がある限り、これらのクライアントに必要な既存のフレームワークを使用できます。 データベース。 Symfony の Doctrine や Laravel の Eloquent のような ORM を使用する代わりに、小さな通信レイヤーを使用して、ソケット経由でバックエンド サーバーと通信します。

また、バックエンド サーバーとクライアントが同じコードベースを共有できることにも注意してください。つまり、開発者の観点からは、クライアントとサーバー間の通信について心配する必要はなく、透過的に行われます。

残高のある銀行口座の例を見てみましょう。 このアーキテクチャでは、次のようなコードを記述します。

final class AccountsController

    public function index(): View
    
        $accounts = Account::all();

        return new View('accounts.index', [
            'accounts' => $accounts,
        ]);
    

私は主に Laravel コンテキストで作業しており、Eloquent ORM には慣れていることを覚えておいてください。 リポジトリ パターンを使用したい場合は、それも問題ありません。

舞台裏では、 Account::all() また $accountRepository->all() はデータベース クエリを実行せず、バックエンド サーバーに小さなメッセージを送信します。バックエンド サーバーはアカウントをメモリからクライアントに送り返します。

アカウントの残高を変更する場合は、次のように行います。

final class BalanceController

    public function increase(Account $account, int $amount): Redirect
    
        $aggregateRoot = AccountAggregateRoot::find($account);
   
        $aggregateRoot->increaseBalance($amount);

        return new Redirect(
            [AccountsController::class, 'index'], 
            [$account]
        );
    

舞台裏では、 AccountAggregateRoot::increaseBalance() イベントをサーバーに送信し、サーバーはそれを保存して、関連するすべてのサブスクライバーに通知します。

そのような実装が何であるか疑問に思っているなら AccountAggregateRoot のように見えるかもしれませんが、簡略化されたバージョンは次のとおりです。

final class AccountAggregateRootRoot extends AggregateRoot

    public function increaseBalance(int $amount): self
    
        $this->event(new BalanceIncreased($amount));

        return $this;
    

そして最後にこれが Account エンティティは次のようになります。 ORM スタイルの構成がないことに注意してください。 これらは単純なメモリ内 PHP オブジェクトです。

final class Account extends Entity

    public string $uuid;

    public string $name;

    public int $balance = 0;

最後に、PHP の高速な要求/応答サイクルが実際に重要であると述べたことを思い出してください。 その理由は次のとおりです。更新をサーバーに送信している場合、それらの更新をクライアントにブロードキャストすることを心配する必要はありません。 通常、すべてのクライアントは 1 秒か 2 秒しか存在しないため、同期を維持することについて心配する必要はほとんどありません。

#デメリット

これらはすべて、理論的には興味深いように思えますが、実際にはどうでしょうか? パフォーマンスはどうですか? すべてをメモリに保存するには、どれくらいの RAM が必要ですか? 複雑なクエリを実行することで、状態の読み取りを最適化できるでしょうか? スナップショットはどのように保存されますか? バージョン管理についてはどうですか?

多くの質問はまだ答えられていません。 この投稿の目的は、すべての回答を提供することではなく、コミュニティの皆さんと考えや質問を共有することです。 あなたが何を思い付くことができるか誰が知っていますか?

このコードはオープンソースであると言いましたが、ここで見ることができます。 Reddit でフィードバックをお待ちしております。 ツイッター または電子メール。

次の投稿
カリフォルニアで最も古い 5 本の木
前の投稿
ティム アレンがサンタ スーツに戻る理由と、引退が彼の語彙にない理由について

ノート:

AZ: 動物の世界、ペット、ペット、野生の自然に関するカテゴリー記事…
SP:スポーツカテゴリー。
New vs Ne: ニュースコラム。
Te: テクノロジー カテゴリ。
Gt:エンターテインメントカテゴリー。
Bt: 占い、星占い、超常現象、超常現象。
Ta:人生コラム。