Coding Memo

Event Bus Pattern [이벤트 버스 패턴] 본문

etc

Event Bus Pattern [이벤트 버스 패턴]

minttea25 2023. 6. 18. 14:54

이벤트 버스를 통해 구독자와 게시자가 서로 통신하는 형태의 디자인 패턴으로 게시-구독(Publisher - Subscriber)의 형태로 구현된다.

서로 직접적인 종속성 없이 통신할 수 있도록 지원하는 패턴이다.

컴포넌트가 이벤트 버스에 이벤트를 게시(publish) 후, 다른 컴포넌트가 해당 이벤트를 구독하고 반응 (handle, trigger)하는 것이 핵심 디자인이다.

 

이벤트 버스 패턴도 관찰자 패턴과 마찬가지로 언어 자체적으로 간접적 지원을 위한 인터페이스를 제공하거나 언어와 관련된 프레임워크에 포함되는 경우가 많다.

Java Application Event Publisher (Spring)
C# Event-based Asynchronous Pattern (EAP), Event Keyword
Javascript Nodejs, React, Angular 등

이벤트 버스 패턴

Event Bus Pattern

 

Event Bus 게시자와 구독자 간 이벤트 교환을 하게 하는 통신 중재자 역할
Publisher 이벤트를 생성하는 구성 요소로 구독자가 누구인지 알 필요 없이 이벤트 버스에 이벤트를 게시
Subscriber 이벤트 버스에서 특정 이벤트를 수신하는 구성요소로 필요한 특정 이벤트를 구독(subscribe)하고, 이벤트가 버스에 게시 되면 구독자에게 알림이 전송되어 특정 작업 수행 (handle)

 

비유하자면,

Publisher는 신문사이고 EventBus는 신문 구독서비스를 해주는 업체, Subscriber는 신문 구독자들이다. 신문사는 신문 구독서비스 업체에게 신문을 게시해주고, 신문 구독서비스 업체는 구독자들에게 신문을 배달한다. 구독자들은 신문사가 어떤걸 게시하는지 상관치 않고 신문 구독서비스 업체에 특정 신문사를 구독 신청하면 된다.

이렇게 하면 신문사와 구독자는 서로 모르는 사이여도 무방하다.

(구독자는 신문을 받으면 어떻게 사용할지는 구독자 마음이다. (handle))

 


구현 (Example)

 

C#의 delegate와 EventBus를 이용해 이벤트 버스를 구현해보자.

 

 

EventData, EventArgs

C#의 이벤트 버스 클래스를 이용할 때 필요한 매개변수 개체이다.

(EventArgs를 상속받아 만든다.)

public class MyEventData
{
    public string Message { get; set; }
}

public class MyEventArgs : EventArgs
{
    public MyEventData Data { get; set; }
}

 

 

EventBus

public class EventBus
{
    private Dictionary<Type, List<Delegate>> eventHandlers;

    public EventBus()
    {
        eventHandlers = new Dictionary<Type, List<Delegate>>();
    }

    public void Subscribe<TEvent>(Action<TEvent> handler) where TEvent : MyEventArgs
    {
        Type eventType = typeof(TEvent);
        if (!eventHandlers.ContainsKey(eventType))
        {
            eventHandlers[eventType] = new List<Delegate>();
        }
        eventHandlers[eventType].Add(handler);
    }

    public void Unsubscribe<TEvent>(Action<TEvent> handler) where TEvent : MyEventArgs
    {
        Type eventType = typeof(TEvent);
        if (eventHandlers.ContainsKey(eventType))
        {
            eventHandlers[eventType].Remove(handler);
        }
    }

    public void Publish<TEvent>(TEvent eventArgs) where TEvent : MyEventArgs
    {
        Type eventType = typeof(TEvent);
        if (eventHandlers.ContainsKey(eventType))
        {
            List<Delegate> handlers = eventHandlers[eventType];
            foreach (var handler in handlers)
            {
                ((Action<TEvent>)handler)(eventArgs);
            }
        }
    }
}

타입으로 이벤트를 구별하고 Dictionary를 통해 이벤트 발생 시 실행할 함수를 delegate로 등록한다.

(Observer 패턴과 유사할지도)

 

Event Publisher

public class EventPublisher
{
    private EventBus eventBus;

    public EventPublisher(EventBus eventBus)
    {
        this.eventBus = eventBus;
    }

    public void PublishEvent()
    {
        MyEventData eventData = new() { Message = "Hello, World!" };
        MyEventArgs eventArgs = new() { Data = eventData };
        eventBus.Publish(eventArgs);
    }
}

이벤트 게시자(Publisher)는 이벤트 게시 시 해당 이벤트에 대한 데이터도 같이 넘겨주도록 한다.

 

 

 Event Subscriber

public class EventSubscriber
{
    public EventSubscriber(EventBus eventBus)
    {
        eventBus.Subscribe<MyEventArgs>(HandleEvent);
    }

    public void HandleEvent(MyEventArgs eventArgs)
    {
        Console.WriteLine("Event received: " + eventArgs.Data.Message);
    }
}

