ノードにおけるイベント駆動型プログラミングの使用法js


執筆Vlado Tesanovic ✏️
今日のソフトウェアを構築するための最も使用されるアプローチは / 層を通して垂直に伝播する層の階層構造(n層)の応答メカニズムパターンMVC , 非常に人気のある、として、方法では、標準的な人々がソフトウェアを学び、書くとき.
階層化されたアーキテクチャは最も簡単なものであり、多くの問題を解決することができるので、それはソフトウェア産業に存在するすべての問題を解決するための銀の弾丸であることを意味しません.ソフトウェアのいくつかは、異なるデザインパターンを使用してより表現的に記述することができます.階層化されたアーキテクチャは、中小規模のプロジェクトとうまくいきます.トリッキーな部分は、すべてを組織して、あまりにも多くの層を作らないようにすることですBaklava code .

階層構造


また、我々はイベント駆動型プログラミングを主にフロントエンドの開発に使用しています.そこでは、1つのイベントがシステムを通して伝播され、多くの俳優がそのイベントをキャッチすることができます.データフローは一方向であり、既存のコンポーネントを編集せずに新しい機能を追加できます.
ユーザインタフェースを構築するためにイベント駆動型プログラミングが支配的であるが、サーバ側のコードも書くことができる.良いユースケースは、サーバーからの即時の応答を必要としないで、要求の進行を発表するために異なる通信チャンネルを使用する非常に非同期のシステムです.

デモ


