イベントアグリゲータ

複数のオブジェクトからのチャンネルイベントを単一オブジェクトに集約し、クライアントの登録を簡素化します。

これは、2000年代半ばに執筆していた「エンタープライズアプリケーションアーキテクチャのさらなる開発」の一部です。残念ながら、それ以来、多くの他のことが私の注意を奪ってきたため、これ以上取り組む時間がありませんでしたし、近い将来もそうなる見込みはありません。そのため、この資料は非常にドラフト段階のものであり、再び取り組む時間を見つけることができるまで、修正や更新を行うつもりはありません。

多くのオブジェクトを持つシステムでは、クライアントがイベントを購読する場合に複雑さが生じる可能性があります。クライアントは、各オブジェクトごとに個別に検索して登録する必要があります。各オブジェクトに複数のイベントがある場合、各イベントには個別の購読が必要です。

イベントアグリゲータは、多くのオブジェクトのイベントの単一ソースとして機能します。クライアントがアグリゲータに登録するだけで済むように、多くのオブジェクトのすべてのイベントに登録します。

動作方法

イベントアグリゲータは、間接参照の簡単な要素です。最も単純な形式では、関心のあるすべてのソースオブジェクトに登録し、すべてのターゲットオブジェクトをイベントアグリゲータに登録します。イベントアグリゲータは、ソースオブジェクトからのイベントに反応して、そのイベントをターゲットオブジェクトに伝播します。

最も単純なイベントアグリゲータは、複数のオブジェクトからのイベントを自身に集約し、同じイベントをオブザーバーに渡します。イベントアグリゲータはイベントを一般化することもでき、ソースオブジェクトに固有のイベントをより一般的なイベントに変換します。これにより、アグリゲータのオブザーバーは、多くの個々のイベントタイプに登録する必要がなくなります。これにより、オブザーバーの登録プロセスは簡素化されますが、オブザーバーに実質的な影響を与えない可能性のあるイベントの通知を受けるというコストが発生します。

イベントアグリゲータはオブザーバパターンに基づいているため、オブザーバに関する危険領域をすべて考慮することが重要です。

いつ使用するのか

潜在的なイベントソースとなるオブジェクトが多数ある場合は、イベントアグリゲータが適切な選択肢です。オブザーバがすべてに登録するのではなく、登録ロジックをイベントアグリゲータに集約できます。登録の簡素化に加えて、イベントアグリゲータは、オブザーバの使用におけるメモリ管理の問題も簡素化します。

参考資料

イベントアグリゲータは、オブザーバ関係のみに焦点を当てた特定のファサードの一種と考えることができます。

例:コンサルタントの監視 (C#)

Thoughtworksに入社すると、すぐに携帯電話が支給されます。古いジョークでは、これは(CEOの)ロイが昼夜を問わずいつでも電話をかけられるようにしたかったからだということです。そこで、ここでは、ロイにすべてのコンサルタントの場所と、電話で呼び出せるかどうかを示す単純なアプリケーションを想像してみましょう。

図1:コンサルタントクラス

これを捉えるために、図1のようなコンサルタントオブジェクトを使用します。コンサルタントが移動すると、これらの移動に関するメッセージをキャプチャし、どのコンサルタントがどこへ移動したのかを示します。

これらのメッセージがシステムによって受信されると、コンサルタントオブジェクトに渡されて処理され、コンサルタントのステータスが更新されます。現在の場所の更新は非常に明らかですが、可用性の更新には多少の計算が必要です。基本的に、各コンサルタントには自宅があり、自宅にいる間は利用可能かどうかを選択できます。(移動中は常にロイの電話の気まぐれに左右されると仮定しています。)

class Consultant...

  public bool IsAvailable {
    get {return IsAvailableAt(_current_location);}
  }
  private bool IsAvailableAt(string location) {
    return (location == Home) ? _availableAtHome : true;
  }

そのため、コンサルタントが移動メッセージを処理する際には、変更される可能性のある2つのこと、つまり現在の場所と可用性を考慮する必要があります。それぞれについて個別のイベントをシグナルします。

class Consultant...

  public void HandleMovement(MsgMovement movement) {
    if (_current_location != movement.Place) {
      if (LocationChanged != null) LocationChanged(this, EventArgs.Empty);
      if (IsAvailableAt(movement.Place) != IsAvailable) {
        if (AvailabilityChanged != null) AvailabilityChanged(this, EventArgs.Empty);
      }
      _current_location = movement.Place;
    }
  }

このアプリケーションの画面は、すべてのコンサルタントのこれらのイベントに登録することを選択できますが、ここではイベントアグリゲータを使用します。イベントアグリゲータは、すべてのイベントに登録することでコンサルタントを監視します。

class EventAggregator...

  public void Listen (Consultant subject) {
    subject.LocationChanged += new Consultant.ConsultantEventHandler(HandleConsultantLocationChanged);
    subject.AvailabilityChanged += new Consultant.ConsultantEventHandler(HandleConsultantAvailabilityChanged);
  }

イベントを受信すると、変更の種類に固有のイベントと、変更が発生したことを示す一般的なイベントの2つのイベントをシグナルします。これにより、クライアントは希望する粒度で登録できます。

class EventAggregator...

  private void HandleConsultantLocationChanged (Consultant consultant, EventArgs args) {
    if (ConsultantLocationChanged != null) ConsultantLocationChanged(consultant, args); 
    if (ConsultantChanged != null) ConsultantChanged(consultant, args);
  }
  private void HandleConsultantAvailabilityChanged (Consultant consultant, EventArgs args) {
    if (ConsultantAvailabilityChanged != null) ConsultantAvailabilityChanged(consultant, args);
    if (ConsultantChanged != null) ConsultantChanged(consultant, args);
  }
  public event Consultant.ConsultantEventHandler ConsultantChanged;
  public event Consultant.ConsultantEventHandler ConsultantLocationChanged;
  public event Consultant.ConsultantEventHandler ConsultantAvailabilityChanged;

ほとんどの場合、単一の粗粒度のイベントを使用することをお勧めします。ただし、これは、クライアントのニーズに応じてさまざまな反応を提供することで、受信イベントにどのように反応できるかの良い例を示しています。これにより、イベントアグリゲータアダプタとファサードの両方として機能することもできます。