이벤트 구독자(Subscriber)는 이벤트가 발생했을 때 어떤 함수를 실행할지 결정한다. (HandleEvent)

이벤트 버스를 구독할 때 이벤트 발생시 실행될 함수를 인자로 넘겨준다.

 

Main

class Program
{
    public static void Main()
    {
        {
            EventBus eventBus = new();

            // Create and register subscribers
            EventSubscriber subscriber1 = new(eventBus);
            EventSubscriber subscriber2 = new(eventBus);

            EventPublisher publisher = new(eventBus);


            publisher.PublishEvent();
        }
    }
}

구독자를 생성하고 이벤트 버스를 구독한다.

게시자가 이벤트 버스에 이벤트를 게시한다.

 

 

실행 결과

실행 결과


이벤트 버스 패턴의 장단점

 

이벤트 버스는 관찰자패턴과 유사한 점을 많이 가지고 있어 장단점 또한 비슷하다.

Pros Cons
- Loose Coupling
- Decoupled Communication
- Scalability
- Extensibility
- Lack of direct control
- Increased Complexity
- Overuse and Performance
- Event Order and Synchronization

 

장점

  • Loose Coupling
    게시자와 구독자는 이벤트 버스를 통해서만 통신하므로 직접적인 의존성이 없어서 모듈성과 유연성이 향상됨

  • Decoupled Communication
    게시자와 구독자 간에 명시적인 참조 없이도 서로 통신이 가능하여 서로 분리가 가능해져 코드 유지 관리가 쉬워짐

  • Scalability
    다수의 게시자, 다수의 구독자를 설정할 수 있어 게시자와 구독자에 대한 확장성이 좋음

  • Extensibility
    구독자의 Handle에 대한 수정도 쉽고 이벤트 버스 시스템에 새로운 구성요소도 추가하기가 쉬움

 

단점

  • Lack of Direct Control
    이벤트 버스를 통해 통신이 이루어지기 때문에 이벤트 흐름을 모두 식별하고 추적하는 것이 어려움

  • Increased Complexity
    게시자와 구독자 사이에 추가적인 계층을 도입하는 것이기 때문에 시스템 구조적으로 좀 더 복잡해 질 수 있음

  • Overuse and Performance Impact
    이벤트 버스 패턴이 남용되어 불필요한 이벤트 게시 및 이벤트 처리(handle) 오버헤드가 발생할 수 있음

  • Event Order and Synchronization
    순서를 명시적으로 정의하지 않는 이상 handle의 실행 순서는 보장되지 않고 이는 순서 종속성이 있을 경우 문제가 발생할 수 있으며 따로 컨트롤 해줄 필요가 있음

 


관찰자 패턴과 이벤트 버스 패턴

 

  관찰자 패턴 이벤트 버스 패턴
관계 1:N N:M
통신 Direct (직접적) Mediated Communication
(Event Bus 중개자를 통한 통신)
의존성 Tight (이벤트 버스 패턴에 비해 강함) Loose
범위 단응 응용 프로그램에서 구성 요소 간 서로 다른 구성 요소 및 모듈, 시스템 간
등록 Explicit
(관찰자는 주체에 명시적으로 등록/해제)
Implicit
(구독자는 게시자에게 명시적으로 등록을 하지 않음, 대신 이벤트 버스의 이벤트를 구독 및 게시)

 

관찰자 패턴은 어떻게 구현하느냐에 따라 의존성이 크게 달라 질 수도 있는 반면에 이벤트 버스 패턴은 이벤스 버스를 중간에 두고 확실하게 구분하여 구현할 수가 있다.

 

또한 관찰자 패턴은 1개의 주체에 대해서 관찰자들을 등록하는 것이지만, 이벤트 버스 패턴은 여러개의 이벤트를 구독자가 등록할 수 있다.

 

정리하면, 관찰자 패턴은 직접적인 통신과 비교적 강한 의존성으로 특정 범위 내에서 관찰자와 주체를 일대다의 관계로 연결하는데에 중점을 두고 있다. 반면에 이벤트 버스 패턴은 중앙 집중식의 이벤트 버스를 통해 게시자와 구독자 간의 다대다 통신을 지원하여 서로 다른 구성 요소 및 모듈, 시스템의 글로벌 통신에 주로 이용된다.


Summary

 

이벤트 버스 패턴은 분리된 두 시스템의 통신을  용이하게 하기 위한 디자인 패턴이다. 개발하려는 시스템의 요구사항에 맞게 이벤트 버스 패턴을 이용할지, 관찰자 패턴을 이용할지 선택하는 것은 개발자의 몫이고 고민할 필요가 있다.

'etc' 카테고리의 다른 글

함수 호출 in stack  (0) 2023.06.25
스택과 힙 (stack & heap), 메모리 레이아웃  (0) 2023.06.25
Command Pattern [커맨드 패턴]  (0) 2023.06.18
Observer Pattern [관찰자 패턴]  (0) 2023.06.11
Singleton Design Pattern [싱글턴 패턴]  (0) 2023.06.11