このチュートリアルでは、イベント駆動型プログラミングを実装するだけでなく、イベントをディスパッチしますCQRS データ(クエリ)を読み取るために使用されるデータ(コマンド)を編集するコードを分割するデザインパターン.
アプリケーションの主なブロックは以下の通りです.
  • コマンド
  • ハンドラ
  • イベント
  • クエリ
  • コマンドは、ビジネスロジックを実行したり、新しいイベントをディスパッチするアクションです.イベントは他のコマンドのディスパッチに使われます.イベントハンドラもあります.クエリアクションとクエリハンドラは、アイテムの問い合わせに責任があります.
    つのアクションが定義された順序で他のアクションをトリガーすることができます入札システムを想像し、我々は高度に非同期にしたい.以下のような機能があります.
  • 入札が一番高いか確認する
  • すべての利害関係者(入札者と所有者)にメールを送る
  • データベースに入札を追加する
  • その入札のための活動を作成する
  • 入札開始後2時間入札を延長するBidding Fee Auction )
  • ここでは、システムのフローを示します.

    CQRSモジュールを実装すると、各イベントは1つ以上のコマンドを生成し、各コマンドは新しいイベントをトリガーします.
    このイベント駆動システムはaspect-oriented programming パラダイム基本的には既存の機能を変更せずにソフトウェアに追加機能を追加できることを意味します.この場合、新しいコマンドとコマンドハンドラをイベントで連鎖することを意味します.

    実装


    選んだNestjs 我々の架空の入札システムのために記述された解決を実装するために.
    Nestjs 提供は、その豊かな生態系、CQRSモジュールです.そのモジュールの主なビルディングブロックは、3つの注入可能なクラスです.名前は、イベント、クエリ、またはコマンドのいずれかをトリガーすることができます.
    このデモのためのコードを読んだり書いたりするコードは、学習する必要があります.NESTJSは、機能豊富なフレームワークですdecorators , observables , そして、それはモジュールシステム(角からのものと同様の)とともに来ます.dependency injection , inversion of control など
    コードから重要なビットだけを強調しようとします.さもなければ、この記事は長すぎます.それの下で、あなたはAへのリンクを見つけますGithub repository すべてのコードと作業デモで.以下にディレクトリ構造を示します:

    主なコントローラ(および主なルート/)から、我々はbideventを派遣します.NESTJSでは、コントローラはルートハンドラです.
    @Controller()
    export class AppController {
      constructor(private readonly eventBus: EventBus, private queryBus: QueryBus) {}
    
      @Get()
      async bid(): Promise<object> {
    
        // We are hard-coding values here
        // instead of collecting them from a request
        this.eventBus.publish(
          new BidEvent('4ccd1088-b5da-44e2-baa0-ee4e0a58659d', '0ac04f2a-4866-42de-9387-cf392f64cd52', 233),
        );
    
        return {
          status: 'PENDING',
        };
      }
    
      @Get('/audiences')
      async getAudiences() {
        const allAudiences = await this.queryBus.execute(new GetAuctionQuery());
    
        return allAudiences;
      }
    }
    
    我々のシステムの本当の力は、BidSAGEクラスにあります.このクラス(サービス)の責任は、bideventsを聞いて、コマンドを発送することです.RXJSの経験とNGRXパッケージの書き込み効果を開発者は、このコードをおなじみと読みやすい見つける.
    @Injectable()
    export class BidSaga {
    
      @Saga()
      createBid = (events$: Observable<any>): Observable<ICommand> => {
        return events$.pipe(
          ofType(BidEvent),
          map((event: BidEvent) => {
            return new BidCommand(event.bidUser, event.auctionID, event.bidAmount);
          }),
        );
      }
    
      @Saga()
      createBidSuccess = (events$: Observable<any>): Observable<ICommand> => {
        return events$.pipe(
          ofType(BidEventSuccess),
          flatMap((event: BidEventSuccess) => {
    
            return [
              new MailCommand(event.user.email, {
                title: 'You did it...',
                message: 'Congrats',
              }),
              new PostponeAuctionCommand(event.auctionID),
              // create activity command
            ];
          }),
        );
      }
    }
    
    BitTransactionGuid変数を作成し、bideventに渡したことに注意してください.その値は、コマンドやイベントを接着するために使用されます.
    上記のコードでわかるように、bideventはbiCommandをディスパッチします.さらに、我々のコードBidHandler(BitCommandの)では、bideventSuccessまたはbideventfailのどちらかを送ります.
    export class AuctionModel extends AggregateRoot {
      constructor(private readonly auction: IAuctionInterface) {
        super();
      }
    
      postponeAuction() {
        // validation and etc.
    
        // postpone it, and return new auction object with postponed date
        const auction = { ...this.auction };
    
        this.apply(new AuctionEventsPostponed(auction));
      }
    
      bidOnAuction(userID: string, amount: number) {
        // validation and etc.
        try {
    
          // business logic
          // upon successful bidding, dispatch new event
          this.apply(new BidEventSuccess(this.auction.id, amount, { email: '[email protected]', id: userID }));
    
        } catch (e) {
    
          // dispatch bid event fail action
          this.apply(new BidEventFail(e));
        }
      }
    }
    
    上に示したモデルは、BidHandlerサービスを通して実行されます.
    bideventsuccessが送出された後、新しいコマンドが起動されます.
    @Injectable()
    export class AuctionSaga {
    
      @Saga()
      createBid = (events$: Observable<any>): Observable<ICommand> => {
        return events$.pipe(
          ofType(AuctionEventsPostponed),
          flatMap((event: AuctionEventsPostponed) => {
    
            // send emails to all existing bidders
            const bidders = [
              new MailCommand('bidder1@emailid', {
                title: 'Someone made a bid',
                message: 'Hurry up',
              }),
              new MailCommand('bidder2@emailid', {
                title: 'Someone made a bid',
                message: 'Hurry up',
              }),
            ];
    
            return [
              ...bidders,
              // create activity
            ];
          }),
        );
      }
    }
    
    上記の例で見ることができるように、すべてはコマンドを派遣し、新しいイベントでそれらを連鎖させることです.新しい機能は、新しいコマンドと後に引き起こされる新しいイベントの作成を意味します.
    何かがこのプロセスを通して失敗するならば、我々はシステムでこの入札に関連したものを削除するためにBitTransactionGuid情報で掃除命令を派遣することができます.

    結論


    それが適切な場所に、そして、正しいシナリオのために適用されるならば、イベント駆動型プログラミングパラダイムはアプリケーション・アーキテクチャのための巨大な勝利でありえます.プログラムの流れがイベントによって決定されるアプリケーションを考えるなら、それはこのプログラミングアプローチにぴったりです.
    リポジトリhttps://github.com/vladotesanovic/cqrs
    このポストで何か悪いことを見ている?あなたは正しいバージョンを見つけることができますhere .

    プラグイン:ログオン、WebアプリのDVR





    LogRocket あなたが自分のブラウザで起こったかのように問題を再生することができるフロントエンドのログツールです.代わりに、エラーが発生したり、スクリーンショットやログのダンプのユーザーを求めるのを推測するのではなく、LogRocketすぐに何が間違って理解するためにセッションをリプレイすることができます.これは、フレームワークに関係なく、任意のアプリケーションを完全に動作し、RedUx、Vuex、および@ NGRX/ストアからの追加のコンテキストを記録するプラグインがあります.

    ログのReduxのアクションと状態に加えて、ログログオンレコードコンソールログ、JavaScriptのエラー、StackTrart、ヘッダー/本文、ブラウザのメタデータ、およびカスタムログを使用してネットワークのリクエスト/応答.また、DOMは、最も複雑な単一ページのアプリのピクセル完璧なビデオを再現、ページ上のHTMLとCSSを記録するために楽器を計る.

    Try it for free .
    郵便How to use event-driven programming in Node.js 最初に現れたLogRocket Blog .