Javaにおけるデータバスパターン:コンポーネント通信の効率的な統合
別名
- イベントバス
- メッセージバス
データバスデザインパターンの目的
データバスデザインパターンは、システムのさまざまなコンポーネントが直接接続されることなくデータを交換できる集中型の通信チャネルを提供することを目的としており、疎結合を促進し、スケーラビリティと保守性を向上させます。
実際の例を用いたデータバスパターンの詳細な説明
実世界の例
大規模な空港をデータバスパターンの実世界の例として考えてみましょう。空港では、さまざまな航空会社、乗客、手荷物係、セキュリティ担当者が情報を通信および共有する必要があります。各エンティティが他のすべてのエンティティと直接通信する代わりに、空港は集中型のアナウンスシステム(データバス)を使用します。フライト情報、セキュリティアラート、その他の重要な更新はこのシステムを介してブロードキャストされ、各エンティティは自分に関連するメッセージをリッスンします。この設定により、空港は通信プロセスを分離し、各エンティティが必要な情報のみを受信することを保証すると同時に、既存のエンティティを中断することなくシステムを拡張し、新しいエンティティを統合できます。これは、Javaのデータバスパターンが集中型の通信とイベント処理を促進し、システムのスケーラビリティと保守性を向上させる方法に似ています。
簡単な言葉で
データバスは、転送されるメッセージまたはイベントのタイプに基づいて、通信のためにアプリケーションのコンポーネントを接続するデザインパターンです。このパターンは疎結合を促進し、コンポーネントが直接の依存関係なしに通信できるようにすることで、システムのスケーリングと保守を容易にします。
Javaにおけるデータバスパターンのプログラム例
オンラインでイベントの予約と参加を可能にするアプリがあるとします。イベントの広告などの通知を、イベントを開催するコミュニティまたは組織のすべての一般メンバーに送信したいと考えています。ただし、そのような広告をイベント管理者または主催者に送信したくありません。代わりに、すべてのメンバーに送信された新しい広告のタイミングに関する通知を送信します。データバスを使用すると、クラスまたはコンポーネントが特定のタイプのメッセージのみを受け入れるようにすることで、コミュニティメンバーをタイプ(一般メンバーまたはイベント管理者)別に選択的に通知できます。したがって、一般メンバーと管理者は、送信されるメッセージのタイプを知っていることを除いて、互いに、またはコミュニティ全体に通知するために使用される特定のクラスまたはコンポーネントを知る必要はありません。
上記のオンラインイベントアプリの例では、最初に`Member`インターフェースとその実装:`MessageCollectorMember`(通常のコミュニティメンバー)と`StatusMember`(イベント管理者または主催者)を定義します。
public interface Member extends Consumer<DataType> {
void accept(DataType event);
}
次に、メンバーをサブスクライブまたはサブスクライブ解除し、イベントを公開してすべてのコミュニティメンバーに通知するためのデータバスを実装します。
public class DataBus {
private static final DataBus INSTANCE = new DataBus();
private final Set<Member> listeners = new HashSet<>();
public static DataBus getInstance() {
return INSTANCE;
}
public void subscribe(final Member member) {
this.listeners.add(member);
}
public void unsubscribe(final Member member) {
this.listeners.remove(member);
}
public void publish(final DataType event) {
event.setDataBus(this);
listeners.forEach(
listener -> listener.accept(event));
}
}
`accept`メソッドは、`publish`メソッドの各メンバーに適用されます。
通常のコミュニティメンバー(`MessageCollectorMember`)の場合、`accept`メソッドは`MessageData`タイプのメッセージのみを処理できます。
public class MessageCollectorMember implements Member {
private final String name;
private final List<String> messages = new ArrayList<>();
public MessageCollectorMember(String name) {
this.name = name;
}
@Override
public void accept(final DataType data) {
if (data instanceof MessageData) {
handleEvent((MessageData) data);
}
}
}
イベント管理者または主催者(`StatusMember`)の場合、`accept`メソッドは`StartingData`および`StoppingData`タイプのメッセージを処理できます。
public class StatusMember implements Member {
private final int id;
private LocalDateTime started;
private LocalDateTime stopped;
public StatusMember(int id) {
this.id = id;
}
@Override
public void accept(final DataType data) {
if (data instanceof StartingData) {
handleEvent((StartingData) data);
} else if (data instanceof StoppingData) {
handleEvent((StoppingData) data);
}
}
}
データバスパターンを実装した`App`クラスの例を以下に示します。
class App {
public static void main(String[] args) {
final var bus = DataBus.getInstance();
bus.subscribe(new StatusMember(1));
bus.subscribe(new StatusMember(2));
final var foo = new MessageCollectorMember("Foo");
final var bar = new MessageCollectorMember("Bar");
bus.subscribe(foo);
bus.publish(StartingData.of(LocalDateTime.now()));
}
}
データバスがメッセージを公開すると、出力は次のようになります。
02:33:57.627 [main] INFO com.iluwatar.databus.members.StatusMember - Receiver 2 sees application started at 2022-10-26T02:33:57.613529100
02:33:57.633 [main] INFO com.iluwatar.databus.members.StatusMember - Receiver 1 sees application started at 2022-10-26T02:33:57.613529100
示されているように、`MessageCollectorMembers`は`MessageData`タイプのメッセージのみを受け入れるため、`StatusMember`(イベント管理者または主催者)にのみ表示される`StartingData`または`StoppingData`メッセージは表示されません。この選択的なメッセージ処理により、通常のコミュニティメンバーが管理通知を受信することがなくなります。
Javaでデータバスパターンを使用する場合
- 複数のコンポーネントがデータまたはイベントを共有する必要があるが、直接結合が望ましくない場合。
- 情報のフローが動的に変化する複雑なイベント駆動型システムの場合。
- コンポーネントが異なる環境にデプロイされる可能性のある分散システムの場合。
- マイクロサービスアーキテクチャでのサービス間通信の場合。
Javaにおけるデータバスパターンの実際のアプリケーション
- 大規模アプリケーションのイベント処理システム。
- マイクロサービスアーキテクチャでのサービス間通信。
- 株式取引プラットフォームなどのリアルタイムデータ処理システム。
- Springなどのフレームワーク、特にそのアプリケーションイベントメカニズムにおいて。
データバスパターンの利点とトレードオフ
利点
- 疎結合:コンポーネントは、互いに直接依存することなく相互作用できます。
- 柔軟性:既存のコンポーネントに影響を与えることなく、新しいサブスクライバーまたはパブリッシャーを追加できます。
- スケーラビリティ:パターンは、コンポーネントの独立したスケーリングをサポートします。
- 再利用性:バスとコンポーネントは、異なるシステム間で再利用できます。
トレードオフ
- 複雑さ:データバスを導入すると、システムアーキテクチャが複雑になる可能性があります。
- パフォーマンスオーバーヘッド:通信の追加レイヤーによってレイテンシが発生する可能性があります。
- デバッグの難しさ:特にイベントが多いシステムでは、バスを介したデータフローを追跡するのが難しい場合があります。
関連するJavaデザインパターン
- メディエーター:コンポーネント間の通信を促進しますが、データバスとは異なり、制御を一元化します。
- オブザーバー:データバスで使用されるパブリッシュ/サブスクライブメカニズムと同様に、複数のオブジェクトへの変更を通知します。
- パブリッシュ/サブスクライブ:データバスパターンは、多くの場合、パブリッシャーがサブスクライバーを認識せずにバスにメッセージを投稿するパブリッシュ/サブスクライブメカニズムを使用して実装されます。